diff --git a/mcp/tests/Control_Path_Benchmarks.cpp b/mcp/tests/Control_Path_Benchmarks.cpp index ff6372a..76c4fad 100644 --- a/mcp/tests/Control_Path_Benchmarks.cpp +++ b/mcp/tests/Control_Path_Benchmarks.cpp @@ -277,7 +277,10 @@ protected: const auto input = configuration.input; for (auto& count : workload->completed) count->store(0, std::memory_order_relaxed); - for (const auto& plot : plots) plot->reset_diagnostics(); + for (const auto& plot : plots) { + plot->reset_diagnostics(); + plot->request_taskflow_trace(1); + } if (input == Input_Workload::drag || input == Input_Workload::mixed) { if (!submit_input_batch( input, 0, Event_Type::pointer_press)) { @@ -478,6 +481,29 @@ protected: ? busy_ns / (wall_ns * worker_count) * 100.0 : 0.0; state.counters["worker_cpu_pct"] = wall_ns > 0.0 && worker_count > 0.0 ? cpu_ns / (wall_ns * worker_count) * 100.0 : 0.0; + double longest_sampled_busy_ms{}; + std::string longest_sampled_busy_node; + std::string longest_sampled_busy_plot; + for (std::size_t plot_index = 0; plot_index < plots.size(); ++plot_index) { + const auto trace = plots[plot_index]->taskflow_trace(); + if (!trace.value("complete", false)) continue; + for (const auto& frame : trace.at("frames")) + for (const auto& execution : frame.at("executions")) { + const auto busy = std::max( + 0.0, execution.value("duration_ms", 0.0) - + execution.value("cooperative_wait_ms", 0.0)); + if (busy <= longest_sampled_busy_ms) continue; + longest_sampled_busy_ms = busy; + longest_sampled_busy_node = execution.value( + "node_id", std::string{}); + longest_sampled_busy_plot = + std::string{configuration.plot_ids[plot_index]}; + } + } + state.counters["sampled_longest_busy_ms"] = longest_sampled_busy_ms; + if (!longest_sampled_busy_node.empty()) + state.SetLabel("busy=" + longest_sampled_busy_plot + ":" + + longest_sampled_busy_node); state.counters["cooperative_yields"] = cooperative_waits; state.counters["cooperative_wait_ms"] = cooperative_wait_ns / 1'000'000.0; @@ -632,9 +658,13 @@ public: << " input_requests/s=" << counter("input_request_rate") << " worker_busy=" << counter("worker_busy_pct") << "%" << " worker_cpu=" << counter("worker_cpu_pct") << "%" + << " sampled_busy_max=" + << counter("sampled_longest_busy_ms") << " ms" << " cooperative_yields=" << counter("cooperative_yields") << " cooperative_wait=" << counter("cooperative_wait_ms") << " ms\n"; + if (!report.report_label.empty()) + output << report.report_label << '\n'; } output.flags(flags); output.precision(precision); diff --git a/render_3D/render_3D/scene/Render_Scene_3D.ipp b/render_3D/render_3D/scene/Render_Scene_3D.ipp index bd95486..e460cb4 100644 --- a/render_3D/render_3D/scene/Render_Scene_3D.ipp +++ b/render_3D/render_3D/scene/Render_Scene_3D.ipp @@ -137,7 +137,7 @@ private: owner runtime{}; /* 本目标槽独占的 DRP2 Runtime。 */ owner emitter{}; /* 本目标槽独占的帧计划 emitter。 */ }; - std::shared_ptr render_context_{}; /* 本 Scene 独占 GPU 上下文的共享销毁闸门。 */ + std::shared_ptr render_context_{}; /* 按 GPU 索引与验证模式共享的进程级上下文。 */ std::uintptr_t command_pool_{}; /* 本 Scene 独占 Vulkan Command Pool。 */ std::uintptr_t descriptor_pool_{}; /* 本 Scene 独占 Vulkan Descriptor Pool。 */ std::array runtime_slots_{}; /* 三个固定目标槽的 Runtime。 */ diff --git a/render_3D/render_3D/scene/detail/Datoviz_Frame_Pipeline.ipp b/render_3D/render_3D/scene/detail/Datoviz_Frame_Pipeline.ipp index b59782d..9f7b2b7 100644 --- a/render_3D/render_3D/scene/detail/Datoviz_Frame_Pipeline.ipp +++ b/render_3D/render_3D/scene/detail/Datoviz_Frame_Pipeline.ipp @@ -507,7 +507,7 @@ void Scene_Datoviz_State::quarantine( void Scene_Datoviz_State::abandon_resources() noexcept { quarantined_.store(true, std::memory_order_release); static_cast(targets_.release()); - retain_quarantined_context(render_context_); + render_context_.reset(); } /* 必须在无在途帧时调用;按 Runtime -> Scene -> Vulkan 池 -> GPU 上下文逆序释放。 */ void Scene_Datoviz_State::destroy() { diff --git a/render_3D/render_3D/scene/detail/Datoviz_Scene_Common.ipp b/render_3D/render_3D/scene/detail/Datoviz_Scene_Common.ipp index 9b02e63..13f9c59 100644 --- a/render_3D/render_3D/scene/detail/Datoviz_Scene_Common.ipp +++ b/render_3D/render_3D/scene/detail/Datoviz_Scene_Common.ipp @@ -23,7 +23,6 @@ #include #include #include -#include #include #include #include @@ -126,10 +125,7 @@ struct Datoviz_Render_Context final : std::enable_shared_from_this { public: [[nodiscard]] static std::shared_ptr acquire( - std::uint32_t gpu_index, bool validation_enabled) { - return std::shared_ptr( - new Datoviz_Render_Context(gpu_index, validation_enabled)); - } + std::uint32_t gpu_index, bool validation_enabled); ~Datoviz_Render_Context() { if (gpu_context_ != nullptr) dvz_gpu_ctx_destroy(gpu_context_); } @@ -151,6 +147,10 @@ public: &configuration, &Datoviz_Render_Context::submit_immediate, this); } private: + struct Registry_Entry; + struct Registry; + static Registry registry_; + struct Immediate_Submission final { explicit Immediate_Submission(VkDevice device_value) noexcept : device(device_value) {} @@ -263,20 +263,82 @@ private: if (submission_generation_.load(std::memory_order_acquire) != observed) arm_submission_drain(); } - owner gpu_context_{}; /* 当前 Scene 独占 GPU Device 与分配器。 */ + owner gpu_context_{}; /* 同 GPU 配置的 Scene 共享的进程级 Device 与分配器。 */ moodycamel::ConcurrentQueue> submissions_{}; std::atomic_uint64_t submission_generation_{}; std::atomic_bool submission_drain_active_{}; }; -void retain_quarantined_context( - std::shared_ptr& context) noexcept { - if (!context) return; - static moodycamel::ConcurrentQueue< - std::shared_ptr> quarantined; - if (quarantined.enqueue(context)) - context.reset(); - else - static_cast(new(std::nothrow) - std::shared_ptr(std::move(context))); + +/* + * Sole process authority for a Datoviz GPU configuration. Entries and their + * contexts are immutable after publication, so readers neither lock nor wait. + */ +struct Datoviz_Render_Context::Registry_Entry final { + Registry_Entry(std::uint32_t gpu_index_value, bool validation_value, + std::shared_ptr context_value) noexcept : + gpu_index(gpu_index_value), validation_enabled(validation_value), + context(std::move(context_value)) {} + + std::uint32_t gpu_index{}; + bool validation_enabled{}; + std::shared_ptr context{}; + Registry_Entry* next{}; /* Nullable, non-owning link; Registry::head owns every published entry. */ +}; + +struct Datoviz_Render_Context::Registry final { + Registry() = default; + ~Registry() { + owner entry = head.exchange( + nullptr, std::memory_order_acq_rel); + while (entry != nullptr) { + owner next = entry->next; + delete entry; + entry = next; + } + } + Registry(const Registry&) = delete; + Registry& operator=(const Registry&) = delete; + + [[nodiscard]] std::shared_ptr acquire( + std::uint32_t gpu_index, bool validation_enabled) { + const auto find = [gpu_index, validation_enabled]( + Registry_Entry* first) noexcept -> Registry_Entry* { + for (auto* entry = first; entry != nullptr; entry = entry->next) { + if (entry->gpu_index == gpu_index && + entry->validation_enabled == validation_enabled) + return entry; + } + return nullptr; + }; + + if (auto* entry = find(head.load(std::memory_order_acquire))) + return entry->context; + + auto context = std::shared_ptr( + new Datoviz_Render_Context(gpu_index, validation_enabled)); + auto candidate = std::make_unique( + gpu_index, validation_enabled, context); + for (;;) { + Registry_Entry* first = head.load(std::memory_order_acquire); + if (auto* entry = find(first)) return entry->context; + candidate->next = first; + Registry_Entry* published = candidate.get(); + if (head.compare_exchange_weak( + first, published, std::memory_order_release, + std::memory_order_acquire)) { + static_cast(candidate.release()); + return context; + } + } + } + + std::atomic> head{}; +}; + +inline Datoviz_Render_Context::Registry Datoviz_Render_Context::registry_{}; + +std::shared_ptr Datoviz_Render_Context::acquire( + std::uint32_t gpu_index, bool validation_enabled) { + return registry_.acquire(gpu_index, validation_enabled); } } // namespace aethera::render_3d::detail