2026-07-26 22:54:46 +08:00
|
|
|
|
#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 <vector>
|
|
|
|
|
|
#include <string>
|
|
|
|
|
|
#include <memory>
|
|
|
|
|
|
#include <functional>
|
|
|
|
|
|
#include <future>
|
|
|
|
|
|
#include <atomic>
|
|
|
|
|
|
#include <mutex>
|
|
|
|
|
|
#include <thread>
|
|
|
|
|
|
#include <unordered_map>
|
|
|
|
|
|
#include <type_traits>
|
|
|
|
|
|
#include <cstddef>
|
2026-07-26 23:13:22 +08:00
|
|
|
|
#include <cstdlib>
|
2026-07-26 22:54:46 +08:00
|
|
|
|
|
|
|
|
|
|
namespace vde::core {
|
|
|
|
|
|
|
|
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
// 任务图并行调度
|
|
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
|
|
/// 单个任务节点
|
|
|
|
|
|
struct TaskNode {
|
|
|
|
|
|
int id = 0; ///< 任务 ID
|
|
|
|
|
|
std::string name; ///< 任务名称
|
|
|
|
|
|
std::function<void()> func; ///< 任务执行函数
|
|
|
|
|
|
std::vector<int> 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<TaskNode>& 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<TaskNode> tasks_;
|
|
|
|
|
|
struct Impl;
|
|
|
|
|
|
std::unique_ptr<Impl> impl_;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/// 便捷函数:直接对任务列表执行并行图调度
|
|
|
|
|
|
///
|
|
|
|
|
|
/// @param tasks 任务图节点列表
|
|
|
|
|
|
/// @param num_threads 线程数(0 = 硬件并发数)
|
|
|
|
|
|
///
|
|
|
|
|
|
/// @ingroup core
|
|
|
|
|
|
void parallel_task_graph(
|
|
|
|
|
|
const std::vector<TaskNode>& 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<void()> func, int priority = 0);
|
|
|
|
|
|
|
|
|
|
|
|
/// 提交任务并返回 future
|
|
|
|
|
|
/// @tparam F 可调用对象类型
|
|
|
|
|
|
/// @tparam Args 参数类型
|
|
|
|
|
|
/// @param f 可调用对象
|
|
|
|
|
|
/// @param args 参数
|
|
|
|
|
|
/// @return 任务的 std::future
|
|
|
|
|
|
template<typename F, typename... Args>
|
|
|
|
|
|
auto submit_with_result(F&& f, Args&&... args)
|
|
|
|
|
|
-> std::future<decltype(f(args...))>;
|
|
|
|
|
|
|
|
|
|
|
|
/// 等待所有任务完成
|
|
|
|
|
|
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> 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> impl_;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/// 便捷函数:获取全局内存池
|
|
|
|
|
|
///
|
|
|
|
|
|
/// @return 全局 MemoryPoolIntegration 实例
|
|
|
|
|
|
///
|
|
|
|
|
|
/// @ingroup core
|
|
|
|
|
|
[[nodiscard]] inline MemoryPoolIntegration& memory_pool_integration() {
|
|
|
|
|
|
return MemoryPoolIntegration::instance();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
// 缓存优化提示
|
|
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
|
|
/// 缓存行大小(典型值为 64 字节)
|
|
|
|
|
|
constexpr size_t CACHE_LINE_SIZE = 64;
|
|
|
|
|
|
|
|
|
|
|
|
/// 将值对齐到缓存行
|
|
|
|
|
|
template<typename T>
|
|
|
|
|
|
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<typename T>
|
|
|
|
|
|
struct CacheAlignedAllocator {
|
|
|
|
|
|
using value_type = T;
|
|
|
|
|
|
|
|
|
|
|
|
CacheAlignedAllocator() = default;
|
|
|
|
|
|
template<typename U>
|
|
|
|
|
|
CacheAlignedAllocator(const CacheAlignedAllocator<U>&) {}
|
|
|
|
|
|
|
|
|
|
|
|
[[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<T*>(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<int> value{0};
|
|
|
|
|
|
/// };
|
|
|
|
|
|
/// @endcode
|
|
|
|
|
|
template<size_t Alignment = CACHE_LINE_SIZE>
|
|
|
|
|
|
struct PaddedAtomic {
|
|
|
|
|
|
std::atomic<int> value{0};
|
|
|
|
|
|
char padding[Alignment - sizeof(std::atomic<int>)]{};
|
|
|
|
|
|
};
|
|
|
|
|
|
static_assert(sizeof(PaddedAtomic<CACHE_LINE_SIZE>) == 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<std::string> hot_fields; ///< 热字段列表(应放在前面)
|
|
|
|
|
|
std::vector<std::string> 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<AccessRecord>& access_records,
|
|
|
|
|
|
const std::string& type_name = "");
|
|
|
|
|
|
|
|
|
|
|
|
/// Point3D 的 SoA 布局
|
|
|
|
|
|
struct Point3DSoA {
|
|
|
|
|
|
std::vector<double> x; ///< X 坐标数组
|
|
|
|
|
|
std::vector<double> y; ///< Y 坐标数组
|
|
|
|
|
|
std::vector<double> z; ///< Z 坐标数组
|
|
|
|
|
|
|
|
|
|
|
|
/// 从 AoS 转换为 SoA
|
|
|
|
|
|
static Point3DSoA from_aos(const std::vector<Point3D>& points);
|
|
|
|
|
|
|
|
|
|
|
|
/// 从 SoA 转换为 AoS
|
|
|
|
|
|
std::vector<Point3D> to_aos() const;
|
|
|
|
|
|
|
|
|
|
|
|
/// 点数
|
|
|
|
|
|
[[nodiscard]] size_t size() const { return x.size(); }
|
|
|
|
|
|
|
|
|
|
|
|
/// 清空
|
|
|
|
|
|
void clear() { x.clear(); y.clear(); z.clear(); }
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-07-26 23:13:22 +08:00
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
// NUMA 感知分配
|
|
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
|
|
/// NUMA 节点信息
|
|
|
|
|
|
struct NumaInfo {
|
|
|
|
|
|
int node_count = 1; ///< NUMA 节点数
|
|
|
|
|
|
int current_node = 0; ///< 当前线程所在节点
|
|
|
|
|
|
std::vector<int> cpu_count; ///< 每节点 CPU 数
|
|
|
|
|
|
std::vector<size_t> 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<typename T>
|
|
|
|
|
|
struct NumaAllocator {
|
|
|
|
|
|
using value_type = T;
|
|
|
|
|
|
int node_id; ///< 目标 NUMA 节点 (-1 = 当前节点)
|
|
|
|
|
|
|
|
|
|
|
|
explicit NumaAllocator(int node = -1) : node_id(node) {}
|
|
|
|
|
|
|
|
|
|
|
|
template<typename U>
|
|
|
|
|
|
NumaAllocator(const NumaAllocator<U>& 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<T*>(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<typename T, typename U>
|
|
|
|
|
|
bool operator==(const NumaAllocator<T>& a, const NumaAllocator<U>& b) {
|
|
|
|
|
|
return a.node_id == b.node_id;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template<typename T, typename U>
|
|
|
|
|
|
bool operator!=(const NumaAllocator<T>& a, const NumaAllocator<U>& b) {
|
|
|
|
|
|
return !(a == b);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// NUMA 感知 STL vector(自动在本地节点分配)
|
|
|
|
|
|
template<typename T>
|
|
|
|
|
|
using NumaVector = std::vector<T, NumaAllocator<T>>;
|
|
|
|
|
|
|
|
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
// 缓存行对齐工具
|
|
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
|
|
/// 缓存行对齐宏
|
|
|
|
|
|
#define VDE_CACHE_ALIGN alignas(64)
|
|
|
|
|
|
|
|
|
|
|
|
/// 缓存行补齐(确保对象独占缓存行,防止假共享)
|
|
|
|
|
|
///
|
|
|
|
|
|
/// 使用示例:
|
|
|
|
|
|
/// @code
|
|
|
|
|
|
/// struct alignas(64) ThreadLocalData {
|
|
|
|
|
|
/// int counter;
|
|
|
|
|
|
/// };
|
|
|
|
|
|
/// // 或
|
|
|
|
|
|
/// VDE_CACHE_ALIGN int shared_counter;
|
|
|
|
|
|
/// @endcode
|
|
|
|
|
|
|
|
|
|
|
|
/// 缓存行大小模板常量
|
|
|
|
|
|
template<typename T>
|
|
|
|
|
|
constexpr size_t cache_line_count(size_t elements_per_line = 1) {
|
|
|
|
|
|
return CACHE_LINE_SIZE / sizeof(T) * elements_per_line;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// 数据结构缓存对齐包装
|
|
|
|
|
|
///
|
|
|
|
|
|
/// 将任意类型 T 对齐到缓存行边界并填充至完整缓存行,彻底消除假共享。
|
|
|
|
|
|
template<typename T>
|
|
|
|
|
|
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<typename T>
|
|
|
|
|
|
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<T*>(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<size_t Stride = 1, PrefetchDistance Dist = PrefetchDistance::NEAR>
|
|
|
|
|
|
inline void prefetch_array(const void* base, size_t index) {
|
|
|
|
|
|
const char* ptr = static_cast<const char*>(base)
|
|
|
|
|
|
+ index * Stride * CACHE_LINE_SIZE;
|
|
|
|
|
|
__builtin_prefetch(ptr, 0, static_cast<int>(Dist) > 1 ? 2 : 3);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// 预取下一个元素(循环优化)
|
|
|
|
|
|
/// @code
|
|
|
|
|
|
/// for (size_t i = 0; i < n; ++i) {
|
|
|
|
|
|
/// prefetch_next(&data[i+8]); // 提前 8 步预取
|
|
|
|
|
|
/// process(data[i]);
|
|
|
|
|
|
/// }
|
|
|
|
|
|
/// @endcode
|
|
|
|
|
|
template<size_t Lookahead = 4>
|
|
|
|
|
|
inline void prefetch_next(const void* addr) {
|
|
|
|
|
|
__builtin_prefetch(addr, 0, 3);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// 预取写入(用于 scatter 操作)
|
|
|
|
|
|
template<size_t Lookahead = 4>
|
|
|
|
|
|
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<const char*>(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<HotPoint> hot, std::vector<ColdPoint> cold;
|
|
|
|
|
|
/// @endcode
|
|
|
|
|
|
|
|
|
|
|
|
/// 冷数据引用包装器
|
|
|
|
|
|
///
|
|
|
|
|
|
/// 将大型/不常访问的数据移出热路径,通过指针间接访问。
|
|
|
|
|
|
template<typename T>
|
|
|
|
|
|
class ColdStorage {
|
|
|
|
|
|
public:
|
|
|
|
|
|
ColdStorage() : ptr_(std::make_unique<T>()) {}
|
|
|
|
|
|
explicit ColdStorage(T&& val) : ptr_(std::make_unique<T>(std::move(val))) {}
|
|
|
|
|
|
explicit ColdStorage(const T& val) : ptr_(std::make_unique<T>(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<T> ptr_;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/// 双缓冲数据布局(AoS ↔ SoA 转换缓存)
|
|
|
|
|
|
///
|
|
|
|
|
|
/// 同时维护 AoS 和 SoA 两种布局,根据访问模式自动选择。
|
|
|
|
|
|
/// 写操作更新两种布局,读操作直接从 SoA 读取(SIMD 友好)。
|
|
|
|
|
|
template<typename T>
|
|
|
|
|
|
class HybridLayout {
|
|
|
|
|
|
public:
|
|
|
|
|
|
/// 更新 AoS 数据(自动同步到 SoA)
|
|
|
|
|
|
void set_aos(const std::vector<T>& data);
|
|
|
|
|
|
|
|
|
|
|
|
/// 获取 SoA 数据(用于 SIMD 处理)
|
|
|
|
|
|
[[nodiscard]] const Point3DSoA& get_soa() const { return soa_; }
|
|
|
|
|
|
|
|
|
|
|
|
/// 获取 AoS 数据
|
|
|
|
|
|
[[nodiscard]] const std::vector<T>& get_aos() const { return aos_; }
|
|
|
|
|
|
|
|
|
|
|
|
/// 标记 SoA 为脏
|
|
|
|
|
|
void invalidate_soa() { soa_dirty_ = true; }
|
|
|
|
|
|
|
|
|
|
|
|
/// 强制重新生成 SoA
|
|
|
|
|
|
void rebuild_soa();
|
|
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
|
std::vector<T> aos_;
|
|
|
|
|
|
Point3DSoA soa_;
|
|
|
|
|
|
bool soa_dirty_ = true;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-07-26 22:54:46 +08:00
|
|
|
|
} // namespace vde::core
|