mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 10:03:23 +09:00
[Perf] (Magma): wire texture and buffer lookups get direct-mapped fronts and a continued pass stops re-marking its attachments (rd12 monolith 59.6 -> 60.5 fps).
This commit is contained in:
@@ -184,8 +184,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
VkBufferManager::WireBufferResource* VkBufferManager::FindWireBuffer(MG_Pipe::MGPipeHandle res) {
|
VkBufferManager::WireBufferResource* VkBufferManager::FindWireBuffer(MG_Pipe::MGPipeHandle res) {
|
||||||
const auto found = m_wireBuffers.find(WireBufferKey(res));
|
const Uint64 key = WireBufferKey(res);
|
||||||
return found == m_wireBuffers.end() ? nullptr : &found->second;
|
auto& lookup = m_wireBufferLookup[key % kWireBufferLookupEntries];
|
||||||
|
if (lookup.resource != nullptr && lookup.key == key && lookup.eraseCount == m_wireBufferEraseCount)
|
||||||
|
return lookup.resource;
|
||||||
|
const auto found = m_wireBuffers.find(key);
|
||||||
|
if (found == m_wireBuffers.end()) return nullptr;
|
||||||
|
lookup = {key, m_wireBufferEraseCount, &found->second};
|
||||||
|
return &found->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
void VkBufferManager::CreateWireBuffer(MG_Pipe::MGPipeHandle res, const MG_Pipe::MGPResourceDesc& desc) {
|
void VkBufferManager::CreateWireBuffer(MG_Pipe::MGPipeHandle res, const MG_Pipe::MGPResourceDesc& desc) {
|
||||||
@@ -992,6 +998,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
DeferWireRelease(std::move(found->second.buffer),
|
DeferWireRelease(std::move(found->second.buffer),
|
||||||
found->second.imported ? T0ReleaseSerial(found->second.lastUseSerial)
|
found->second.imported ? T0ReleaseSerial(found->second.lastUseSerial)
|
||||||
: found->second.lastUseSerial); // P11 B2, as Respecify
|
: found->second.lastUseSerial); // P11 B2, as Respecify
|
||||||
|
++m_wireBufferEraseCount;
|
||||||
m_wireBuffers.erase(found);
|
m_wireBuffers.erase(found);
|
||||||
PublishWireReclaimGauges();
|
PublishWireReclaimGauges();
|
||||||
}
|
}
|
||||||
@@ -1163,6 +1170,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// DestroyAllDeferredReleases above emptied the parked list; this destroys the stores the
|
// DestroyAllDeferredReleases above emptied the parked list; this destroys the stores the
|
||||||
// records still hold, so nothing this arm minted outlives the count.
|
// records still hold, so nothing this arm minted outlives the count.
|
||||||
if (!m_wireBuffers.empty()) ++m_wireStoreDestroyEpoch;
|
if (!m_wireBuffers.empty()) ++m_wireStoreDestroyEpoch;
|
||||||
|
++m_wireBufferEraseCount;
|
||||||
m_wireBuffers.clear();
|
m_wireBuffers.clear();
|
||||||
m_wireStoreCount = 0;
|
m_wireStoreCount = 0;
|
||||||
m_wireStoreCountPeak = 0;
|
m_wireStoreCountPeak = 0;
|
||||||
|
|||||||
@@ -292,6 +292,18 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
IBufferCopyCommandProvider* m_copyProvider = nullptr;
|
IBufferCopyCommandProvider* m_copyProvider = nullptr;
|
||||||
Vector<Vector<VkBufferObject>> m_deferredBufferReleases;
|
Vector<Vector<VkBufferObject>> m_deferredBufferReleases;
|
||||||
std::unordered_map<Uint64, WireBufferResource> m_wireBuffers;
|
std::unordered_map<Uint64, WireBufferResource> m_wireBuffers;
|
||||||
|
// P14: a direct-mapped front for m_wireBuffers (every draw resolves each vertex, index and
|
||||||
|
// uniform buffer). Keyed on WireBufferKey ({slot, gen}, never reused) and believed only at
|
||||||
|
// the erase count it was taken at: a node never moves until erased, and every erase moves
|
||||||
|
// the count.
|
||||||
|
struct WireBufferLookup {
|
||||||
|
Uint64 key = 0;
|
||||||
|
Uint64 eraseCount = 0;
|
||||||
|
WireBufferResource* resource = nullptr;
|
||||||
|
};
|
||||||
|
static constexpr SizeT kWireBufferLookupEntries = 256;
|
||||||
|
Array<WireBufferLookup, kWireBufferLookupEntries> m_wireBufferLookup{};
|
||||||
|
Uint64 m_wireBufferEraseCount = 0;
|
||||||
// See DeferredWireRelease. ONE FLAT LIST rather than the per-frame-slot buckets above:
|
// See DeferredWireRelease. ONE FLAT LIST rather than the per-frame-slot buckets above:
|
||||||
// the whole point is that these entries do not wait for a frame slot to come round.
|
// the whole point is that these entries do not wait for a frame slot to come round.
|
||||||
Vector<DeferredWireRelease> m_deferredWireReleases;
|
Vector<DeferredWireRelease> m_deferredWireReleases;
|
||||||
|
|||||||
@@ -1874,9 +1874,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
const Uint64 key = MG_Record::StagedTextureStore::KeyForHandle(handle);
|
const Uint64 key = MG_Record::StagedTextureStore::KeyForHandle(handle);
|
||||||
auto [it, inserted] = resources.try_emplace(key);
|
auto& lookup = (renderbuffer ? m_wireRenderbufferLookup : m_wireTextureLookup)[key % kWireResourceLookupEntries];
|
||||||
(void)inserted;
|
if (lookup.resource == nullptr || lookup.key != key || lookup.eraseEpoch != m_resourceEraseEpoch) {
|
||||||
TextureResource& resource = it->second;
|
auto [it, inserted] = resources.try_emplace(key);
|
||||||
|
(void)inserted;
|
||||||
|
lookup = {key, m_resourceEraseEpoch, &it->second};
|
||||||
|
}
|
||||||
|
TextureResource& resource = *lookup.resource;
|
||||||
if (resource.image != VK_NULL_HANDLE && resource.syncedWireSerial == record.Serial &&
|
if (resource.image != VK_NULL_HANDLE && resource.syncedWireSerial == record.Serial &&
|
||||||
record.PendingUploads.empty() && (!requireStorage || (resource.usageFlags & VK_IMAGE_USAGE_STORAGE_BIT) != 0)) {
|
record.PendingUploads.empty() && (!requireStorage || (resource.usageFlags & VK_IMAGE_USAGE_STORAGE_BIT) != 0)) {
|
||||||
return &resource;
|
return &resource;
|
||||||
|
|||||||
@@ -693,6 +693,17 @@ private:
|
|||||||
// SyncWireTextureShape defining the RGBA8 storage a YUV shared image is converted into.
|
// SyncWireTextureShape defining the RGBA8 storage a YUV shared image is converted into.
|
||||||
Bool m_syncingYuvTextureStorage = false;
|
Bool m_syncingYuvTextureStorage = false;
|
||||||
Uint64 m_resourceEraseEpoch = 1;
|
Uint64 m_resourceEraseEpoch = 1;
|
||||||
|
// P14: a direct-mapped front for the two wire resource maps, keyed on the handle key
|
||||||
|
// ({slot, gen}, never reused) and believed only at the erase epoch it was taken at - an
|
||||||
|
// unordered_map node never moves until it is erased, and every erase moves the epoch.
|
||||||
|
struct WireResourceLookup {
|
||||||
|
Uint64 key = 0;
|
||||||
|
Uint64 eraseEpoch = 0;
|
||||||
|
TextureResource* resource = nullptr;
|
||||||
|
};
|
||||||
|
static constexpr SizeT kWireResourceLookupEntries = 256;
|
||||||
|
Array<WireResourceLookup, kWireResourceLookupEntries> m_wireTextureLookup{};
|
||||||
|
Array<WireResourceLookup, kWireResourceLookupEntries> m_wireRenderbufferLookup{};
|
||||||
// Formats whose mutable-image probe failed on this device; their images are created
|
// Formats whose mutable-image probe failed on this device; their images are created
|
||||||
// without MUTABLE_FORMAT_BIT so repeat syncs neither re-probe nor flag-mismatch.
|
// without MUTABLE_FORMAT_BIT so repeat syncs neither re-probe nor flag-mismatch.
|
||||||
std::unordered_set<VkFormat> m_mutableFormatUnsupported;
|
std::unordered_set<VkFormat> m_mutableFormatUnsupported;
|
||||||
|
|||||||
@@ -825,7 +825,16 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
Vector<WireImage> sameLayoutAttachments;
|
Vector<WireImage> sameLayoutAttachments;
|
||||||
WireDrawPassKey passKey;
|
WireDrawPassKey passKey;
|
||||||
WireRenderPassCompatibilityKey compatibilityKey;
|
WireRenderPassCompatibilityKey compatibilityKey;
|
||||||
|
struct GpuWriteMark {
|
||||||
|
MG_Pipe::MGPipeHandle storage;
|
||||||
|
Uint32 level, layer, layers;
|
||||||
|
};
|
||||||
|
Vector<GpuWriteMark> gpuWriteMarks;
|
||||||
};
|
};
|
||||||
|
// The current wire pass's attachments were marked GPU-written at this StagedTextureStore
|
||||||
|
// clear generation (SetupWireDraw); false whenever the pass changes.
|
||||||
|
Bool m_wirePassMarksValid = false;
|
||||||
|
Uint64 m_wirePassMarksClearGeneration = 0;
|
||||||
WireDrawScratch m_wireDrawScratch;
|
WireDrawScratch m_wireDrawScratch;
|
||||||
struct WireRetiredObjects {
|
struct WireRetiredObjects {
|
||||||
Uint64 submitIndex = 0;
|
Uint64 submitIndex = 0;
|
||||||
|
|||||||
@@ -105,6 +105,7 @@
|
|||||||
}
|
}
|
||||||
m_wireDrawPass.reset();
|
m_wireDrawPass.reset();
|
||||||
m_wireDrawPassCompatibilityId = 0;
|
m_wireDrawPassCompatibilityId = 0;
|
||||||
|
m_wirePassMarksValid = false;
|
||||||
for (const auto view : m_wireDrawViews) vkDestroyImageView(m_device, view, nullptr);
|
for (const auto view : m_wireDrawViews) vkDestroyImageView(m_device, view, nullptr);
|
||||||
m_wireDrawViews.clear();
|
m_wireDrawViews.clear();
|
||||||
m_wireDrawPassKey = {};
|
m_wireDrawPassKey = {};
|
||||||
@@ -119,6 +120,7 @@
|
|||||||
|
|
||||||
void VulkanRenderer::RetireWireDrawPass() {
|
void VulkanRenderer::RetireWireDrawPass() {
|
||||||
m_wireDrawPassCompatibilityId = 0;
|
m_wireDrawPassCompatibilityId = 0;
|
||||||
|
m_wirePassMarksValid = false;
|
||||||
if (!m_wireDrawPass) return;
|
if (!m_wireDrawPass) return;
|
||||||
auto& frame = m_frameContext.GetCurrent();
|
auto& frame = m_frameContext.GetCurrent();
|
||||||
if (frame.isCommandRecording && VkRenderPassManager::GetActiveRenderPass())
|
if (frame.isCommandRecording && VkRenderPassManager::GetActiveRenderPass())
|
||||||
@@ -506,6 +508,7 @@
|
|||||||
}
|
}
|
||||||
auto& sameLayoutAttachments = scratch.sameLayoutAttachments;
|
auto& sameLayoutAttachments = scratch.sameLayoutAttachments;
|
||||||
sameLayoutAttachments.clear();
|
sameLayoutAttachments.clear();
|
||||||
|
scratch.gpuWriteMarks.clear();
|
||||||
const auto add = [&](const MG_Pipe::MGPSurface& surface, VkImageAspectFlags aspect) -> Uint32 {
|
const auto add = [&](const MG_Pipe::MGPSurface& surface, VkImageAspectFlags aspect) -> Uint32 {
|
||||||
WireImage image = ResolveWireImage(*fbo, surface, aspect, /*isWriteTarget=*/true);
|
WireImage image = ResolveWireImage(*fbo, surface, aspect, /*isWriteTarget=*/true);
|
||||||
if (!image.image) return VK_ATTACHMENT_UNUSED;
|
if (!image.image) return VK_ATTACHMENT_UNUSED;
|
||||||
@@ -543,8 +546,9 @@
|
|||||||
layers = std::min(layers, image.layers);
|
layers = std::min(layers, image.layers);
|
||||||
samples = image.samples;
|
samples = image.samples;
|
||||||
passHash = XXH64(&description, sizeof(description), passHash);
|
passHash = XXH64(&description, sizeof(description), passHash);
|
||||||
|
// Marked below, once the pass is known: a continued pass repeats the same marks.
|
||||||
if (!image.isDefault && surface.Kind == MG_Pipe::kMGPipeSurfaceKindTexture)
|
if (!image.isDefault && surface.Kind == MG_Pipe::kMGPipeSurfaceKindTexture)
|
||||||
m_textureManager->MarkWireTextureGpuWritten(image.storage, image.level, image.layer, image.layers);
|
scratch.gpuWriteMarks.push_back({image.storage, image.level, image.layer, image.layers});
|
||||||
if (image.isDefault) {
|
if (image.isDefault) {
|
||||||
if (aspect == VK_IMAGE_ASPECT_COLOR_BIT) m_swapchainObject.SetImageContentDefined(image.swapchainImageIndex, true);
|
if (aspect == VK_IMAGE_ASPECT_COLOR_BIT) m_swapchainObject.SetImageContentDefined(image.swapchainImageIndex, true);
|
||||||
else m_swapchainObject.SetDepthStencilContentDefined(image.swapchainImageIndex, true);
|
else m_swapchainObject.SetDepthStencilContentDefined(image.swapchainImageIndex, true);
|
||||||
@@ -625,6 +629,19 @@
|
|||||||
created.layers = layers;
|
created.layers = layers;
|
||||||
created.hash = created.compatibilityHash = XXH64(colors.data(), colors.size() * sizeof(colors[0]), passHash);
|
created.hash = created.compatibilityHash = XXH64(colors.data(), colors.size() * sizeof(colors[0]), passHash);
|
||||||
}
|
}
|
||||||
|
// The attachments' GPU-write marks (MarkWireTextureGpuWritten): idempotent while nothing
|
||||||
|
// clears a GPU-dirty mark, so a draw that continues a pass whose draws already marked the
|
||||||
|
// same attachments - the pass key compared equal - repeats them only after a clear.
|
||||||
|
{
|
||||||
|
auto& store = MG_Record::ServerStagedTexture();
|
||||||
|
if (!continuing || !m_wirePassMarksValid ||
|
||||||
|
m_wirePassMarksClearGeneration != store.GpuDirtyClearGeneration()) {
|
||||||
|
for (const auto& mark : scratch.gpuWriteMarks)
|
||||||
|
m_textureManager->MarkWireTextureGpuWritten(mark.storage, mark.level, mark.layer, mark.layers);
|
||||||
|
m_wirePassMarksValid = true;
|
||||||
|
m_wirePassMarksClearGeneration = store.GpuDirtyClearGeneration();
|
||||||
|
}
|
||||||
|
}
|
||||||
auto& pass = *m_wireDrawPass;
|
auto& pass = *m_wireDrawPass;
|
||||||
// Interned once per pass: a continued pass has the same key, so the same compatibility.
|
// Interned once per pass: a continued pass has the same key, so the same compatibility.
|
||||||
if (m_wireDrawPassCompatibilityId == 0) {
|
if (m_wireDrawPassCompatibilityId == 0) {
|
||||||
|
|||||||
@@ -373,6 +373,7 @@ namespace MobileGL::MG_Record {
|
|||||||
m_driverWritten[bucketed] = true;
|
m_driverWritten[bucketed] = true;
|
||||||
}
|
}
|
||||||
shadow.GpuDirty = false;
|
shadow.GpuDirty = false;
|
||||||
|
m_gpuDirtyClears.fetch_add(1, std::memory_order_relaxed);
|
||||||
return CopyRunInto(shadow, key, uploadTarget, level, imageOffset, bytes, byteSize, frontendShadow);
|
return CopyRunInto(shadow, key, uploadTarget, level, imageOffset, bytes, byteSize, frontendShadow);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -390,6 +391,7 @@ namespace MobileGL::MG_Record {
|
|||||||
LevelShadow& shadow = m_shadows[bucketed].Levels[PackLevel(uploadTarget, level)];
|
LevelShadow& shadow = m_shadows[bucketed].Levels[PackLevel(uploadTarget, level)];
|
||||||
RequireDeclaredLevel(shadow, key, uploadTarget, level, extent);
|
RequireDeclaredLevel(shadow, key, uploadTarget, level, extent);
|
||||||
shadow.GpuDirty = false;
|
shadow.GpuDirty = false;
|
||||||
|
m_gpuDirtyClears.fetch_add(1, std::memory_order_relaxed);
|
||||||
// P8-E: the caller states these bytes ARE the level, which a followed copy is part of.
|
// P8-E: the caller states these bytes ARE the level, which a followed copy is part of.
|
||||||
shadow.HoldsFollowedCopy = false;
|
shadow.HoldsFollowedCopy = false;
|
||||||
shadow.Bytes.clear();
|
shadow.Bytes.clear();
|
||||||
@@ -454,6 +456,7 @@ namespace MobileGL::MG_Record {
|
|||||||
shadow.Covered.clear();
|
shadow.Covered.clear();
|
||||||
shadow.GpuDirty = false;
|
shadow.GpuDirty = false;
|
||||||
shadow.HoldsFollowedCopy = false;
|
shadow.HoldsFollowedCopy = false;
|
||||||
|
m_gpuDirtyClears.fetch_add(1, std::memory_order_relaxed);
|
||||||
}
|
}
|
||||||
shadow.Extent = extent;
|
shadow.Extent = extent;
|
||||||
shadow.InternalFormat = internalFormat;
|
shadow.InternalFormat = internalFormat;
|
||||||
@@ -471,6 +474,7 @@ namespace MobileGL::MG_Record {
|
|||||||
const ShadowKey bucketed = Bucketed(key);
|
const ShadowKey bucketed = Bucketed(key);
|
||||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
m_shadows.erase(bucketed);
|
m_shadows.erase(bucketed);
|
||||||
|
m_gpuDirtyClears.fetch_add(1, std::memory_order_relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
// T5's dirty mark: a GPU-side generation made this level's texels newer than any
|
// T5's dirty mark: a GPU-side generation made this level's texels newer than any
|
||||||
@@ -482,14 +486,22 @@ namespace MobileGL::MG_Record {
|
|||||||
const ShadowKey bucketed = Bucketed(key);
|
const ShadowKey bucketed = Bucketed(key);
|
||||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
m_shadows[bucketed].Levels[PackLevel(uploadTarget, level)].GpuDirty = dirty;
|
m_shadows[bucketed].Levels[PackLevel(uploadTarget, level)].GpuDirty = dirty;
|
||||||
|
if (!dirty) m_gpuDirtyClears.fetch_add(1, std::memory_order_relaxed);
|
||||||
m_any.store(true, std::memory_order_release);
|
m_any.store(true, std::memory_order_release);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// P14: moves whenever a level's GPU-dirty mark may have gone false - a CPU run or adoption
|
||||||
|
// over it, a redefinition, an explicit clear, or a level/shadow dropped. A caller that
|
||||||
|
// re-marks the same levels every draw (Magma's attachments) may skip the repeat while it
|
||||||
|
// has not moved: marking is idempotent until something clears it.
|
||||||
|
Uint64 GpuDirtyClearGeneration() const { return m_gpuDirtyClears.load(std::memory_order_relaxed); }
|
||||||
|
|
||||||
void Drop(Uint64 key) {
|
void Drop(Uint64 key) {
|
||||||
if (!m_any.load(std::memory_order_acquire)) return;
|
if (!m_any.load(std::memory_order_acquire)) return;
|
||||||
const ShadowKey bucketed = Bucketed(key);
|
const ShadowKey bucketed = Bucketed(key);
|
||||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
m_shadows.erase(bucketed);
|
m_shadows.erase(bucketed);
|
||||||
|
m_gpuDirtyClears.fetch_add(1, std::memory_order_relaxed);
|
||||||
m_driverWritten.erase(bucketed);
|
m_driverWritten.erase(bucketed);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -503,10 +515,12 @@ namespace MobileGL::MG_Record {
|
|||||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
if (session == 0) {
|
if (session == 0) {
|
||||||
m_shadows.clear();
|
m_shadows.clear();
|
||||||
|
m_gpuDirtyClears.fetch_add(1, std::memory_order_relaxed);
|
||||||
m_driverWritten.clear();
|
m_driverWritten.clear();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
EraseSession(m_shadows, session);
|
EraseSession(m_shadows, session);
|
||||||
|
m_gpuDirtyClears.fetch_add(1, std::memory_order_relaxed);
|
||||||
EraseSession(m_driverWritten, session);
|
EraseSession(m_driverWritten, session);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1024,6 +1038,7 @@ namespace MobileGL::MG_Record {
|
|||||||
// Read on every IsLevelGpuDirty / LevelExtentOrUndefined, so the monolith cost is one
|
// Read on every IsLevelGpuDirty / LevelExtentOrUndefined, so the monolith cost is one
|
||||||
// acquire load of a never-written flag rather than a mutex and two hash lookups.
|
// acquire load of a never-written flag rather than a mutex and two hash lookups.
|
||||||
std::atomic<Bool> m_any{false};
|
std::atomic<Bool> m_any{false};
|
||||||
|
std::atomic<Uint64> m_gpuDirtyClears{0};
|
||||||
};
|
};
|
||||||
|
|
||||||
// ONE PER PROCESS, and its copying arm is decided ONCE at first use - StagedShadow's
|
// ONE PER PROCESS, and its copying arm is decided ONCE at first use - StagedShadow's
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ run_one() {
|
|||||||
adb -s "$SER" logcat -c >/dev/null 2>&1
|
adb -s "$SER" logcat -c >/dev/null 2>&1
|
||||||
if [ "$(arm_kind "$arm")" = tcp ]; then
|
if [ "$(arm_kind "$arm")" = tcp ]; then
|
||||||
python "$(cygpath -w "$REPO_ROOT/tools/trace_replay/tcp_device_server.py")" start --serial "$SER" --package "$pkg" \
|
python "$(cygpath -w "$REPO_ROOT/tools/trace_replay/tcp_device_server.py")" start --serial "$SER" --package "$pkg" \
|
||||||
--backend "$backend" --listen tcp://127.0.0.1:40613 >"$dir/server-start.log" 2>&1 || { echo server_start_failed >"$dir/STATUS"; log "FAIL tcp server"; return 1; }
|
--backend "$backend" --listen tcp://127.0.0.1:40613 ${EXTRA_SERVER_ENV:+--env "$EXTRA_SERVER_ENV"} >"$dir/server-start.log" 2>&1 || { echo server_start_failed >"$dir/STATUS"; log "FAIL tcp server"; return 1; }
|
||||||
local waited=0
|
local waited=0
|
||||||
until adb -s "$SER" logcat -d 2>/dev/null | grep -q "listening on tcp://127.0.0.1:40613"; do
|
until adb -s "$SER" logcat -d 2>/dev/null | grep -q "listening on tcp://127.0.0.1:40613"; do
|
||||||
sleep 1; waited=$((waited + 1)); [ $waited -ge 30 ] && { echo server_not_listening >"$dir/STATUS"; log "FAIL tcp listen"; return 1; }
|
sleep 1; waited=$((waited + 1)); [ $waited -ge 30 ] && { echo server_not_listening >"$dir/STATUS"; log "FAIL tcp listen"; return 1; }
|
||||||
|
|||||||
@@ -53,6 +53,21 @@ def at(points, t):
|
|||||||
return v0 + (v1 - v0) * (t - t0) / max(1, t1 - t0)
|
return v0 + (v1 - v0) * (t - t0) / max(1, t1 - t0)
|
||||||
|
|
||||||
|
|
||||||
|
def window_delta(points, start, end):
|
||||||
|
"""On-CPU ns a counter series gained inside [start, end]: the sum of its positive steps, each
|
||||||
|
weighted by how much of its interval lies in the window. Robust to a counter that restarts
|
||||||
|
(a thread id reused by a new thread under the same name reads as a drop, which is skipped)."""
|
||||||
|
if not points or points[0][0] > start + 250_000_000 or points[-1][0] < end - 250_000_000:
|
||||||
|
return None
|
||||||
|
total = 0.0
|
||||||
|
for (t0, v0), (t1, v1) in zip(points, points[1:]):
|
||||||
|
if t1 <= start or t0 >= end or v1 <= v0:
|
||||||
|
continue
|
||||||
|
overlap = min(t1, end) - max(t0, start)
|
||||||
|
total += (v1 - v0) * overlap / max(1, t1 - t0)
|
||||||
|
return total
|
||||||
|
|
||||||
|
|
||||||
def reduce_run(run_dir, wl):
|
def reduce_run(run_dir, wl):
|
||||||
bench_path = run_dir / "benchmark.json"
|
bench_path = run_dir / "benchmark.json"
|
||||||
if not bench_path.exists():
|
if not bench_path.exists():
|
||||||
@@ -82,14 +97,14 @@ def reduce_run(run_dir, wl):
|
|||||||
for (pid, tid, comm), pts in series.items():
|
for (pid, tid, comm), pts in series.items():
|
||||||
if pid < 0:
|
if pid < 0:
|
||||||
continue
|
continue
|
||||||
a, b = at(pts, start), at(pts, end)
|
used = window_delta(pts, start, end)
|
||||||
if a is None or b is None:
|
if used is None:
|
||||||
continue
|
continue
|
||||||
ms = (b - a) / 1e6 / len(steady)
|
ms = used / 1e6 / len(steady)
|
||||||
if ms <= 0.005:
|
if ms <= 0.005:
|
||||||
continue
|
continue
|
||||||
threads.append({"pid": pid, "tid": tid, "comm": comm, "ms_per_frame": ms,
|
threads.append({"pid": pid, "tid": tid, "comm": comm, "ms_per_frame": ms,
|
||||||
"util": (b - a) / span})
|
"util": used / span})
|
||||||
procs[pid] += ms
|
procs[pid] += ms
|
||||||
threads.sort(key=lambda r: -r["ms_per_frame"])
|
threads.sort(key=lambda r: -r["ms_per_frame"])
|
||||||
out["threads"] = threads[:8]
|
out["threads"] = threads[:8]
|
||||||
|
|||||||
Reference in New Issue
Block a user