7.8 KiB
7.8 KiB
tags, date created, date modified
| tags | date created | date modified |
|---|---|---|
| 星期一, 十一月 24日 2025, 5:32:36 下午 | 星期一, 十一月 24日 2025, 10:39:00 晚上 |
2.5.1 内部高性能业务对象模型 (Internal High-Performance Business Object Model)
基线核心宗旨:“Hardware-First (硬件优先)”。
内部数据对象的设计不再是为了“方便编程”,而是为了适配 CPU 缓存行 (Cache Line)、迎合 SIMD 指令集 (AVX/NEON) 以及 最小化内存带宽消耗。
1. 核心设计契约 (Design Contracts)
在定义具体结构体之前,必须确立以下不可逾越的“红线”:
-
严格 POD (Plain Old Data):
- 所有业务对象必须是 Trivial 和 Standard-Layout 的。
- 禁止:虚函数 (
virtual)、智能指针成员 (std::shared_ptr)、堆内存容器成员 (std::vector,std::string)。 - 理由:确保对象可以被
memcpy直接拷贝(虽然我们用零拷贝,但在某些边界仍需落盘或调试),且在内存中是连续的,对 CPU 预取器(Prefetcher)友好。
-
强制对齐 (Forced Alignment):
- 利用 C++11
alignas关键字,强制结构体大小为 16 字节 (128-bit) 或 32 字节 (256-bit) 的倍数。 - 理由:适配 ARM NEON (128-bit) 和 x86 AVX2 (256-bit) 寄存器宽度,允许编译器生成向量化加载/存储指令 (
vld1q,vmovaps),而非逐个标量读写。
- 利用 C++11
2. 业务对象定义基线
2.1 点迹对象:DetectionResult
这是信号处理流水线产出的最高频数据(每秒可能数万个),是 CPU(数据处理模块)的数据密集型计算对象。
C++ 结构体定义 (基线):
/**
* @brief 单个检测点迹 (Plot/Detection)
* @note 强制 16 字节对齐,适配 SSE/NEON 128位寄存器
* @size 48 Bytes (3个 128-bit 块)
*/
struct alignas(16) DetectionResult {
// Block 0: 空间坐标 (16 Bytes) -> 可以直接加载到一个向量寄存器
float range; // 距离 (m)
float azimuth; // 方位角 (rad)
float elevation; // 俯仰角 (rad)
float velocity; // 径向速度 (m/s)
// Block 1: 信号质量与辅助信息 (16 Bytes)
float snr; // 信噪比 (dB)
float power; // 信号功率
float noise; // 噪声底
uint32_t channel_id;// 通道/波束ID (用于区分多源)
// Block 2: 索引与标识 (16 Bytes)
uint32_t range_idx; // 距离门索引
uint32_t doppler_idx; // 多普勒索引
uint32_t batch_id; // 所属的 CPI 批次 ID
uint32_t padding; // [显式填充] 保持 16B 对齐
};
设计决策辩护:
- Float vs Double:选用
float(32-bit)。对于雷达点迹处理,单精度浮点数精度通常已足够,且相比double能让 SIMD 吞吐量翻倍(一个 AVX2 寄存器处理 8 个 float vs 4 个 double)。 - Block 0 布局:将
r, azi, ele, v紧凑排列,使得坐标转换算法(极坐标转笛卡尔坐标)可以一次加载Block 0并执行向量运算。 - Explicit Padding:末尾增加
padding字段,不仅为了凑齐 48 字节,更是为了防止隐式填充导致的内存脏数据风险。
2.2 航迹对象:TrackData
这是数据处理模块的核心状态对象,用于卡尔曼滤波等矩阵运算。此处的改动最大:我们废弃了旧文档中 std::vector 的设计,改为定长数组,以符合 POD 约束。
C++ 结构体定义 (基线):
/**
* @brief 单个航迹状态 (Track)
* @note 强制 32 字节对齐,适配 AVX2 256位寄存器
* @note 假设使用 6维状态向量 (x, y, z, vx, vy, vz)
*/
struct alignas(32) TrackData {
// --- Header (32 Bytes) ---
uint64_t track_id; // 全局唯一航迹 ID
uint64_t timestamp_us; // 更新时间戳
uint32_t status; // 状态枚举 (Tentative/Confirmed)
uint32_t hit_count; // 关联次数
uint32_t miss_count; // 丢失次数
uint32_t station_id; // 源站 ID (分布式新增)
// --- State Vector (32 Bytes) ---
// 6维状态向量,最后补 2 个 float padding 凑齐 8 个 float (256-bit)
// 便于 AVX2 一次加载整个状态向量
float state[8]; // [x, y, z, vx, vy, vz, pad, pad]
// --- Covariance Matrix (Diagonal/Simplified) ---
// 这是一个设计折中。完整的 6x6 协方差矩阵需要 36*4 = 144 Bytes。
// 为了保持对象轻量,这里仅存储对角线元素 (方差),
// 或者如果内存允许,展开存储下三角矩阵。
// 此处演示存储完整对角线 + 填充 (32 Bytes)
float covariance_diag[8]; // [var_x, var_y, var_z, …, pad, pad]
// --- Quality & Classification (32 Bytes) ---
float prob_target; // 目标存在概率
float maneuver_indicator;// 机动指示器
uint32_t type_label; // 分类标签
uint32_t _reserved[5]; // 预留空间,保持缓存行对齐
};
设计决策辩护:
- 定长数组 (
float state[8]):替代std::vector。- 收益:内存绝对连续。卡尔曼滤波预测步骤中,CPU 预取器可以直接将
state和covariance拉入 L1 缓存,消除指针跳转(Pointer Chasing)开销。 - 对齐技巧:6 维向量补齐到 8 维,完美契合 AVX2 (256-bit = 8 floats)。这意味着向量加减法只需一条指令。
- 收益:内存绝对连续。卡尔曼滤波预测步骤中,CPU 预取器可以直接将
- 分块对齐:每个数据块(Header, State, Covariance)都对齐到 32 字节。这确保了字段不会跨越缓存行(Cache Line Split),消除了跨行访问的惩罚。
3. 容器与内存管理策略
定义了“原子”结构体后,还需要定义装载它们的“容器”。
3.1 容器选型:std::vector + 对齐分配器
在 DataPacket<T> 的 Payload 中,我们不存储单个对象,而是存储对象的数组。
// 定义专属分配器,确保 vector 的底层内存首地址也是 32 字节对齐的
template <typename T>
using AlignedVector = std::vector<T, boost::alignment::aligned_allocator<T, 32>>;
// 最终在 DataPacket 中的 Payload 类型
using DetectionList = AlignedVector<DetectionResult>;
using TrackList = AlignedVector<TrackData>;
-
为什么需要 Aligned Allocator?
普通的 std::vector 只保证元素大小对齐,不保证数组首地址对齐(尽管现代 glibc 通常对齐到 16B)。为了让第一条 AVX 指令(vmovaps)安全执行,首地址必须严格对齐。
3.2 内存布局图示 (AoS 布局)
在内存中,一批点迹的物理视图如下。这种 Array of Structures (AoS) 布局对于“逐个点迹处理”的逻辑(如卡尔曼滤波的 Update 步骤)是非常缓存友好的。
| <--- Cache Line 64B ---> | <--- Cache Line 64B ---> |
[ Det 0 (48B) ] [ Det 1 (48B) ] [ Det 2 (48B) ] [ … ]
^ ^ ^
Align(16) Align(16) Align(16)
- 注意:由于 48B 不是 64B 的约数,
Det 1会跨越缓存行边界。 - 优化选项 (SoA):如果在某些极端性能场景下(如纯粹的坐标转换),我们可以考虑 Structure of Arrays (SoA),即
vector<float> range_list, azimuth_list…。但考虑到代码复杂度和卡尔曼滤波的逻辑特性(通常需要同时访问一个点的所有属性),AoS (上述结构体) 是当前工程最佳平衡点。
总结
2.5.1 章节基线 已确立为:
- Strict POD:摒弃
std::vector成员,全面拥抱定长数组。 - SIMD Alignment:
DetectionResult16B 对齐,TrackData32B 对齐。 - Explicit Padding:显式填充所有空隙,消除未定义行为。
- Aligned Container:使用带对齐分配器的 Vector 承载数据。
这套模型直接服务于 DataProcessor 的 CPU 计算效率,确保数据进入算法时是“最可口”的形态。