171 lines
7.8 KiB
Markdown
171 lines
7.8 KiB
Markdown
---
|
||
tags: []
|
||
date created: 星期一, 十一月 24日 2025, 5:32:36 下午
|
||
date modified: 星期一, 十一月 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)
|
||
|
||
在定义具体结构体之前,必须确立以下不可逾越的“红线”:
|
||
|
||
1. **严格 POD (Plain Old Data)**:
|
||
|
||
- 所有业务对象必须是 **Trivial** 和 **Standard-Layout** 的。
|
||
- **禁止**:虚函数 (`virtual`)、智能指针成员 (`std::shared_ptr`)、堆内存容器成员 (`std::vector`, `std::string`)。
|
||
- **理由**:确保对象可以被 `memcpy` 直接拷贝(虽然我们用零拷贝,但在某些边界仍需落盘或调试),且在内存中是**连续**的,对 CPU 预取器(Prefetcher)友好。
|
||
|
||
2. **强制对齐 (Forced Alignment)**:
|
||
|
||
- 利用 C++11 `alignas` 关键字,强制结构体大小为 **16 字节 (128-bit)** 或 **32 字节 (256-bit)** 的倍数。
|
||
- **理由**:适配 ARM NEON (128-bit) 和 x86 AVX2 (256-bit) 寄存器宽度,允许编译器生成向量化加载/存储指令 (`vld1q`, `vmovaps`),而非逐个标量读写。
|
||
|
||
---
|
||
|
||
## 2. 业务对象定义基线
|
||
|
||
### 2.1 点迹对象:`DetectionResult`
|
||
|
||
这是信号处理流水线产出的最高频数据(每秒可能数万个),是 CPU(数据处理模块)的数据密集型计算对象。
|
||
|
||
**C++ 结构体定义 (基线)**:
|
||
|
||
```cpp
|
||
/**
|
||
* @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++ 结构体定义 (基线)**:
|
||
|
||
```cpp
|
||
/**
|
||
* @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)。这意味着向量加减法只需一条指令。
|
||
- **分块对齐**:每个数据块(Header, State, Covariance)都对齐到 32 字节。这确保了字段不会跨越缓存行(Cache Line Split),消除了跨行访问的惩罚。
|
||
|
||
---
|
||
|
||
## 3. 容器与内存管理策略
|
||
|
||
定义了“原子”结构体后,还需要定义装载它们的“容器”。
|
||
|
||
### 3.1 容器选型:`std::vector` + 对齐分配器
|
||
|
||
在 `DataPacket<T>` 的 Payload 中,我们不存储单个对象,而是存储对象的数组。
|
||
|
||
```cpp
|
||
// 定义专属分配器,确保 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 步骤)是非常缓存友好的。
|
||
|
||
```plaintext
|
||
| <--- 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 章节基线** 已确立为:
|
||
|
||
1. **Strict POD**:摒弃 `std::vector` 成员,全面拥抱定长数组。
|
||
2. **SIMD Alignment**:`DetectionResult` 16B 对齐,`TrackData` 32B 对齐。
|
||
3. **Explicit Padding**:显式填充所有空隙,消除未定义行为。
|
||
4. **Aligned Container**:使用带对齐分配器的 Vector 承载数据。
|
||
|
||
这套模型直接服务于 **DataProcessor** 的 CPU 计算效率,确保数据进入算法时是“最可口”的形态。
|