#pragma once /** * @file performance_tuning.h * @brief 性能调优 — 任务图并行调度、工作窃取、内存池、缓存优化、数据布局 * * 提供计算密集型任务的系统级性能优化工具: * - parallel_task_graph — 基于任务依赖图的并行调度 * - work_stealing_scheduler — 工作窃取线程池 * - memory_pool_integration — 全局内存池(减少 malloc 开销) * - cache_optimization_hints — 缓存对齐/预取提示 * - profile_guided_layout — 基于性能分析的数据布局优化 * * @ingroup core */ #include "vde/core/point.h" #include "vde/core/aabb.h" #include #include #include #include #include #include #include #include #include #include #include #include namespace vde::core { // ═══════════════════════════════════════════════════════════════════════════ // 任务图并行调度 // ═══════════════════════════════════════════════════════════════════════════ /// 单个任务节点 struct TaskNode { int id = 0; ///< 任务 ID std::string name; ///< 任务名称 std::function func; ///< 任务执行函数 std::vector dependencies; ///< 依赖任务 ID 列表 int priority = 0; ///< 优先级(越大越优先) }; /// 并行任务图 — 基于拓扑排序 + 线程池的 DAG 调度器 /// /// 使用示例: /// @code /// ParallelTaskGraph graph(4); // 4 线程 /// graph.add_task({0, "read", []{ read_data(); }, {}}); /// graph.add_task({1, "proc1", []{ proc1(); }, {0}}); // 依赖任务 0 /// graph.add_task({2, "proc2", []{ proc2(); }, {0}}); // 依赖任务 0 /// graph.add_task({3, "merge", []{ merge(); }, {1,2}}); // 依赖 1,2 /// graph.execute(); /// @endcode class ParallelTaskGraph { public: /// 构造函数 /// @param num_threads 线程数(0 = 硬件并发数) explicit ParallelTaskGraph(int num_threads = 0); ~ParallelTaskGraph(); // 禁止拷贝 ParallelTaskGraph(const ParallelTaskGraph&) = delete; ParallelTaskGraph& operator=(const ParallelTaskGraph&) = delete; /// 添加任务节点 void add_task(const TaskNode& task); /// 批量添加任务 void add_tasks(const std::vector& tasks); /// 执行所有任务(拓扑排序 + 并行调度) /// 如果存在循环依赖,抛出 std::runtime_error void execute(); /// 重置所有任务(可复用) void reset(); /// 获取任务数 [[nodiscard]] size_t task_count() const { return tasks_.size(); } /// 获取线程数 [[nodiscard]] int thread_count() const { return num_threads_; } private: int num_threads_; std::vector tasks_; struct Impl; std::unique_ptr impl_; }; /// 便捷函数:直接对任务列表执行并行图调度 /// /// @param tasks 任务图节点列表 /// @param num_threads 线程数(0 = 硬件并发数) /// /// @ingroup core void parallel_task_graph( const std::vector& tasks, int num_threads = 0); // ═══════════════════════════════════════════════════════════════════════════ // 工作窃取调度器 // ═══════════════════════════════════════════════════════════════════════════ /// 工作窃取调度器 — 线程池从任务队列中窃取工作 /// /// 每个线程维护自己的任务队列,空闲线程从其他线程队列窃取任务。 /// 适用于负载不均的批处理场景(如面片处理、CAD 算法随机化)。 class WorkStealingScheduler { public: /// 构造函数 /// @param num_threads 线程数(0 = 硬件并发数) explicit WorkStealingScheduler(int num_threads = 0); ~WorkStealingScheduler(); // 禁止拷贝 WorkStealingScheduler(const WorkStealingScheduler&) = delete; WorkStealingScheduler& operator=(const WorkStealingScheduler&) = delete; /// 提交带优先级的任务 /// @param func 任务函数 /// @param priority 优先级(越大越优先,默认 0) void submit(std::function func, int priority = 0); /// 提交任务并返回 future /// @tparam F 可调用对象类型 /// @tparam Args 参数类型 /// @param f 可调用对象 /// @param args 参数 /// @return 任务的 std::future template auto submit_with_result(F&& f, Args&&... args) -> std::future; /// 等待所有任务完成 void wait_all(); /// 获取活跃线程数 [[nodiscard]] int active_threads() const; /// 获取队列中待处理任务数 [[nodiscard]] size_t pending_tasks() const; /// 停止调度器 void shutdown(); private: int num_threads_; struct Impl; std::unique_ptr impl_; }; /// 全局工作窃取调度器 /// /// @return 全局共享的 WorkStealingScheduler 实例 /// /// @ingroup core [[nodiscard]] WorkStealingScheduler& work_stealing_scheduler(); // ═══════════════════════════════════════════════════════════════════════════ // 全局内存池集成 // ═══════════════════════════════════════════════════════════════════════════ /// 内存池统计数据 struct MemoryPoolStats { size_t total_allocations = 0; ///< 总分配次数 size_t total_deallocations = 0; ///< 总释放次数 size_t current_bytes = 0; ///< 当前占用字节 size_t peak_bytes = 0; ///< 峰值占用字节 size_t cache_hits = 0; ///< 缓存命中(池中直接分配) size_t cache_misses = 0; ///< 缓存未命中(需 malloc) }; /// 全局内存池集成 /// /// 单例内存池,为常见大小的 Point3D、Vector3D、AABB 等对象 /// 提供预分配池,减少频繁 malloc/free 的开销。 /// /// 使用方式: /// @code /// auto& pool = memory_pool_integration(); /// auto* pt = pool.allocate_point3d(); /// // ... 使用 pt ... /// pool.deallocate_point3d(pt); /// @endcode class MemoryPoolIntegration { public: /// 获取单例 [[nodiscard]] static MemoryPoolIntegration& instance(); /// 分配一个 Point3D [[nodiscard]] Point3D* allocate_point3d(); /// 释放一个 Point3D void deallocate_point3d(Point3D* p); /// 分配一个 Vector3D [[nodiscard]] Vector3D* allocate_vector3d(); /// 释放一个 Vector3D void deallocate_vector3d(Vector3D* v); /// 分配指定大小的内存块 [[nodiscard]] void* allocate(size_t bytes); /// 释放内存块 void deallocate(void* ptr, size_t bytes); /// 获取统计信息 [[nodiscard]] MemoryPoolStats stats() const; /// 重置池(释放所有缓存) void reset(); /// 设置池大小 /// @param pool_size 每种大小的缓存数量 void set_pool_size(size_t pool_size); /// 预热池(预分配指定数量的对象) void warm_up(size_t count); private: MemoryPoolIntegration(); ~MemoryPoolIntegration(); MemoryPoolIntegration(const MemoryPoolIntegration&) = delete; MemoryPoolIntegration& operator=(const MemoryPoolIntegration&) = delete; struct Impl; std::unique_ptr impl_; }; /// 便捷函数:获取全局内存池 /// /// @return 全局 MemoryPoolIntegration 实例 /// /// @ingroup core [[nodiscard]] inline MemoryPoolIntegration& memory_pool_integration() { return MemoryPoolIntegration::instance(); } // ═══════════════════════════════════════════════════════════════════════════ // 缓存优化提示 // ═══════════════════════════════════════════════════════════════════════════ /// 缓存行大小(典型值为 64 字节) constexpr size_t CACHE_LINE_SIZE = 64; /// 将值对齐到缓存行 template constexpr size_t cache_aligned_size() { constexpr size_t s = sizeof(T); return ((s + CACHE_LINE_SIZE - 1) / CACHE_LINE_SIZE) * CACHE_LINE_SIZE; } /// 缓存对齐分配器 template struct CacheAlignedAllocator { using value_type = T; CacheAlignedAllocator() = default; template CacheAlignedAllocator(const CacheAlignedAllocator&) {} [[nodiscard]] T* allocate(std::size_t n) { void* ptr = nullptr; if (posix_memalign(&ptr, CACHE_LINE_SIZE, n * sizeof(T)) != 0) { throw std::bad_alloc(); } return static_cast(ptr); } void deallocate(T* ptr, std::size_t) { free(ptr); } }; /// 缓存优化提示 /// /// 返回当前硬件平台的优化建议: /// - 缓存行大小 /// - 预取距离(以缓存行为单位的步进距离) /// - NUMA 节点信息(如果可用) /// struct CacheOptimizationHints { size_t l1_cache_size = 32 * 1024; ///< L1 数据缓存大小 (bytes) size_t l2_cache_size = 256 * 1024; ///< L2 缓存大小 (bytes) size_t l3_cache_size = 8 * 1024 * 1024; ///< L3 缓存大小 (bytes) size_t cache_line_size = 64; ///< 缓存行大小 (bytes) int numa_node_count = 1; ///< NUMA 节点数 bool hyperthreading = true; ///< 是否超线程 }; /// 获取缓存优化提示 /// /// @return 当前硬件平台的缓存优化提示 /// /// @ingroup core [[nodiscard]] CacheOptimizationHints cache_optimization_hints(); /// 预取内存地址到缓存(编译器提示) /// /// @param addr 要预取的内存地址 /// /// @ingroup core inline void prefetch(const void* addr) { __builtin_prefetch(addr, 0, 3); } /// 预取写入(使缓存行进入修改状态) /// /// @param addr 要预取的内存地址 /// /// @ingroup core inline void prefetch_write(const void* addr) { __builtin_prefetch(addr, 1, 3); } /// 防止假共享的填充字段 /// /// 用法:将共享原子变量放在填充结构体中 /// @code /// struct alignas(64) PaddedCounter { /// std::atomic value{0}; /// }; /// @endcode template struct PaddedAtomic { std::atomic value{0}; char padding[Alignment - sizeof(std::atomic)]{}; }; static_assert(sizeof(PaddedAtomic) == CACHE_LINE_SIZE, "PaddedAtomic must be exactly one cache line"); // ═══════════════════════════════════════════════════════════════════════════ // 数据布局优化 (Profile-Guided Layout) // ═══════════════════════════════════════════════════════════════════════════ /// 访问频率记录 struct AccessRecord { std::string field_name; ///< 字段名 size_t access_count = 0; ///< 访问次数 size_t cache_misses = 0; ///< 缓存未命中次数 double hotness = 0.0; ///< 热度(访问/总访问) }; /// SoA (Structure of Arrays) 布局变换方案 /// /// 将 AoS 布局(结构体数组)转换为 SoA 布局(数组结构体)以改善缓存利用率。 /// 适用场景:遍历大量 Point3D/Vector3D 进行坐标变换、碰撞检测等。 struct SoALayoutPlan { std::vector hot_fields; ///< 热字段列表(应放在前面) std::vector cold_fields; ///< 冷字段列表(可放在后面) size_t stride_bytes = 0; ///< 行跨距 (bytes) double estimated_improvement = 0.0; ///< 预估性能提升比例 }; /// 基于性能分析的数据布局优化 /// /// 分析给定类型的访问模式,生成 SoA 布局变换方案。 /// 热字段(频繁访问)放在一起以提高缓存命中率, /// 冷字段(偶尔访问)分离以减少缓存污染。 /// /// @param access_records 各字段的访问记录 /// @param type_name 类型名称 /// @return SoA 布局变换方案 /// /// @ingroup core [[nodiscard]] SoALayoutPlan profile_guided_layout( const std::vector& access_records, const std::string& type_name = ""); /// Point3D 的 SoA 布局 struct Point3DSoA { std::vector x; ///< X 坐标数组 std::vector y; ///< Y 坐标数组 std::vector z; ///< Z 坐标数组 /// 从 AoS 转换为 SoA static Point3DSoA from_aos(const std::vector& points); /// 从 SoA 转换为 AoS std::vector to_aos() const; /// 点数 [[nodiscard]] size_t size() const { return x.size(); } /// 清空 void clear() { x.clear(); y.clear(); z.clear(); } }; // ═══════════════════════════════════════════════════════════════════════════ // NUMA 感知分配 // ═══════════════════════════════════════════════════════════════════════════ /// NUMA 节点信息 struct NumaInfo { int node_count = 1; ///< NUMA 节点数 int current_node = 0; ///< 当前线程所在节点 std::vector cpu_count; ///< 每节点 CPU 数 std::vector memory_mb; ///< 每节点内存 MB /// 是否运行在 NUMA 架构上 [[nodiscard]] bool is_numa() const { return node_count > 1; } }; /// 检测 NUMA 拓扑信息 [[nodiscard]] NumaInfo detect_numa_topology(); /// NUMA 感知分配器 /// /// 在指定 NUMA 节点上分配内存,避免跨节点访问延迟。 /// 如果系统不支持 NUMA(单节点),回退到普通 malloc。 /// /// @tparam T 元素类型 template struct NumaAllocator { using value_type = T; int node_id; ///< 目标 NUMA 节点 (-1 = 当前节点) explicit NumaAllocator(int node = -1) : node_id(node) {} template NumaAllocator(const NumaAllocator& other) : node_id(other.node_id) {} [[nodiscard]] T* allocate(std::size_t n) { size_t bytes = n * sizeof(T); void* ptr = numa_allocate(bytes, node_id); if (!ptr) throw std::bad_alloc(); return static_cast(ptr); } void deallocate(T* ptr, std::size_t /*n*/) { numa_deallocate(ptr); } /// 在指定 NUMA 节点上分配内存 static void* numa_allocate(size_t bytes, int node); /// 释放 NUMA 内存 static void numa_deallocate(void* ptr); /// 将已分配内存绑定到指定 NUMA 节点 static void bind_to_node(void* ptr, size_t bytes, int node); /// 将当前线程绑定到指定 NUMA 节点 static bool pin_thread_to_node(int node); }; template bool operator==(const NumaAllocator& a, const NumaAllocator& b) { return a.node_id == b.node_id; } template bool operator!=(const NumaAllocator& a, const NumaAllocator& b) { return !(a == b); } /// NUMA 感知 STL vector(自动在本地节点分配) template using NumaVector = std::vector>; // ═══════════════════════════════════════════════════════════════════════════ // 缓存行对齐工具 // ═══════════════════════════════════════════════════════════════════════════ /// 缓存行对齐宏 #define VDE_CACHE_ALIGN alignas(64) /// 缓存行补齐(确保对象独占缓存行,防止假共享) /// /// 使用示例: /// @code /// struct alignas(64) ThreadLocalData { /// int counter; /// }; /// // 或 /// VDE_CACHE_ALIGN int shared_counter; /// @endcode /// 缓存行大小模板常量 template constexpr size_t cache_line_count(size_t elements_per_line = 1) { return CACHE_LINE_SIZE / sizeof(T) * elements_per_line; } /// 数据结构缓存对齐包装 /// /// 将任意类型 T 对齐到缓存行边界并填充至完整缓存行,彻底消除假共享。 template struct alignas(CACHE_LINE_SIZE) CacheLineAligned { T data; CacheLineAligned() = default; explicit CacheLineAligned(const T& d) : data(d) {} explicit CacheLineAligned(T&& d) : data(std::move(d)) {} T* operator->() { return &data; } const T* operator->() const { return &data; } T& operator*() { return data; } const T& operator*() const { return data; } // 填充至完整缓存行 private: char padding_[CACHE_LINE_SIZE - sizeof(T)] = {}; }; /// 缓存行对齐的可变数组 /// /// 分配对齐到缓存行的动态数组。使用场景:多线程分别访问不重叠的 /// 缓存行(例如每个线程写一个计数器,不产生假共享)。 template class CacheLineArray { public: explicit CacheLineArray(size_t count) : count_(count) { // posix_memalign 分配 void* p = nullptr; if (posix_memalign(&p, CACHE_LINE_SIZE, count * sizeof(T)) != 0) { throw std::bad_alloc(); } data_ = static_cast(p); // placement new for (size_t i = 0; i < count; ++i) { new (&data_[i]) T(); } } ~CacheLineArray() { for (size_t i = 0; i < count_; ++i) { data_[i].~T(); } free(data_); } CacheLineArray(const CacheLineArray&) = delete; CacheLineArray& operator=(const CacheLineArray&) = delete; T& operator[](size_t i) { return data_[i]; } const T& operator[](size_t i) const { return data_[i]; } [[nodiscard]] size_t size() const { return count_; } [[nodiscard]] T* data() { return data_; } [[nodiscard]] const T* data() const { return data_; } private: T* data_; size_t count_; }; // ═══════════════════════════════════════════════════════════════════════════ // 预取提示(增强版) // ═══════════════════════════════════════════════════════════════════════════ /// 预取距离辅助 enum class PrefetchDistance { NEAR = 1, ///< L1 近距 (1 cache line) MID = 8, ///< L2 中距 (8 cache lines) FAR = 16, ///< L3 远距 (16 cache lines) }; /// 预取数组元素(带步进) template inline void prefetch_array(const void* base, size_t index) { const char* ptr = static_cast(base) + index * Stride * CACHE_LINE_SIZE; __builtin_prefetch(ptr, 0, static_cast(Dist) > 1 ? 2 : 3); } /// 预取下一个元素(循环优化) /// @code /// for (size_t i = 0; i < n; ++i) { /// prefetch_next(&data[i+8]); // 提前 8 步预取 /// process(data[i]); /// } /// @endcode template inline void prefetch_next(const void* addr) { __builtin_prefetch(addr, 0, 3); } /// 预取写入(用于 scatter 操作) template inline void prefetch_write_next(void* addr) { __builtin_prefetch(addr, 1, 3); } /// 顺序预取批量数据 /// /// @param base 数组基址 /// @param count 元素数 /// @param stride 元素步长 (bytes) /// @param distance 预取距离(元素数) inline void prefetch_range(const void* base, size_t count, size_t stride, size_t distance = 8) { const char* ptr = static_cast(base); size_t limit = std::min(count, count); for (size_t i = 0; i < limit; i += distance) { __builtin_prefetch(ptr + i * stride, 0, 3); } } // ═══════════════════════════════════════════════════════════════════════════ // hot/cold 数据分离 // ═══════════════════════════════════════════════════════════════════════════ /// hot 数据标记(频繁访问,应保持在一级缓存) #define VDE_HOT_DATA [[gnu::hot]] /// cold 数据标记(极少访问,不应污染缓存) #define VDE_COLD_DATA [[gnu::cold]] /// hot 函数标记(频繁调用路径) #define VDE_HOT_FUNC __attribute__((hot)) /// cold 函数标记(错误处理、日志等冷路径) #define VDE_COLD_FUNC __attribute__((cold)) /// 将热数据和冷数据分离存储的结构体模式 /// /// 使用示例:将 Point3D 的热字段 (xyz) 和冷字段 (id, metadata) 分离: /// @code /// struct HotPoint { /// VDE_CACHE_ALIGN double x, y, z; // 热:每次遍历都访问 /// }; /// struct ColdPoint { /// int id; /// std::string metadata; // 冷:偶尔访问 /// }; /// // SoA: std::vector hot, std::vector cold; /// @endcode /// 冷数据引用包装器 /// /// 将大型/不常访问的数据移出热路径,通过指针间接访问。 template class ColdStorage { public: ColdStorage() : ptr_(std::make_unique()) {} explicit ColdStorage(T&& val) : ptr_(std::make_unique(std::move(val))) {} explicit ColdStorage(const T& val) : ptr_(std::make_unique(val)) {} T& get() { return *ptr_; } const T& get() const { return *ptr_; } T* operator->() { return ptr_.get(); } const T* operator->() const { return ptr_.get(); } private: std::unique_ptr ptr_; }; /// 双缓冲数据布局(AoS ↔ SoA 转换缓存) /// /// 同时维护 AoS 和 SoA 两种布局,根据访问模式自动选择。 /// 写操作更新两种布局,读操作直接从 SoA 读取(SIMD 友好)。 template class HybridLayout { public: /// 更新 AoS 数据(自动同步到 SoA) void set_aos(const std::vector& data); /// 获取 SoA 数据(用于 SIMD 处理) [[nodiscard]] const Point3DSoA& get_soa() const { return soa_; } /// 获取 AoS 数据 [[nodiscard]] const std::vector& get_aos() const { return aos_; } /// 标记 SoA 为脏 void invalidate_soa() { soa_dirty_ = true; } /// 强制重新生成 SoA void rebuild_soa(); private: std::vector aos_; Point3DSoA soa_; bool soa_dirty_ = true; }; } // namespace vde::core