6.1 KiB
6.1 KiB
tags, date created, date modified
| tags | date created | date modified |
|---|---|---|
| 星期一, 十一月 24日 2025, 11:19:42 晚上 | 星期一, 十一月 24日 2025, 11:20:43 晚上 |
2.5.4 零拷贝数据容器规范 (Zero-Copy Data Container Specification)
基线核心宗旨:“RAII for Ownership, Header for Context (RAII 管资产,Header 管上下文)”。 容器必须是 Movable-Only (仅可移动) 的,严禁拷贝。它利用 C++ 类型系统强制执行所有权的单一性,并通过标准化的 Header 确保全链路的可观测性。
1. 核心设计契约 (Design Contracts)
-
仅移动语义 (Move-Only Semantics):
DataPacket<T>的拷贝构造函数和拷贝赋值操作符必须被 显式删除 (= delete)。- 理由:防止开发者无意中触发深拷贝,破坏零拷贝原则;防止多个对象持有同一块物理内存的所有权,导致 Double Free。
-
统一元数据 (Unified Metadata):
- 所有流转的数据包,无论其负载是原始波形还是航迹,都必须携带相同的 Header 结构。
- 理由:使得中间件(如调度器、监控探针)可以在不解析 Payload 的情况下读取
TraceID和Timestamp。
-
自动回收 (Self-Reclamation):
- 容器析构时,必须自动触发 Payload 的资源释放(归还内存池或释放堆内存)。
- 理由:消除内存泄漏风险,简化模块内的错误处理逻辑(异常安全)。
2. 通用容器定义 (Generic Definition)
/**
* @brief 通用零拷贝数据容器
* @tparam PayloadType 负载类型 (必须支持移动语义)
*/
template <typename PayloadType>
struct DataPacket {
// --- 1. 标准化头部 (Fixed Header) ---
struct Header {
// 全链路追踪 ID (从 TraceContext 继承)
uint64_t trace_id;
// 数据生成时间戳 (UTC Microseconds, 总控授时)
uint64_t timestamp_us;
// 流水线序列号 (用于检测内部丢包/乱序)
uint64_t sequence_id;
// 源模块标识 (用于调试拓扑)
uint32_t source_module_id;
// 标志位 (e.g., END_OF_STREAM, CONFIG_UPDATE_BARRIER)
uint32_t flags;
} header;
// --- 2. 业务负载 (Flexible Payload) ---
PayloadType payload;
// --- 3. 构造与移动语义 ---
// 默认构造
DataPacket() = default;
// 显式移动构造
DataPacket(DataPacket&& other) noexcept
: header(other.header), payload(std::move(other.payload)) {}
// 显式移动赋值
DataPacket& operator=(DataPacket&& other) noexcept {
if (this != &other) {
header = other.header;
payload = std::move(other.payload);
}
return *this;
}
// 严禁拷贝 !!!
DataPacket(const DataPacket&) = delete;
DataPacket& operator=(const DataPacket&) = delete;
};
3. 核心特化与所有权管理 (Specializations & Ownership)
根据 2.5.1 定义的业务对象,我们需要针对两类不同性质的数据定义具体的容器行为。
3.1 原始回波数据包 (RawDataPacket)
- 场景:
DataReceiver->SignalProcessor。数据量极大(MB 级),必须使用页锁定内存池。 - Payload 类型:
std::unique_ptr<DataObject, MemoryPoolDeleter>。 - 所有权逻辑:
RawDataPacket持有unique_ptr。- 当 Packet 在队列中移动时,
unique_ptr随之移动。 - 当 Packet 在消费端(
SignalProcessor)析构时,MemoryPoolDeleter被调用,将底层的void*归还给PinnedMemoryPool。
// 定义特定的删除器
struct MemoryPoolDeleter {
IMemoryPool* pool;
void operator()(void* ptr) const {
if (pool && ptr) pool->release(ptr);
}
};
// 别名定义
using RawPayload = std::unique_ptr<DataObject, MemoryPoolDeleter>;
using RawDataPacket = DataPacket<RawPayload>;
3.2 结果数据包 (DetectionPacket / TrackPacket)
- 场景:
SignalProcessor->DataProcessor->DisplayController。数据量较小(KB 级),使用堆内存或对齐分配器。 - Payload 类型:
std::vector<DetectionResult>(Aligned)。 - 所有权逻辑:
std::vector本身就是 RAII 容器。DataPacket的移动会自动触发vector的移动(仅交换内部指针begin/end/capacity),成本极低(3 个指针大小)。- 注意:此处不需要自定义删除器,除非我们想引入“对象池”来复用 vector 的内存(对于 100Hz 的频率,
std::vector的默认分配器性能通常可以接受,暂不引入对象池以降低复杂度)。
// 别名定义
// 使用 2.5.1 定义的对齐容器
using DetectionPayload = AlignedVector<DetectionResult>;
using DetectionPacket = DataPacket<DetectionPayload>;
using TrackPayload = AlignedVector<TrackData>;
using TrackPacket = DataPacket<TrackPayload>;
4. 辅助工厂方法 (Factory Helpers)
为了简化 Header 的填充(防止开发者忘记填 TraceID),应提供工厂函数。
template <typename T>
DataPacket<T> MakePacket(T&& payload, uint64_t seq_id) {
DataPacket<T> packet;
// 自动捕获当前上下文
packet.header.trace_id = TraceContext::getCurrentId();
packet.header.timestamp_us = Clock::nowInUs();
packet.header.sequence_id = seq_id;
packet.header.source_module_id = ModuleContext::getCurrentModuleId();
packet.header.flags = 0;
// 移动负载
packet.payload = std::move(payload);
return packet;
}
总结
2.5.4 章节基线 已确立为:
- Generic Envelope:统一的
DataPacket<T>模板,强制包含 Header。 - Move-Only:通过删除拷贝构造函数,物理上杜绝深拷贝。
- Hybrid Payload Strategy:
- 大内存(Raw):
unique_ptr+CustomDeleter(Pool)。 - 小内存(Result):
std::vector(Move Semantics)。
- 大内存(Raw):
这一设计确保了数据在“集装箱”里流转时,既安全(不会泄露),又轻快(只有指针在动)。