创建仓库
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
---
|
||||
tags: []
|
||||
date created: 星期三, 十一月 19日 2025, 9:34:40 晚上
|
||||
date modified: 星期三, 十一月 19日 2025, 9:35:26 晚上
|
||||
---
|
||||
|
||||
# 2.1.1 物理链路层与传输媒介 (Physical Link Layer & Transport Medium)
|
||||
|
||||
**审计综述**:
|
||||
系统在数据采集链路上存在**物理硬件阻断(P0 级)**。用于雷达数据采集的 NIC 接口 (`ens4f*`) 仅为 **1GbE 级别**,而非高吞吐雷达系统所需的 10GbE。此外,核心 GPU 的 PCIe 链路也存在降级。
|
||||
|
||||
**1. 核心数据通路 (Host-to-Device/PCIe)**
|
||||
|
||||
- **关键性**:**P0 (性能)**
|
||||
- **信息解析**:
|
||||
- **GPU Link Status**:核心 GPU 链路能力为 PCIe 4.0 x16 (16GT/s Width x16)。
|
||||
- **Negotiated Status**:实际运行状态为 x8 (Width x8 (downgraded))。
|
||||
- **结论**:GPU 链路存在降级,物理带宽被限制在理论容量的 50%。这影响 Host-Device 内存传输(如 DMA 数据传输),但其降级后的带宽(x8)仍远高于网络采集链路。
|
||||
|
||||
**2. 网络数据采集链路 (Data Acquisition Link)**
|
||||
|
||||
- **关键性**:**P0 (功能阻断)**
|
||||
- **信息解析**:
|
||||
- **网卡型号**:Beijing Wangxun Technology Co., Ltd. WX1860AL4 Gigabit Ethernet Controller。
|
||||
- **物理极限**:网卡仅支持 **1000baseT/Full** (1Gb/s)。这**无法**满足高帧率、高分辨率雷达系统对 10GbE/40GbE 的带宽要求。
|
||||
- **链路状态**:目前 `ens4f1` 接口处于连接中断状态 (`Link detected: no`),且之前工作在 **100 Mb/s** 的极低速度。
|
||||
- **辅助接口**:`ens2f7u1u2` 为 USB 2.0 接口,其最大理论吞吐低于 1Gb/s,不可用于数据采集。
|
||||
- **风险总结**:
|
||||
- **硬件阻断**:系统当前无 10GbE 接口。
|
||||
- **运维风险**:当前可用的 1GbE 链路仍存在不稳定的 100Mb/s 降级风险。
|
||||
|
||||
**3. 结论与下一步**
|
||||
|
||||
- **最终判定**:**系统硬件不满足雷达数据采集的最低带宽要求。**
|
||||
- **下一步行动**:在等待硬件升级(安装 10GbE 网卡)期间,我们将继续审计软件层面,重点检查如何在当前 1GbE 的极限下,通过配置 **JUMBO Frame** 等方式,将带宽压榨至最高。
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
tags: []
|
||||
date created: 星期三, 十一月 19日 2025, 10:14:33 晚上
|
||||
date modified: 星期三, 十一月 19日 2025, 10:14:46 晚上
|
||||
---
|
||||
|
||||
### 2.1.2 数据链路层协议与封装 (Data Link Layer Protocol & Encapsulation)
|
||||
|
||||
- **概要**: 本节旨在确立雷达数据采集链路的 L2/L3 层协议与最大传输单元 (MTU) 规格。鉴于系统存在 **P0 级 1GbE 硬件带宽瓶颈**,为最大化有效数据吞吐并保障实时性,协议基线选择标准 **UDP/IP**,并强制采用 **JUMBO Frame (MTU 9000)** 技术,以实现对网络性能的 P1 级优化。
|
||||
|
||||
---
|
||||
|
||||
#### 1. 协议基线与 MTU 确立
|
||||
|
||||
| 基线元素 | 确立值 | 论证 |
|
||||
| :--- | :--- | :--- |
|
||||
| **传输协议** | UDP/IPv4 | 采用标准 UDP 协议,以满足雷达数据流对**无连接、低延迟**的传输特性要求,牺牲可靠性(由应用层序列号校验弥补)。 |
|
||||
| **MTU** | **9000 字节** (JUMBO Frame) | 旨在将**网络开销最小化**,并将 **CPU 中断频率降低 6 倍**,是当前 1GbE 链路下达成高吞吐 KPI 的关键优化手段。 |
|
||||
| **数据封装** | 定制雷达数据包头部 | 必须在 9000 字节 MTU 限制内,封装 **TraceID**、**序列号**和 **校验和** 字段。 |
|
||||
|
||||
#### 2. 技术论证:JUMBO Frame 的核心价值
|
||||
|
||||
MTU 9000 的选择并非只是带宽的简单放大,它在当前 **Kylin/Feiteng** 实时处理平台上提供了两大核心工程优势:
|
||||
|
||||
##### 2.1. 实时性保障:消除 CPU 中断风暴
|
||||
|
||||
- **问题描述**: 在 1GbE 链路满载且使用标准 MTU 1500 字节时,CPU 内核每秒需处理约 **81,000 个**数据包中断(不考虑中断聚合)。这种高频的中断会导致 CPU 资源大量消耗在**上下文切换**和**中断服务**上,严重破坏实时性。
|
||||
- **解决方案**: 将 MTU 提升至 9000 字节后,传输相同的数据量所需的中断次数降为原来的 **约 1/6**。这极大地减轻了内核压力,将 CPU 资源释放回用户态,有助于满足 **CPU 资源占用率 \< 5% (单核)** 的 KPI。
|
||||
|
||||
##### 2.2. 吞吐效率:最小化协议开销
|
||||
|
||||
- **问题描述**: 在 MTU 1500 下,每个数据包的协议头(约 42 字节)占据了约 3% 的有效带宽。
|
||||
- **解决方案**: JUMBO Frame 将协议头开销稀释至 **0.5% 以下**。这在 1GbE 这种物理瓶颈链路 上至关重要,它确保了链路能最大限度地传输**雷达净载荷**,为达到 **数据吞吐量 KPI** 提供软件保障。
|
||||
|
||||
#### 3. 实施规范与系统依赖
|
||||
|
||||
JUMBO Frame 的实现是一个**端到端**的配置基线,需要严格遵循以下规范:
|
||||
|
||||
| 实施环节 | 规范操作 | 状态 |
|
||||
| :--- | :--- | :--- |
|
||||
| **Host NIC 配置** | 必须通过 `ethtool` 或 `ip link` 命令,将采集接口的 MTU 强制设定为 9000 字节。 | 已确认 |
|
||||
| **雷达前端配置** | 雷达阵面 DPU/ADC 的发送端 MTU 必须精确匹配 9000 字节。 | 外部依赖 |
|
||||
| **内核缓冲区** | 必须修正内核参数 `net.core.rmem_max`,使其容量足以承载 **8192** 个 MTU 9000 的数据包。当前需将 `rmem_max` 提升至至少 **64MB** 以消除丢包风险 [sysctl output]。 | P1 级修正 |
|
||||
| **NIC 环形缓冲区** | RX 队列深度必须配置为硬件最大值 **8192** [ethtool output],以提供最长的瞬态延迟容忍度。 | P1 级配置 |
|
||||
@@ -0,0 +1,90 @@
|
||||
---
|
||||
tags: []
|
||||
date created: 星期四, 十一月 20日 2025, 8:40:05 晚上
|
||||
date modified: 星期四, 十一月 20日 2025, 8:48:20 晚上
|
||||
---
|
||||
|
||||
# 2.2.1 锁页内存管理与分配策略 (Page-Locked&Pinned Memory Management)
|
||||
|
||||
### 一、 约束输入与对齐 (Constraints & Alignment)
|
||||
|
||||
根据前序审计与设计文档,我们面临以下**硬性约束**:
|
||||
|
||||
1. **OS 内存机制**: Kylin V10 (Linux) 使用虚拟内存分页。普通的 `malloc/new` 分配的是**可分页内存 (Pageable Memory)**。
|
||||
2. **DMA 物理限制**: GPU 的 DMA 引擎(Copy Engine)需要访问**物理地址**。如果使用可分页内存,驱动必须先隐式锁定页面(CPU 开销),再分块传输,导致带宽严重下降。
|
||||
3. **吞吐量目标**: 雷达接收模块要求 **\> 10,000 packets/sec**。频繁的系统调用(`malloc` / `free` / `cudaMallocHost`)是不可接受的。
|
||||
4. **硬件平台**: 智铠 MR-V100 的 SDK (CoreX) 兼容 CUDA 10.2 API。
|
||||
|
||||
-----
|
||||
|
||||
### 二、 权衡分析与选项呈现 (Trade-off Matrix)
|
||||
|
||||
#### 议题 1:锁页内存申请 API (Allocation API)
|
||||
|
||||
| 选项 | A. `cudaMallocHost` (推荐) | B. `malloc` + `cudaHostRegister` |
|
||||
| :--- | :--- | :--- |
|
||||
| **机制** | 直接由 GPU 驱动在内核态分配**物理连续**(尽可能)且**已锁定**的内存。 | 用户先申请普通内存,再通知驱动去锁定这些页面。 |
|
||||
| **DMA 性能** | **最高**。驱动对物理地址布局有完全控制权,TLB 命中率高。 | **中等/高**。取决于 OS 分配的物理页碎片化程度。 |
|
||||
| **UVA 适配性** | **完美**。配合 `cudaHostAllocMapped` 标志,可直接映射到 GPU 地址空间(为 2.2.4 铺路)。 | **较差**。虽然也支持 Mapped,但对齐要求严格,容易出错。 |
|
||||
| **CPU 开销** | 分配时开销极大(重系统调用),必须配合**内存池**使用。 | 注册/注销开销大,同样需要配合内存池。 |
|
||||
|
||||
#### 议题 2:内存池架构 (Pool Architecture)
|
||||
|
||||
| 选项 | A. 预分配固定块池 (Fixed-Block Pool) (推荐) | B. 动态堆内存池 (Dynamic Heap) |
|
||||
| :--- | :--- | :--- |
|
||||
| **机制** | 启动时申请一大块内存(如 512MB),切分为 N 个固定大小(如 64KB)的块。 | 像 OS 堆一样支持任意大小的 `alloc/free`。 |
|
||||
| **适配场景** | **雷达原始数据**。脉冲/包大小通常是固定的或有明确上限。 | 通用计算,大小不一的对象。 |
|
||||
| **性能** | **O(1) 极速分配**。无内存碎片。 | O(log n) 分配。存在外部碎片风险。 |
|
||||
| **设计一致性** | 符合 `01_数据接收模块设计.md` 中定义的 `packet_block_size_kb`。 | 增加不必要的复杂度。 |
|
||||
|
||||
-----
|
||||
|
||||
### 三、 基线确立与论证
|
||||
|
||||
基于上述分析,针对 **2.2.1 锁页内存管理与分配策略**,确立以下工程基线:
|
||||
|
||||
#### 1\. 分配 API 基线:`cudaMallocHost` + `cudaHostAllocMapped`
|
||||
|
||||
- **决策**:摒弃标准的 `new/malloc`,**强制**使用 GPU 运行时 API `cudaMallocHost` (在 CoreX SDK 中对应同名 API) 分配所有用于 H2D 传输的缓冲区。
|
||||
- **标志位 (Critical)**:必须使用 **`cudaHostAllocMapped`** 标志。
|
||||
- *论证*:这不仅锁定了页面,还将其映射到了 GPU 的统一地址空间(UVA)。这是实现后续 **2.2.4 零拷贝技术** 的先决条件。如果没有此标志,GPU 无法通过 PCIe 直接访问这块 CPU 内存。
|
||||
- **对齐约束**:`cudaMallocHost` 自动保证了页面对齐(通常 4KB 或 64KB),满足 DMA 要求。
|
||||
|
||||
#### 2\. 内存池策略:静态预分配 + 固定块管理
|
||||
|
||||
- **决策**:在系统初始化阶段(`initialize()`)一次性分配所有所需的锁页内存,**严禁**在 `start()` 后的运行时路径中调用 `cudaMallocHost`。
|
||||
- **实现细节**:
|
||||
- **池大小**:依据设计文档,预分配 **256MB**。
|
||||
- **块大小**:**64KB**。这足以容纳 9000 字节的 JUMBO Frame,且对齐友好。
|
||||
- **管理结构**:使用简单的 `std::stack<void*>` 或无锁队列 `SPSC` 管理空闲块指针,实现 O(1) 复杂度的获取与归还。
|
||||
|
||||
#### 3\. 伪代码规范 (C++14 Standard)
|
||||
|
||||
```cpp
|
||||
// 基线实现范式
|
||||
void* d_ptr; // Device pointer (mapped)
|
||||
void* h_ptr; // Host pointer
|
||||
|
||||
// 1. 必须使用 Mapped 标志以启用 UVA (为 2.2.4 准备)
|
||||
// 2. 必须使用 Portable 标志以支持多 GPU 上下文 (尽管目前是单卡,但在异构设计中是好习惯)
|
||||
unsigned int flags = cudaHostAllocMapped | cudaHostAllocPortable;
|
||||
|
||||
// 3. 分配 - 仅在初始化阶段执行
|
||||
cudaError_t err = cudaHostAlloc(&h_ptr, POOL_SIZE_BYTES, flags);
|
||||
if (err != cudaSuccess) {
|
||||
// 触发 Fatal Error 事件
|
||||
}
|
||||
|
||||
// 4. 获取对应的 Device 指针 (用于后续 2.2.4 Zero-Copy)
|
||||
cudaHostGetDevicePointer(&d_ptr, h_ptr, 0);
|
||||
|
||||
// … 将 h_ptr 切分为 64KB 的块放入 free_list …
|
||||
```
|
||||
|
||||
-----
|
||||
|
||||
**下一步行动**:
|
||||
|
||||
我们已确立了**如何分配**这块特殊的“高速内存”。接下来,我们需要讨论**如何调度**这块内存的传输,即 **2.2.2 异步流水线与计算通信重叠**,这将决定 GPU 的 Copy Engine 是否能被充分利用,从而掩盖 PCIe 带宽瓶颈。
|
||||
|
||||
**提问**:您是否同意使用带有 `Mapped` 标志的 `cudaMallocHost` 作为分配基线?确认后我们将进入 2.2.2 讨论三级流水线设计。
|
||||
@@ -0,0 +1,123 @@
|
||||
---
|
||||
tags:
|
||||
date created: 星期四, 十一月 20日 2025, 9:50:03 晚上
|
||||
date modified: 星期四, 十一月 20日 2025, 9:50:24 晚上
|
||||
---
|
||||
|
||||
# 2.2.2 异步流水线与计算通信重叠 (Asynchronous Pipelining & Compute-Copy Overlap)
|
||||
|
||||
**覆盖范围**:定义如何利用智铠 GPU 的独立 Copy Engine (DMA 引擎) 与 Compute Engine (计算引擎) 的并行能力,通过 **CUDA Streams** 实现“传输 - 计算 - 传输”的三级流水线并行,从而掩盖 PCIe 总线的物理延迟。
|
||||
|
||||
#### 一、 约束输入与对齐
|
||||
|
||||
1. **硬件能力**:Iluvatar MR-V100 通常具备独立的 Copy Engine(用于 H2D/D2H)和 Compute Engine。这意味着 **数据拷贝** 和 **Kernel 执行** 在硬件上是物理隔离的,可以同时进行。
|
||||
2. **API 约束**:必须使用 **Async** 系列 API (如 `cudaMemcpyAsync`) 配合 **Non-Default Stream** 才能触发重叠。
|
||||
3. **业务逻辑**:雷达信号处理通常是流式的:`接收(H2D) -> 处理(Kernel) -> 输出(D2H)`。
|
||||
|
||||
#### 二、 权衡分析与选项呈现 (Trade-off Matrix)
|
||||
|
||||
我们主要在**流的设计模式**上进行权衡:
|
||||
|
||||
| 选项 | A. 单流串行 (Serial Stream) | B. 多流乒乓/多缓冲 (Multi-Stream Ping-Pong) **(推荐)** | C. 细粒度多流 (Hyper-Q) |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **机制** | 1 个流。H2D -\> Kernel -\> D2H 顺序执行。 | 2-3 个流。Stream A 做计算时,Stream B 做 H2D 拷贝。 | N 个流(N \>\> 3)。将任务切分为极小片。 |
|
||||
| **PCIe 利用率** | **低**。总线在 Kernel 计算期间闲置。 | **高**。总线和计算单元始终处于忙碌状态。 | **极高**,但调度开销大。 |
|
||||
| **延迟掩盖** | 无掩盖。总耗时 = T(copy) + T(compute)。 | **完全掩盖**。理想情况下总耗时 = max(T(copy), T(compute))。 | 同上,但可能引入调度抖动。 |
|
||||
| **实现复杂度** | 低。 | 中。需要管理多个 Buffer 的状态 (Ping-Pong)。 | 高。 |
|
||||
| **适用性** | 调试模式。 | **雷达实时处理标准范式。** | 超大规模并发任务。 |
|
||||
|
||||
#### 三、 基线确立与实施规范
|
||||
|
||||
为了最大化吞吐量,我们确立 **B. 多流乒乓 (Multi-Stream Ping-Pong)** 为设计基线。
|
||||
|
||||
##### 1\. 流水线架构基线:三级流水线 + 双流 (Double Buffering)
|
||||
|
||||
- **核心逻辑**:创建 **2 个 CUDA Stream** (Stream 0, Stream 1) 和 **2 组页锁定内存 Buffer** (Buffer A, Buffer B)。
|
||||
- **调度策略**:
|
||||
- **时刻 T0**:Stream 0 开始传输 Buffer A (H2D)。
|
||||
- **时刻 T1**:
|
||||
- Stream 0 开始处理 Buffer A (Kernel)。
|
||||
- **同时**,Stream 1 开始传输 Buffer B (H2D) —— **此处发生了 Copy 与 Compute 的重叠**。
|
||||
- **时刻 T2**:
|
||||
- Stream 0 开始回传 Buffer A 结果 (D2H)。
|
||||
- Stream 1 开始处理 Buffer B (Kernel)。
|
||||
|
||||
##### 2\. 关键 API 实施规范
|
||||
|
||||
- **流创建**:
|
||||
|
||||
```cpp
|
||||
cudaStream_t streams[2];
|
||||
for(int i=0; i<2; i++) cudaStreamCreateWithFlags(&streams[i], cudaStreamNonBlocking);
|
||||
```
|
||||
|
||||
- *注意*:必须使用 `cudaStreamNonBlocking`,防止与默认流(Default Stream)发生隐式同步,导致流水线断流。
|
||||
- **异步传输**:
|
||||
|
||||
```cpp
|
||||
// 必须使用 Async 版本,且指定 stream
|
||||
cudaMemcpyAsync(d_ptr, h_ptr, size, cudaMemcpyHostToDevice, streams[i]);
|
||||
```
|
||||
|
||||
- **同步策略**:
|
||||
- **严禁**使用 `cudaDeviceSynchronize()`(全卡同步)。
|
||||
- **推荐**使用 `cudaStreamSynchronize(streams[i])` 或 `cudaEventRecord/Synchronize` 来精细控制单个 Buffer 的生命周期,确保 Host 端在复用 Buffer 前,GPU 已经操作完毕。
|
||||
|
||||
##### 3\. 缓冲区管理状态机
|
||||
|
||||
为了配合 `01_数据接收模块` 的 `MemoryPool`,我们需要一个简单的状态机来管理 Buffer 在 Host 和 Device 之间的流转:
|
||||
|
||||
- `HOST_OWNED` (I/O 线程填充数据)
|
||||
- `DEVICE_OWNED_H2D` (正在上传)
|
||||
- `DEVICE_OWNED_COMPUTE` (正在计算)
|
||||
- `DEVICE_OWNED_D2H` (正在回传)
|
||||
- `RELEASED` (回传完毕,归还 Pool)
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
%% 状态定义
|
||||
state "HOST_OWNED<br/>(主机所有)" as HOST
|
||||
state "DEVICE_OWNED_H2D<br/>(传输中: H->D)" as H2D
|
||||
state "DEVICE_OWNED_COMPUTE<br/>(计算中: Kernel)" as COMPUTE
|
||||
state "DEVICE_OWNED_D2H<br/>(传输中: D->H)" as D2H
|
||||
state "RELEASED<br/>(待归还)" as RELEASED
|
||||
|
||||
%% 流程流转
|
||||
[*] --> HOST : 从 MemoryPool 申请
|
||||
|
||||
HOST --> H2D : I/O线程填充数据\n并调用 cudaMemcpyAsync
|
||||
note right of HOST
|
||||
此时数据位于页锁定内存
|
||||
CPU 写入完成
|
||||
end note
|
||||
|
||||
H2D --> COMPUTE : 记录 H2D_Event\nStreamWaitEvent
|
||||
note right of H2D
|
||||
DMA 引擎正在搬运
|
||||
CPU 不阻塞
|
||||
end note
|
||||
|
||||
COMPUTE --> D2H : Kernel 执行完毕\n自动触发 D2H
|
||||
note right of COMPUTE
|
||||
GPU 核心正在计算
|
||||
数据驻留显存
|
||||
end note
|
||||
|
||||
D2H --> RELEASED : D2H 完成回调\n或 Event 同步
|
||||
note right of D2H
|
||||
结果已写回 Host
|
||||
end note
|
||||
|
||||
RELEASED --> HOST : DataPacket 析构\n自动归还 Pool
|
||||
|
||||
RELEASED --> [*]
|
||||
```
|
||||
|
||||
-----
|
||||
|
||||
**下一步行动**:
|
||||
|
||||
我们已经定义了 **“怎么传”**(Pinned Memory)和 **“怎么调度”**(Async Streams)。
|
||||
接下来,我们需要解决 **“传给谁** 的问题,即 **2.2.3 NUMA 感知的内存亲和性控制**。考虑到飞腾 S5000C 的双路架构,如果数据传错了 CPU 节点,上述所有优化都会因为 QPI 总线瓶颈而大打折扣。
|
||||
|
||||
**提问**:您是否同意将 **“双流乒乓 (Double Stream Ping-Pong)”** 作为异步流水线的基线?确认后我们进入 2.2.3 NUMA 亲和性的讨论。
|
||||
@@ -0,0 +1,93 @@
|
||||
---
|
||||
tags: []
|
||||
aliases:
|
||||
- 2.2.3 NUMA 感知的内存亲和性控制 (NUMA-Aware Memory Affinity Control)
|
||||
date created: 星期四, 十一月 20日 2025, 10:14:01 晚上
|
||||
date modified: 星期四, 十一月 20日 2025, 10:14:41 晚上
|
||||
---
|
||||
|
||||
# 2.2.3 NUMA 感知的内存亲和性控制 (NUMA-Aware Memory Affinity Control)
|
||||
|
||||
### 一、 约束输入与对齐 (Constraints & Alignment)
|
||||
|
||||
基于第一章的审计报告,我们面临以下**硬性物理约束**:
|
||||
|
||||
1. **CPU 拓扑**:
|
||||
- **Node 0**: CPU 0-15
|
||||
- **Node 1**: CPU 16-31
|
||||
2. **GPU 位置**:Iluvatar MR-V100 物理挂载在 **Node 1** 上。
|
||||
3. **OS 策略**:`numa_balancing` 已被禁用。这意味着我们不能指望操作系统自动把内存迁移到正确的节点,**必须**手动管理。
|
||||
4. **性能陷阱**:如果 Host 内存分配在 Node 0,而 DMA 引擎在 GPU (Node 1) 上,DMA 读取将必须穿过片间互联总线 (Inter-Chip Interconnect),这通常只有本地内存带宽的一半甚至更低。
|
||||
|
||||
-----
|
||||
|
||||
### 二、 权衡分析与选项呈现 (Trade-off Matrix)
|
||||
|
||||
#### 议题:如何强制内存与计算位于 Node 1?
|
||||
|
||||
| 选项 | A. 仅依赖 `numactl` (进程级绑定) | B. 代码级硬亲和性 (线程级绑定) | C. `mbind` / `set_mempolicy` (API 级内存绑定) |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **机制** | 在启动命令前加 `numactl --cpunodebind=1 --membind=1`。 | 在 C++ 代码中调用 `pthread_setaffinity_np` 将关键线程钉死在 Core 16-31。 | 在调用 `malloc` / `cudaMallocHost` 前设置内存分配策略。 |
|
||||
| **可靠性** | **高**。这是最稳健的保底方案,确保进程内所有内存页都在 Node 1。 | **极高**。可以精细控制哪个线程跑在哪个核(如 I/O 线程绑 Core 16, Worker 绑 Core 17-20)。 | **中**。`cudaMallocHost` 的行为可能受驱动实现影响,不如 `numactl` 强制有效。 |
|
||||
| **灵活性** | 低。整个进程被限制在半个 CPU 上。 | 高。允许非关键线程(如日志、监控)漂移到 Node 0。 | 高。允许精细控制每块内存的位置。 |
|
||||
| **实施成本** | 零代码修改。运维配置即可。 | 需要修改 `ExecutionEngine` 代码。 | 需要修改内存池代码。 |
|
||||
|
||||
-----
|
||||
|
||||
### 三、 基线确立与实施规范
|
||||
|
||||
为了达成 **P0 级的性能稳定性**,我们采取 **“运维强制 + 代码辅助”** 的双重保险策略。
|
||||
|
||||
#### 1\. 运维基线:全进程约束 (Process-Level)
|
||||
|
||||
- **决策**:所有雷达信号处理进程 **必须** 通过 `numactl` 启动。
|
||||
- **命令规范**:
|
||||
|
||||
```bash
|
||||
# 强制 CPU 和 内存 都在 Node 1
|
||||
numactl --cpunodebind=1 --membind=1 ./main_app
|
||||
```
|
||||
|
||||
- **论证**:这是最底层的安全网。即使代码写错了,OS 也不会把内存分配到 Node 0 去,只会报 OOM (Out of Memory),这比“默默变慢”更容易排查。
|
||||
|
||||
#### 2\. 代码基线:线程亲和性 (Thread-Level)
|
||||
|
||||
- **决策**:在 `ExecutionEngine` 中启动 I/O 线程和 Worker 线程时,**显式设置 CPU 亲和性**。
|
||||
- **资源规划 (示例)**:
|
||||
- **Core 16 (Node 1)**: `DataReceiver` 的 **I/O 线程** (独占,处理中断聚合后的高速包)。
|
||||
- **Core 17-24 (Node 1)**: `SignalProcessor` 的 **计算/Worker 线程** (负责 CUDA API 调用和数据封包)。
|
||||
- **Core 0-15 (Node 0)**: *非关键路径*(日志落盘、监控数据聚合、显控交互)。需要注意,虽然 `numactl` 限制了 `--cpunodebind=1`,但我们可以通过 `numactl --preferred=1` 或者在代码中用 `sched_setaffinity` 突破限制,将非实时任务扔回 Node 0(如果确实需要利用那 16 个核)。**但在 V1.0 阶段,建议简单化,全部限制在 Node 1。**
|
||||
- **C++ 实现规范**:
|
||||
|
||||
```cpp
|
||||
void set_thread_affinity(int core_id) {
|
||||
cpu_set_t cpuset;
|
||||
CPU_ZERO(&cpuset);
|
||||
CPU_SET(core_id, &cpuset);
|
||||
// 必须检查返回值,确保绑定成功
|
||||
if (pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset) != 0) {
|
||||
// 记录致命错误,因为实时性无法保证
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3\. 内存分配时机:First-Touch 原则
|
||||
|
||||
- **决策**:鉴于我们使用了 `cudaMallocHost`,CUDA 驱动通常会在**调用分配函数的那个线程**所在的 NUMA 节点上分配物理内存(或者遵循进程的 `membind` 策略)。
|
||||
- **规范**:**必须在 `initialize()` 阶段,且在已经绑定了 CPU 亲和性的线程中** 执行 `cudaMallocHost`。
|
||||
- *错误做法*:在主线程(可能还没绑定核)分配内存池,然后传递给工作线程。
|
||||
- *正确做法*:主线程先将自己绑定到 Node 1,或者通过 `numactl` 启动,然后再初始化 `MemoryPool`。
|
||||
|
||||
-----
|
||||
|
||||
### 总结与下一步行动
|
||||
|
||||
我们已经确立了:
|
||||
|
||||
1. **怎么分**:`cudaMallocHost` + Pinned + Mapped (2.2.1)
|
||||
2. **怎么传**:双流乒乓 + 重叠 (2.2.2)
|
||||
3. **在哪传**:**NUMA Node 1** (通过 `numactl` + 线程绑定) (2.2.3)
|
||||
|
||||
现在,物理层和传输层的地基已经打牢。下一步,我们需要讨论 **2.2.4 统一虚拟寻址与零拷贝技术 (UVA & Zero-Copy)**。这将决定我们在某些特定场景下(如传输波控码或小批量参数),是否可以**完全省去** `cudaMemcpy`,直接让 GPU " 伸手 " 到 Host 内存里拿数据。
|
||||
|
||||
**提问**:您是否确认 **“numactl 强制绑定 Node 1 + 关键线程显式钉核”** 的基线?确认后我们进入 2.2.4。
|
||||
116
系统基座文件/2/2.2/2.2.4 统一虚拟寻址与零拷贝技术 (UVA & Zero-Copy).md
Normal file
116
系统基座文件/2/2.2/2.2.4 统一虚拟寻址与零拷贝技术 (UVA & Zero-Copy).md
Normal file
@@ -0,0 +1,116 @@
|
||||
---
|
||||
tags: []
|
||||
aliases:
|
||||
- 2.2.4 统一虚拟寻址与零拷贝技术 (UVA & Zero-Copy)
|
||||
date created: 星期四, 十一月 20日 2025, 10:24:28 晚上
|
||||
date modified: 星期四, 十一月 20日 2025, 10:25:20 晚上
|
||||
---
|
||||
|
||||
# 2.2.4 统一虚拟寻址与零拷贝技术 (UVA & Zero-Copy)
|
||||
|
||||
## 一、 约束输入与对齐 (Constraints & Alignment)
|
||||
|
||||
根据审计结果与硬件特性,我们拥有以下有利条件:
|
||||
|
||||
1. **驱动支持 (UVA Ready)**:审计显示 `iluvatar.ko` 模块参数 `itr_enable_vmm_va:Y`,说明智铠驱动已开启虚拟内存管理,支持 UVA。这意味着 Host 指针可以直接被 GPU Kernel 解引用,无需显式指针转换(`cudaHostGetDevicePointer` 仍建议调用以确保兼容性,但逻辑上地址空间是统一的)。
|
||||
2. **物理通道**:PCIe 4.0 x8 (或 x16 修复后)。带宽虽高,但\*\* 延迟(Latency)\*\* 仍远高于访问板载显存(VRAM)。
|
||||
3. **计算特性**:雷达信号处理(FFT、滤波)是**访存密集型**任务,同一个数据点会被多次读取(例如 FFT 的蝶形运算)。
|
||||
|
||||
-----
|
||||
|
||||
## 二、 权衡分析与选项呈现 (Trade-off Matrix)
|
||||
|
||||
我们将数据分为两类场景进行权衡:**“小数据/控制流”** 与 **“大数据/原始回波”**。
|
||||
|
||||
### 场景 A:小数据传输(如波控码、雷达参数、状态字)
|
||||
|
||||
- **特征**:数据量小(\< 4KB),更新频率低,GPU 仅读取一次或极少次。
|
||||
|
||||
| 选项 | 1. 显式拷贝 (`cudaMemcpyAsync`) | 2. 零拷贝直接访问 (Zero-Copy) **(推荐)** |
|
||||
| :--- | :--- | :--- |
|
||||
| **机制** | `Host -> PCIe -> VRAM -> Kernel` | `Kernel -> PCIe -> Host RAM` |
|
||||
| **启动开销** | **高**。API 调用开销 + DMA 启动开销(约 10-20us)。 | **零**。无 API 调用,Kernel 直接读取指针。 |
|
||||
| **总线效率** | 低。对于几十字节的数据,DMA 建立连接的成本远超传输本身。 | 中。虽然单次 PCIe 访问延迟高,但省去了 DMA 启动时间,总体更快。 |
|
||||
| **适用性** | 不推荐。“杀鸡用牛刀”。 | **最佳实践**。适合传递动态参数结构体。 |
|
||||
|
||||
### 场景 B:大数据传输(原始回波 I/Q 数据)
|
||||
|
||||
- **特征**:数据量大(MB 级),吞吐要求高,Kernel 需**反复多次**读取同一块数据。
|
||||
|
||||
| 选项 | 1. 显式拷贝 (`cudaMemcpyAsync`) **(推荐)** | 2. 零拷贝直接访问 (Zero-Copy) |
|
||||
| :--- | :--- | :--- |
|
||||
| **机制** | `Host -> DMA(Burst) -> VRAM -> Kernel` | `Kernel -> PCIe(TLP) -> Host RAM` |
|
||||
| **访存带宽** | **极高 (VRAM)**。HBM/GDDR 带宽(900GB/s+)。 | **极低 (PCIe)**。受限于 PCIe x8/x16(16-32GB/s)。 |
|
||||
| **TLB 风险** | 无。数据在 VRAM 中物理连续。 | **高 (TLB Miss)**。GPU 需频繁通过 IOMMU 查询 Host 页表,导致流水线停顿。 |
|
||||
| **计算影响** | 计算核心全速运行,无 IO 等待。 | **计算核心饥饿**。Kernel 算几步就要等几百个时钟周期的 PCIe 数据。 |
|
||||
|
||||
-----
|
||||
|
||||
## 三、 基线确立与实施规范
|
||||
|
||||
基于上述分析,我们确立 **“小数据零拷贝,大数据显式拷贝”** 的混合策略基线。
|
||||
|
||||
### 1\. 小数据基线:UVA 零拷贝 (Zero-Copy)
|
||||
|
||||
针对雷达的**控制参数**(如 `DataContext` 中的元数据、当前波束指向信息),我们利用 UVA 特性实现零拷贝。
|
||||
|
||||
- **分配规范**:
|
||||
- 继续使用 `cudaMallocHost`。
|
||||
- **必须**添加 `cudaHostAllocMapped` | `cudaHostAllocWriteCombined` 标志。
|
||||
- *注意*:`WriteCombined` (WC) 会禁止 CPU 缓存。这对 CPU 读取极慢,但对 CPU 顺序写入 +GPU 读取性能极佳。因为这些参数通常是 CPU 写一次、GPU 读一次,WC 是绝佳选择。
|
||||
- **访问规范**:
|
||||
- CPU 端:直接写入结构体成员。
|
||||
- GPU 端:将 Host 指针直接传给 Kernel,Kernel 像访问普通显存一样解引用。
|
||||
|
||||
### 2\. 大数据基线:显式异步 DMA (Explicit DMA)
|
||||
|
||||
针对**原始回波数据**(即 `DataReceiver` 传递过来的 Payload),**严禁**使用零拷贝。
|
||||
|
||||
- **决策**:维持 2.2.2 确立的 `cudaMemcpyAsync` 三级流水线。
|
||||
- **论证**:
|
||||
- **带宽瓶颈**:雷达信号处理算法(如 FFT)的算术强度(Compute-to-Memory Ratio)通常较低,主要受限于显存带宽。如果让 Kernel 直接跨 PCIe 去读 Host 内存,带宽将从 \~900GB/s 骤降至 \~16GB/s,导致 GPU 算力闲置率高达 98%,这绝对是不可接受的。
|
||||
- **TLB 抖动**:大数据量的随机访问(或大跨度访问,如转置)会打爆 IOMMU 的 TLB 缓存,引发严重的性能抖动。
|
||||
|
||||
### 3\. 基线实施代码范式
|
||||
|
||||
```cpp
|
||||
// 1. 小数据 (参数/配置): 使用 Zero-Copy + WriteCombined
|
||||
struct RadarParams {
|
||||
float beam_azimuth;
|
||||
float beam_elevation;
|
||||
int waveform_id;
|
||||
};
|
||||
|
||||
RadarParams* h_params;
|
||||
// 分配时启用 Mapped (UVA) 和 WriteCombined (CPU写优化)
|
||||
cudaHostAlloc(&h_params, sizeof(RadarParams),
|
||||
cudaHostAllocMapped | cudaHostAllocWriteCombined);
|
||||
|
||||
// CPU 写入 (极快,直接进 Write Combine Buffer)
|
||||
h_params->beam_azimuth = 45.0f;
|
||||
|
||||
// GPU Kernel 启动 (直接传指针,无需 Memcpy)
|
||||
// d_params 可以通过 cudaHostGetDevicePointer 获取,或者在 UVA 下直接用 h_params
|
||||
process_kernel<<<…>>>(…, h_params);
|
||||
|
||||
|
||||
// 2. 大数据 (回波): 使用 Explicit Async Copy
|
||||
// 维持之前确立的 MemoryPool (Pinned) + Stream Async Copy
|
||||
cudaMemcpyAsync(d_buffer, h_pinned_buffer, size, cudaMemcpyHostToDevice, stream);
|
||||
process_data_kernel<<<…>>>(…, d_buffer); // Kernel 访问的是高速 VRAM
|
||||
```
|
||||
|
||||
-----
|
||||
|
||||
## 总结与下一步行动
|
||||
|
||||
我们确立了 **2.2.4 统一虚拟寻址与零拷贝技术** 的基线:
|
||||
|
||||
1. **控制流**:启用 **Zero-Copy (UVA + WC)**,消除小数据的传输开销。
|
||||
2. **数据流**:坚守 **Explicit DMA (VRAM)**,保障计算吞吐量。
|
||||
|
||||
现在,我们解决了“怎么传”、“传给谁”、“谁不用传”。只剩最后一个细节:**“传多大一块?”**
|
||||
|
||||
这是 **2.2.5 传输粒度与 TLP 效率优化**。PCIe 总线传输 1 个字节和传输 128 字节的物理开销(TLP Header)是一样的。如果我们的 `DataPacket` 切分太碎,PCIe 有效带宽就会打折。我们需要结合您的 `01_数据接收模块设计.md` 中的 `packet_block_size_kb` (64KB) 来最后确认这一粒度。
|
||||
|
||||
**提问**:您是否确认 **“控制流零拷贝,数据流显式拷贝”** 的混合基线?确认后我们进入 2.2.5。
|
||||
@@ -0,0 +1,90 @@
|
||||
---
|
||||
tags:
|
||||
aliases:
|
||||
- 2.2.5 传输粒度与 TLP 效率优化 (Transfer Granularity & TLP Efficiency)
|
||||
date created: 星期四, 十一月 20日 2025, 11:17:17 晚上
|
||||
date modified: 星期四, 十一月 20日 2025, 11:17:43 晚上
|
||||
---
|
||||
|
||||
# 2.2.5 传输粒度与 TLP 效率优化 (Transfer Granularity & TLP Efficiency)
|
||||
|
||||
在确定了物理通道(Node 1)和传输机制(DMA Async)后,这一节解决的是**“一次搬运多少数据最划算”**的问题。这看似是细节,但在 PCIe 物理瓶颈下(x8 降级 + 小 MPS),错误的粒度会导致总线有效带宽暴跌。
|
||||
|
||||
### 一、 约束输入与对齐 (Constraints & Alignment)
|
||||
|
||||
基于之前的审计结果和设计文档,我们面临两个严峻的物理与逻辑约束:
|
||||
|
||||
1. **PCIe 物理瓶颈 (P0)**:
|
||||
- **链路状态**:PCIe 4.0 x8 (Downgraded)。
|
||||
- **MPS (Max Payload Size)**:审计发现部分设备仅为 **128 Bytes** 或 **256 Bytes**。
|
||||
- *解读*:这是 PCIe 协议层的最大包长。这意味着无论您上层 DMA 发多大的数据块,到底层都会被切碎成 128 字节的小片。
|
||||
- *代价*:PCIe TLP (Transaction Layer Packet) 头部开销约 12-16 字节。如果 MPS 只有 128 字节,**固定协议开销占比高达 ~10%**。这是物理层“税”,我们无法改变,只能通过上层策略来稀释**驱动层的启动开销**。
|
||||
|
||||
2. **逻辑数据块定义**:
|
||||
- **内存池块大小**:`01_数据接收模块设计.md` 中定义 `packet_block_size_kb` 默认为 **64KB**。
|
||||
- **信号处理单位**:雷达处理通常基于 **CPI (Coherent Processing Interval)** 或 **脉冲 (Pulse)**,其数据量通常在 MB 级别。
|
||||
|
||||
---
|
||||
|
||||
### 二、 权衡分析与选项呈现 (Trade-off Matrix)
|
||||
|
||||
我们需要在**实时性(低延迟)**和**总线吞吐率**EHOLDER}总线吞吐率**之间寻找平衡点。
|
||||
|
||||
#### 议题:DMA 传输粒度 (Transfer Batch Size)
|
||||
|
||||
| 选项 | A. 单包/单脉冲传输 (Fine-Grained) | B. 块/批次传输 (Coarse-Grained) **和** |
|
||||
| :--- | :--- | :--- |
|
||||
| **(推荐)** | 9KB (1 个 JUMBO Frame) 或 32KB (1 个脉冲) | **粒度示例** (多个脉冲或完整 CPI) |
|
||||
| **64KB - 2MB** | **驱动开销**。每次 DMA 启动都需要 CPU 陷入内核态写寄存器(约 5-10us)。如果每秒 10,000 包,CPU 光启动 DMA 就占满核心。 | **极高**。启动开销被大量数据摊薄。 |
|
||||
| **低** | **PCIe 效率**。频繁的小传输会导致 PCIe 链路在“空闲”和“忙碌”间切换,难以形成突发传输 (Burst),无法填满 MPS 限制下的带宽。 | **低**。长传输能让 PCIe 控制器充分利用总线,连续发送 TLP,达到物理带宽极限。 |
|
||||
| **高** | 理论延迟最低,但容易受 CPU 抖动影响。 | 引入了 **延迟表现** (等待凑够一批数据),但抖动更小,流水线更稳。 |
|
||||
|
||||
---
|
||||
|
||||
### 三、 基线确立与实施规范
|
||||
|
||||
为了在 PCIe x8 和小 MPS 的双重限制下“榨干”带宽,我们必须采取 **“组包延迟”** 的策略。
|
||||
|
||||
#### 1. 传输粒度基线:**“大块聚合”**
|
||||
|
||||
- **≥ 64KB (对齐内存池块)**:确立 **决策** 为最小 DMA 传输单元(Minimum DMA Unit)。
|
||||
- **64KB**:
|
||||
- 您的 `MemoryPool` 设计为 **论证** 一块,这恰好是一个平衡点。
|
||||
- 在 PCIe 4.0 x8 上,传输 64KB 耗时约 4-5us。这足以掩盖 DMA 引擎的启动开销(Launch Overhead),使总线利用率进入“高效区”。
|
||||
- **64KB**针对每个 9KB 的 UDP 包单独发起 `cudaMemcpyAsync`。这会引发 CPU 中断风暴并导致 GPU 指令队列溢出。
|
||||
|
||||
#### 2. 动态批处理策略 (Adaptive Batching)
|
||||
|
||||
考虑到雷达工作模式(搜索/跟踪)的脉冲重复频率(PRF)不同,建议在 `ExecutionEngine` 中实施动态策略:
|
||||
|
||||
- **严禁**:
|
||||
- **策略逻辑**:当 `DataReceiver` 填满一个 64KB 的 `MemoryBlock` 时,立即标记为就绪。
|
||||
- **空间触发**:如果数据流较慢(如低重频模式),设定一个 **时间触发**。如果 200us 内没填满 64KB,强制推送当前已有数据。
|
||||
- **超时阈值 (e.g., 200us)**:防止在低数据率下,为了凑满 64KB 而导致首个数据包滞留过久,破坏 **目的** 的延迟 KPI。
|
||||
|
||||
#### 3. 显存对齐与 TLP 优化
|
||||
|
||||
- **P99 < 5ms**:DMA 的目标地址(GPU 显存)首地址必须 **决策**。
|
||||
- **256 字节对齐**:
|
||||
- 虽然审计显示 MPS 可能是 128B,但为了适配可能的 256B MPS 设备及 GPU 内存控制器的合并访问需求(通常要求 128B/256B 对齐),**论证**是通用且安全的基线。
|
||||
- **256B 对齐**:`cudaMalloc` 分配的内存天然是 256B 对齐的。关键在于如果我们在 Host 端把多个小包拼到一个大 Buffer 里,**实现**最好也是 128B/256B 的倍数。
|
||||
|
||||
#### 4. TLP 效率的终极计算 (Reality Check)
|
||||
|
||||
- **每个子块的偏移量**:MPS = 128 Bytes。
|
||||
- **现状**:每个 TLP 包 = 12-16B Header + 128B Data。
|
||||
- **理论极限**:$128 / (128 + 16) \approx 88.8\%$。
|
||||
- **最高有效率**:无论软件层如何优化,PCIe 层的物理开销决定了您**结论**。在评估带宽 KPI (`> 70% of theoretical max`) 时,必须扣除这 ~11% 的硬件损耗。**永远无法达到 100% 的理论带宽**。
|
||||
|
||||
---
|
||||
|
||||
### 总结与下一步行动
|
||||
|
||||
我们确立了:
|
||||
1. **目标设定为理论值的 75%-80% 是合理的极限**:**粒度** (与内存池对齐),严禁单包传输。
|
||||
2. **最小 64KB**:**策略** 双触发。
|
||||
3. **空间满 (64KB) 或 时间到 (200us)**:强制 **对齐**。
|
||||
|
||||
至此,H2D (Host-to-Device) 的传输策略已完全定型。数据进入显存后,如何存放才能让 GPU 算得快?这是 **256 字节对齐** 的内容,涉及 SoA vs AoS 以及 Padding 策略,这直接影响 Kernel 的访存效率。
|
||||
|
||||
**2.2.6 显存布局与对齐约束**:您是否确认 **提问** 的基线?确认后我们进入 2.2.6。
|
||||
@@ -0,0 +1,123 @@
|
||||
---
|
||||
tags: []
|
||||
date created: 星期四, 十一月 20日 2025, 11:20:35 晚上
|
||||
date modified: 星期四, 十一月 20日 2025, 11:21:14 晚上
|
||||
---
|
||||
|
||||
# 2.2.6 显存布局与对齐约束 (VRAM Layout & Alignment Constraints)
|
||||
|
||||
- **覆盖范围**:定义雷达数据立方体(Radar Data Cube)在显存中的物理排列格式。重点解决 **SoA (结构数组)** vs **AoS (数组结构)** 的选择、**Padding (填充)** 策略,以及适配 `cuFFT` / `cuBLAS` 库要求的复数存储格式。
|
||||
|
||||
## 一、 约束输入与对齐 (Constraints & Alignment)
|
||||
|
||||
基于 `02_信号处理模块设计.md` 和 GPU 硬件特性,我们要对齐以下约束:
|
||||
|
||||
1. **硬件合并访问 (Coalesced Access)**:GPU 读取显存的最佳模式是“一刀切”。同一个 Warp(32 个线程)必须访问一段**连续且对齐**的内存(通常是 128 字节)。如果数据是跳跃的(Strided),有效带宽会下降 80% 以上。
|
||||
2. **雷达数据立方体特性**:数据具有三个维度:`[通道 (Channel)]`、`[脉冲 (Pulse)]`、`[距离门 (Range)]`。
|
||||
3. **算法库约束**:
|
||||
- **CoreX Math Libs (cuFFT)**:智铠重构版 `cuFFT` 通常要求输入数据为 **Interleaved Complex** (`float2` 或 `cuComplex`,即 `real, imag` 相邻) 或 **Split Complex** (`real[]`, `imag[]` 分离)。标准 CUDA 库倾向于 **Interleaved**。
|
||||
4. **并行维度**:
|
||||
- **脉冲压缩**:在 **距离门** 维度并行。这意味着“距离”维必须是内存中最连续的维度(Stride = 1)。
|
||||
|
||||
-----
|
||||
|
||||
## 二、 权衡分析与选项呈现 (Trade-off Matrix)
|
||||
|
||||
### 议题 1:复数数据格式 (Complex Number Format)
|
||||
|
||||
| 选项 | A. 交织存储 (Interleaved / AoS) **(推荐)** | B. 分离存储 (Split / SoA) |
|
||||
| :--- | :--- | :--- |
|
||||
| **格式** | `R I R I R I …` (`struct {float r, i}`) | `R R R …` / `I I I …` |
|
||||
| **cuFFT 兼容性** | **原生支持**。`cufftExecC2C` 默认接受此格式。 | 需要使用 `cufftExecZ2Z` 并配置 stride,或者手动转换,稍显麻烦。 |
|
||||
| **访存效率** | **高**。读取一个复数只需一次 64-bit 加载指令(`LD.E`)。 | **中**。读取一个复数需要两次 32-bit 加载指令,且地址相隔很远,增加指令发射压力。 |
|
||||
| **结论** | **基线标准**。 | 不推荐,除非特定算法有强需求。 |
|
||||
|
||||
### 议题 2:数据立方体排列 (Data Cube Layout)
|
||||
|
||||
假设我们处理一个 `C` 通道、`P` 脉冲、`R` 距离门的数据块。
|
||||
|
||||
| 选项 | A. `[Channel][Pulse][Range]` (推荐) | B. `[Range][Pulse][Channel]` |
|
||||
| :--- | :--- | :--- |
|
||||
| **最内层维度** | **Range (距离)**。内存中连续存放 `R0, R1, R2…`。 | **Channel (通道)**。内存中连续存放 `C0, C1, C2…`。 |
|
||||
| **脉冲压缩友好度** | **完美**。FFT 是针对 Range 做的,数据连续,读取效率 100%。 | **灾难**。FFT 需要读 Range 维,这里 Range 维跨度极大,导致严重的 TLB Miss 和非合并访问。 |
|
||||
| **波束合成友好度** | **差**。DBF 需要跨通道计算。但在脉压之后做一次**转置**即可解决。 | **好**。 |
|
||||
| **结论** | **基线标准**。符合“先脉压,后多普勒/DBF”的处理流。 | 仅适用于纯 DBF 前置的特殊雷达。 |
|
||||
|
||||
### 议题 3:行对齐与 Pitch (Padding Strategy)
|
||||
|
||||
显存是按“行”管理的。如果一行的字节数不是 256 字节的倍数,换行访问时就会错位,破坏对齐。
|
||||
|
||||
| 选项 | A. 紧凑排列 (Packed) | B. 对齐填充 (Pitched / Padded) **(推荐)** |
|
||||
| :--- | :--- | :--- |
|
||||
| **机制** | 数据紧挨着放。`Row1_End` 紧接 `Row2_Start`。 | 在每行末尾填充垃圾数据,使得 `Row_Stride` 是 256B 的倍数。 |
|
||||
| **空间利用** | 100%。 | 略有浪费(\< 1%)。 |
|
||||
| **访问性能** | **不稳定**。如果 `R` 不是 64 的倍数,第二行的起始地址就未对齐,导致 Warp 访问分裂,性能下降。 | **极致稳定**。确保每一行的起始地址都是对齐的,所有 Kernel 都能全速运行。 |
|
||||
|
||||
-----
|
||||
|
||||
## 三、 基线确立与实施规范
|
||||
|
||||
为了让 GPU 的吞吐量 KPI 达标,我们确立以下显存基线:
|
||||
|
||||
### 1\. 数据结构基线:Interleaved Complex (`float2`)
|
||||
|
||||
- **决策**:所有 I/Q 信号在显存中统一存储为 **`float2`** 类型(对应 `std::complex<float>`)。
|
||||
- **规范**:
|
||||
|
||||
```cpp
|
||||
// 严禁使用自定义结构体,直接用 CUDA 内置类型以确保编译器优化
|
||||
using Complex = float2;
|
||||
// 实部: .x, 虚部: .y
|
||||
```
|
||||
|
||||
### 2\. 物理布局基线:`[Channel][Pulse][Range]` + Padding
|
||||
|
||||
- **决策**:采用 **Channel-Major, Pulse-Major, Range-Contiguous** 的布局,并对 **Range** 维度实施 **Pitch 对齐**。
|
||||
- **二维视图**:将三维立方体视为一个巨大的二维矩阵:
|
||||
- **宽度 (Width)**:`Range * sizeof(Complex)`
|
||||
- **高度 (Height)**:`Channel * Pulse`
|
||||
- **分配规范**:**必须**使用 `cudaMallocPitch` 分配显存,而不是 `cudaMalloc`。
|
||||
- *论证*:`cudaMallocPitch` 会自动计算最佳的 `pitch`(字节步长),确保每一行(即每一个脉冲)的起始地址都满足硬件(Iluvatar MR-V100)的 256 字节对齐要求。
|
||||
|
||||
### 3\. 传输与访问规范
|
||||
|
||||
- **H2D 传输**:使用 `cudaMemcpy2DAsync`。
|
||||
- 虽然数据源(Host Pinned Memory)可能是紧凑的,但目标(Device Memory)是有 Padding 的。`cudaMemcpy2D` 能自动处理这种“紧凑到对齐”的搬运。
|
||||
- **Kernel 索引**:
|
||||
|
||||
```cpp
|
||||
// Kernel 代码范式
|
||||
// pitch 是字节单位,需要转为 float2 单位
|
||||
int stride = pitch / sizeof(float2);
|
||||
int p = blockIdx.y; // 脉冲索引 (或 C*P 的扁平索引)
|
||||
int r = threadIdx.x + blockIdx.x * blockDim.x; // 距离门索引
|
||||
|
||||
if (r < Range) {
|
||||
// 使用 stride 而不是 Range 来换行,跳过 Padding 区域
|
||||
Complex data = input_data[p * stride + r];
|
||||
// … 计算 …
|
||||
}
|
||||
```
|
||||
|
||||
## 总结:2.2 章节最终基线图谱
|
||||
|
||||
至此,我们完成了 **2.2 异构 DMA 与内存传输机制** 的全链路设计。从 Host 内存分配到 PCIe 传输,再到显存落地,每一环都扣上了“高性能”的锁扣。
|
||||
|
||||
| 环节 | 基线决策 | 关键配置/API |
|
||||
| :--- | :--- | :--- |
|
||||
| **2.2.1 内存分配** | **锁页内存 + UVA 映射** | `cudaMallocHost` + `cudaHostAllocMapped` |
|
||||
| **2.2.2 传输调度** | **双流乒乓 (Double Buffering)** | 2x `cudaStream_t`, `cudaMemcpyAsync` |
|
||||
| **2.2.3 物理位置** | **NUMA Node 1 本地化** | `numactl --cpunodebind=1 --membind=1` |
|
||||
| **2.2.4 零拷贝** | **控制流 Zero-Copy,数据流 Explicit** | `WC` 标志 (控制流) vs `Async Copy` (数据流) |
|
||||
| **2.2.5 传输粒度** | **≥ 64KB 块,256B 对齐** | 空间/时间双触发策略 |
|
||||
| **2.2.6 显存布局** | **`[C][P][R]` 布局 + Pitch 对齐** | `cudaMallocPitch`, `float2` (Interleaved) |
|
||||
|
||||
-----
|
||||
|
||||
**下一步行动**:
|
||||
|
||||
2.2 章节已圆满完成。我们可以进入 **2.3 内部控制平面通信接口 (Internal Control Plane Interface - IPC)**。
|
||||
|
||||
虽然数据流(Data Plane)的问题解决了,但 Host 端的**任务调度器**如何优雅地指挥**信号处理模块**?如何不通过锁竞争就能实现高频的控制指令下发?这将是 2.3 讨论的重点。
|
||||
|
||||
**提问**:您是否确认 **2.2.6 的 Pitch 对齐与布局基线**?确认后我们结束 2.2 章节,进入 2.3。
|
||||
@@ -0,0 +1,25 @@
|
||||
---
|
||||
tags:
|
||||
aliases:
|
||||
- 2.3 内部控制平面通信接口 (Internal Control Plane Interface - IPC)
|
||||
date created: 星期四, 十一月 20日 2025, 11:47:20 晚上
|
||||
date modified: 星期四, 十一月 20日 2025, 11:48:22 晚上
|
||||
---
|
||||
|
||||
# 2.3 内部控制平面通信接口 (Internal Control Plane Interface - IPC)
|
||||
|
||||
- **覆盖范围**:定义系统内部各功能模块(`IModule`)与核心管理组件(调度器、配置管理器)之间的**控制流交互机制**。该接口基于**进程内事件总线(In-Process EventBus)**架构,实现模块间的解耦、生命周期编排、资源仲裁及故障传递。**核心约束**:控制平面严禁传输任何业务数据块(如 I/Q 数据或点迹数组),仅允许传输元数据、状态码和控制指令。
|
||||
- **2.3.1 事件总线架构与路由机制 (Event Bus Architecture & Routing Mechanism)**
|
||||
- **核心指向**:定义系统控制流的中枢神经。采用**发布 - 订阅 (Pub/Sub)** 模式,实现 `IEventBus` 接口。支持**同步分发**(`publishSync`,用于高优先级指令的即时回调)与**异步分发**(`publishAsync`,用于状态上报的非阻塞入队)的混合路由策略,确保控制指令在微秒级内准确送达。
|
||||
- **2.3.2 全链路追踪上下文传递 (Trace Context Propagation)**
|
||||
- **核心指向**:定义控制指令的审计与追踪规范。强制要求所有控制事件(Event)必须携带全局唯一的 `TraceID`。涵盖在跨线程(如从 `API网关` 线程到 `SignalProcessor` 工作线程)传递事件时,利用 `TraceContextGuard` 或类似的 **RAII 机制**自动捕获、保存和恢复线程本地存储(TLS)中的追踪上下文,实现“无感”的链路追踪。
|
||||
- **2.3.3 生命周期编排与状态同步协议 (Lifecycle Orchestration & State Synchronization)**
|
||||
- **核心指向**:定义 `TaskScheduler` 与业务模块间的握手协议。涵盖标准化的生命周期指令事件(`StartModuleEvent`, `StopModuleEvent`, `PauseModuleEvent`)以及模块的状态变更回执(`ModuleRunningEvent`, `ModuleStoppedEvent`)。重点关注在系统启动/关闭时的**拓扑依赖顺序**控制逻辑,确保无“悬空”状态。
|
||||
- **2.3.4 故障传播与恢复信令 (Fault Propagation & Recovery Signaling)**
|
||||
- **核心指向**:定义异常情况下的通信契约。涵盖**致命错误上报**(`ModuleFailedEvent`,携带标准化 `ErrorCode` 和堆栈快照)的格式,以及调度器下发的**恢复指令流**(如 `PauseDataFlow` -> `RestartModule` -> `ResumeDataFlow`)的时序规范。集成**熔断器(Circuit Breaker)**状态广播,防止故障扩散。
|
||||
- **2.3.5 资源仲裁与抢占式优先级控制 (Resource Arbitration & Preemptive Priority Control)**
|
||||
- **核心指向**:针对 CPU/GPU 异构计算资源的动态协调接口。涵盖由 `ResourceCoordinator` 发出的强制性指令(如 `SetComputePriorityEvent(LOW/HIGH)`),以及业务模块在收到指令后切换 **CUDA 流优先级** 或执行 **任务分片(Task Slicing)** 避让的响应时限要求(如 < 10ms)。
|
||||
- **2.3.6 两阶段配置热更新协议 (Two-Phase Configuration Hot-Reload Protocol)**
|
||||
- **核心指向**:定义动态配置变更时的协商机制。涵盖 `ConfigManager` 发起的 **“验证询问”**(`ValidateConfigChangeEvent`,模块需在超时前反馈可行性)和 **“变更通知”**(`ConfigChangedEvent`,模块执行原子更新),确保在并发环境下配置更新的事务一致性。
|
||||
- **2.3.7 性能指标遥测通道 (Performance Telemetry Channel)**
|
||||
- **核心指向**:定义业务模块向 `MonitoringModule` 上报健康数据的单向通道。涵盖 `MetricsUpdateEvent` 的数据结构定义(键值对映射),以及采用 **线程本地缓存(Thread-Local Storage)** 结合 **MPSC(多生产单消费)队列** 的高吞吐、无锁上报策略,彻底消除监控逻辑对业务主线程的锁竞争干扰。
|
||||
@@ -0,0 +1,120 @@
|
||||
---
|
||||
tags: []
|
||||
date created: 星期四, 十一月 20日 2025, 11:58:41 晚上
|
||||
date modified: 星期四, 十一月 20日 2025, 11:59:25 晚上
|
||||
---
|
||||
|
||||
# 2.3.1 事件总线架构与路由机制 (Event Bus Architecture & Routing Mechanism)
|
||||
|
||||
## 一、 约束输入与对齐 (Constraints & Alignment)
|
||||
|
||||
基于您提供的设计文档(特别是 `05_任务调度器设计.md`)和系统环境,我们面临以下**硬性约束**:
|
||||
|
||||
1. **进程内通信 (In-Process)**:本节讨论的是同一个进程(`main_app`)内部,不同 C++ 对象(模块)之间的交互。**严禁**引入 Socket、Pipe 或由 OS 调度的 IPC 机制(如 DBus/ZMQ),以避免微秒级的系统调用开销。
|
||||
2. **语言标准**:必须兼容 **C++14** (GCC 7.3)。
|
||||
3. **实时性要求**:控制指令(如 `StopModule`)必须在 **\< 1ms** 内到达目标模块。
|
||||
4. **全链路追踪**:事件总线是 `TraceID` 传递的关键载体,必须支持上下文的自动传播。
|
||||
|
||||
-----
|
||||
|
||||
## 二、 权衡分析与选项呈现 (Trade-off Matrix)
|
||||
|
||||
### 议题 1:路由分发策略 (Dispatch Strategy)
|
||||
|
||||
| 选项 | A. 纯同步直接调用 (Synchronous Direct) | B. 纯异步队列 (Asynchronous Queued) | C. 混合双通道 (Hybrid Dual-Channel) **(推荐)** |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **机制** | `publish()` 时直接在**调用者线程**遍历并执行所有回调函数。 | `publish()` 将事件推入队列。后台线程池异步取出并执行回调。 | 提供 `publishSync`(高优指令)和 `publishAsync`(状态上报)两个接口。 |
|
||||
| **延迟** | **最低 (微秒级)**。无上下文切换,无排队。 | **较高**。受队列深度和调度器负载影响。 | **灵活**。关键指令零延迟,非关键消息不阻塞主业务。 |
|
||||
| **死锁风险** | **高**。如果回调函数里又发了新事件,容易导致递归死锁。 | **低**。解耦了生产者和消费者。 | **中**。需规范同步通道的使用场景。 |
|
||||
| **适用场景** | 紧急停止、资源抢占。 | 日志上报、非关键状态更新。 | **生产环境标准解**。 |
|
||||
|
||||
### 议题 2:订阅者模型 (Subscriber Model)
|
||||
|
||||
| 选项 | A. 泛型/模板回调 (Type-Erasure) **(推荐)** | B. 继承接口 (Inheritance) |
|
||||
| :--- | :--- | :--- |
|
||||
| **机制** | `bus->subscribe<MyEvent>(lambda)`。利用 `std::function` 和 `std::type_index`。 | 订阅者必须实现 `IEventHandler<MyEvent>` 接口。 |
|
||||
| **耦合度** | **极低**。模块不需要继承特定基类,只要函数签名对就行。 | **高**。侵入性强,增加类层级复杂度。 |
|
||||
| **灵活性** | **高**。支持 Lambda,便于捕获 `this` 指针或上下文。 | 低。 |
|
||||
| **性能** | 极高(现代编译器优化 `std::function` 很好)。 | 虚函数调用开销(微小)。 |
|
||||
|
||||
-----
|
||||
|
||||
## 三、 基线确立与实施规范
|
||||
|
||||
为了兼顾雷达系统对**指令的即时响应**(如资源抢占)和**状态处理的高吞吐**(如海量模块状态变更),我们确立 **C. 混合双通道 + 泛型回调** 为设计基线。
|
||||
|
||||
### 1\. 接口定义基线 (C++14)
|
||||
|
||||
我们定义一个强类型的、支持 `TraceID` 注入的接口。
|
||||
|
||||
```cpp
|
||||
class IEventBus {
|
||||
public:
|
||||
virtual ~IEventBus() = default;
|
||||
|
||||
/**
|
||||
* @brief 订阅特定类型的事件
|
||||
* @tparam EventType 事件结构体类型
|
||||
* @param handler 回调函数,接收 const EventType&
|
||||
*/
|
||||
template <typename EventType>
|
||||
void subscribe(std::function<void(const EventType&)> handler);
|
||||
|
||||
/**
|
||||
* @brief 同步发布 (高优先级指令)
|
||||
* @details 在当前线程立即执行所有订阅者。调用者会被阻塞直到所有处理完成。
|
||||
* @param event 事件对象 (需继承自 BaseEvent 以携带 TraceID)
|
||||
*/
|
||||
template <typename EventType>
|
||||
void publishSync(const EventType& event);
|
||||
|
||||
/**
|
||||
* @brief 异步发布 (状态上报/非关键消息)
|
||||
* @details 将事件放入无锁队列,由 EventBus 内部的 Worker 线程稍后处理。立即返回。
|
||||
* @param event 事件对象
|
||||
*/
|
||||
template <typename EventType>
|
||||
void publishAsync(const EventType& event);
|
||||
};
|
||||
```
|
||||
|
||||
### 2\. 核心实现机制
|
||||
|
||||
- **同步通道 (`publishSync`)**:
|
||||
- **实现**:直接查找 `std::unordered_map<std::type_index, std::vector<Handler>>`。
|
||||
- **锁策略**:使用 `std::shared_timed_mutex` (读写锁)。发布时加**读锁**(允许多个事件同时发布,只要不修改订阅关系),订阅时加**写锁**。
|
||||
- **死锁规避**:**严禁**在 `publishSync` 的回调中再次调用 `subscribe`(修改订阅表)。允许递归调用 `publish`,但需注意栈溢出风险。
|
||||
- **异步通道 (`publishAsync`)**:
|
||||
- **实现**:维护一个 `WorkQueue`。由于事件类型各异,队列元素需使用 `std::function<void()>` 包装器(Type Erasure)来存储“执行动作”,而不是存储原始事件数据。
|
||||
- **并发模型**:
|
||||
- **单分发线程 (默认)**:一个后台线程专门负责从队列取任务并执行。保证了同一事件的消费顺序。
|
||||
- **队列选型**:**MoodyCamel `ConcurrentQueue`** (MPMC 无锁队列) 或 `boost::lockfree::queue`。鉴于麒麟系统库现状,若无第三方库,使用 `std::deque` + `std::mutex` + `std::condition_variable` 也是可接受的(吞吐量在控制面不是瓶颈)。
|
||||
|
||||
### 3\. TraceID 的隐式传递 (2.3.2 预埋)
|
||||
|
||||
- **基线要求**:`IEventBus` 不仅仅是搬运工,它还是**上下文的管理者**。
|
||||
- **同步发布时**:`TraceID` 自然随着线程栈传递。
|
||||
- **异步发布时**:`publishAsync` 必须在**入队时**捕获当前线程的 `TraceID`,并将其打包到 Lambda 中。在**出队执行时**,先恢复该 `TraceID` 到线程本地存储 (TLS),再执行回调。
|
||||
- *这解决了异步调用导致追踪链断裂的经典难题。*
|
||||
|
||||
### 4\. 异常安全边界
|
||||
|
||||
- **基线决策**:`EventBus` **必须捕获**订阅者抛出的所有异常。
|
||||
- **论证**:一个模块的回调函数崩溃(throw exception)绝不能导致发消息的模块(如调度器)崩溃,也不能中断后续其他模块接收该事件。
|
||||
- **行为**:`try-catch` 包裹每个 handler 的调用。捕获异常后,记录 `ERROR` 日志(包含 TraceID),然后继续执行下一个 handler。
|
||||
|
||||
-----
|
||||
|
||||
## 总结与下一步行动
|
||||
|
||||
我们确立了 **2.3.1 事件总线** 的基线:
|
||||
|
||||
1. **架构**:**混合双通道 (Sync/Async)**。
|
||||
2. **实现**:**泛型 Pub/Sub**,基于 C++14。
|
||||
3. **安全**:**异常隔离** + **读写锁保护**。
|
||||
|
||||
有了这个“神经系统”,我们可以开始讨论在这上面跑什么“数据包”了。为了让全链路追踪真正生效,我们需要定义事件的“信封”格式。
|
||||
|
||||
**下一步建议**:进入 **2.3.2 全链路追踪上下文传递 (Trace Context Propagation)**,详细定义 `BaseEvent` 结构和 `TraceContextGuard` 的实现机制。这是之前多次提到的 `TraceID` 落地的具体技术点。
|
||||
|
||||
**提问**:您是否确认 **“混合双通道 + 泛型回调”** 的事件总线基线?确认后我们将深入 2.3.2。
|
||||
152
系统基座文件/2/2.3/2.3.2 全链路追踪上下文传递 (Trace Context Propagation).md
Normal file
152
系统基座文件/2/2.3/2.3.2 全链路追踪上下文传递 (Trace Context Propagation).md
Normal file
@@ -0,0 +1,152 @@
|
||||
---
|
||||
tags: []
|
||||
date created: 星期五, 十一月 21日 2025, 12:00:13 凌晨
|
||||
date modified: 星期一, 十一月 24日 2025, 4:31:24 下午
|
||||
---
|
||||
|
||||
# 2.3.2 全链路追踪上下文传递 (Trace Context Propagation)
|
||||
|
||||
## 一、 约束输入与对齐 (Constraints & Alignment)
|
||||
|
||||
基于设计文档和 C++14 环境,我们需要对齐以下硬性约束:
|
||||
|
||||
1. **无侵入性 (Non-Intrusive)**:业务逻辑代码(如算法计算)不应到处传递 `trace_id` 参数。追踪上下文的获取应当是“隐式”的。
|
||||
2. **跨线程连续性 (Cross-Thread Continuity)**:系统大量使用异步队列(`EventBus::publishAsync`)和工作线程池。TraceID 必须能跨越线程边界,不能断链。
|
||||
3. **性能极其敏感**:追踪机制是**热路径 (Hot Path)**。获取当前 TraceID 的开销必须是纳秒级,严禁涉及锁竞争或复杂的哈希查找。
|
||||
4. **来源明确**:
|
||||
|
||||
- **数据面**:由 `DataReceiver` 在收到 UDP 包时生成。
|
||||
- **控制面**:由 `TaskScheduler` 在定时任务或外部 API 调用时生成。
|
||||
|
||||
---
|
||||
|
||||
## 二、 权衡分析与选项呈现 (Trade-off Matrix)
|
||||
|
||||
### 议题 1:上下文存储方式 (Storage Mechanism)
|
||||
|
||||
|**选项**|**A. 显式参数传递 (Explicit Parameter)**|**B. 全局 Map 映射 (Global Map)**|**C. 线程本地存储 (Thread Local Storage - TLS) (推荐)**|
|
||||
|---|---|---|---|
|
||||
|**机制**|每个函数增加 `const TraceId& tid` 参数。|维护 `Map<ThreadID, TraceID>`。|使用 C++ `thread_local` 关键字。|
|
||||
|**侵入性**|**极高**。所有接口签名都要改,污染业务代码。|**低**。但在读写时需要加锁(或无锁 Map),有性能开销。|**零**。业务代码无感。|
|
||||
|**性能**|最佳。|差(锁竞争)。|**极佳**。直接的内存地址访问,无锁。|
|
||||
|**缺陷**|代码丑陋。|性能瓶颈。|**跨线程时会丢失**(需额外机制弥补)。|
|
||||
|
||||
### 议题 2:跨线程传递策略 (Propagation Strategy)
|
||||
|
||||
针对 TLS 跨线程丢失的问题:
|
||||
|
||||
|**选项**|**A. 手动拷贝 (Manual Copy)**|**B. 智能闭包捕获 (Smart Closure Capture) (推荐)**|
|
||||
|---|---|---|
|
||||
|**机制**|在 `publishAsync` 前手动取出 ID,在回调里手动设置。|封装 `EventBus` 的任务包装器,**在入队瞬间自动捕获 TLS,在执行瞬间自动恢复 TLS**。|
|
||||
|**可靠性**|**低**。开发者容易忘,导致断链。|**高**。由基础设施层保证,业务无感。|
|
||||
|**复杂度**|低。|中。需要编写通用的任务包装模板。|
|
||||
|
||||
---
|
||||
|
||||
## 三、 基线确立与实施规范
|
||||
|
||||
为了实现“高性能”与“全链路无感”,我们确立 **C. 线程本地存储 (TLS) + B. 智能闭包捕获** 为技术基线。
|
||||
|
||||
### 1. 核心数据结构基线
|
||||
|
||||
- **`TraceId` 类型**:使用 `uint64_t` 或 `uuid`(推荐 64 位整数配合 SnowFlake 算法,追求极致性能)。
|
||||
- **`BaseEvent` 接口**:所有控制面事件必须继承此基类。
|
||||
|
||||
```cpp
|
||||
struct BaseEvent {
|
||||
uint64_t trace_id; // 事件携带的“信封”
|
||||
uint64_t timestamp;
|
||||
|
||||
BaseEvent() {
|
||||
// 构造时自动从当前线程 TLS 捕获 TraceID
|
||||
// 如果当前是根源(无 ID),则保持 0 或生成新 ID(视策略而定)
|
||||
trace_id = TraceContext::getCurrentId();
|
||||
timestamp = CurrentTimeMillis();
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### 2. 上下文管理基线 (RAII + TLS)
|
||||
|
||||
我们定义一个静态辅助类 `TraceContext` 和一个 RAII 守卫 `TraceContextGuard`。
|
||||
|
||||
- **`TraceContext` (TLS 管理)**
|
||||
|
||||
```cpp
|
||||
class TraceContext {
|
||||
public:
|
||||
static void set(uint64_t id) { current_trace_id_ = id; }
|
||||
static uint64_t get() { return current_trace_id_; }
|
||||
static void clear() { current_trace_id_ = 0; }
|
||||
|
||||
// 生成一个新的全局唯一 ID
|
||||
static uint64_t generateNew();
|
||||
|
||||
private:
|
||||
// 核心:每个线程独立一份,无锁,极速
|
||||
static thread_local uint64_t current_trace_id_;
|
||||
};
|
||||
```
|
||||
|
||||
- **`TraceContextGuard` (RAII 自动恢复)**
|
||||
- **作用**:在作用域结束时自动还原之前的 ID,支持嵌套调用。
|
||||
- **场景**:用于事件处理函数入口,确保处理完事件后,线程状态复原,不污染后续逻辑。
|
||||
|
||||
### 3. EventBus 集成规范 (跨线程核心)
|
||||
|
||||
这是本节最关键的设计:**如何在 `publishAsync` 时“偷渡”上下文?**
|
||||
|
||||
- **基线实现逻辑**:
|
||||
|
||||
1. **Publish 时 (线程 A)**:`publishAsync` 函数内部,获取当前线程 A 的 `TraceContext::get()`。
|
||||
2. **入队时**:将取出的 `trace_id` 和用户的 `handler` 打包成一个 `WrappedTask`。
|
||||
3. **Execute 时 (线程 B)**:`WrappedTask` 被执行。它首先使用 `TraceContextGuard` 将线程 B 的 TLS 设置为保存的 `trace_id`,然后执行用户 `handler`,最后 RAII 自动清理。
|
||||
|
||||
- **伪代码范式**:
|
||||
|
||||
```cpp
|
||||
template <typename EventType>
|
||||
void IEventBus::publishAsync(const EventType& event) {
|
||||
// 1. 捕获上下文 (此时还在发送者线程)
|
||||
uint64_t context_id = event.trace_id;
|
||||
|
||||
// 2. 包装任务 (Lambda Capture)
|
||||
auto wrapped_task = [handler, event, context_id]() {
|
||||
// 3. 恢复上下文 (此时已在接收者线程)
|
||||
TraceContextGuard guard(context_id);
|
||||
|
||||
// 4. 执行业务逻辑 (此时日志库能读到正确的 TLS TraceID)
|
||||
handler(event);
|
||||
};
|
||||
|
||||
// 5. 推入队列
|
||||
work_queue_.push(wrapped_task);
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 日志集成规范
|
||||
|
||||
- **基线要求**:所有日志宏(如 `RADAR_INFO`)必须自动读取 `TraceContext::get()` 并打印。
|
||||
- **格式**:`[Time][Level][ThreadID][TraceID] Message`。
|
||||
- **效果**:业务代码只需写 `RADAR_INFO("Processing data")`,日志文件里会自动出现 `… [TraceID: 12345] Processing data`。
|
||||
|
||||
---
|
||||
|
||||
## 总结:2.3.2 基线图谱
|
||||
|
||||
|**维度**|**基线决策**|**关键技术点**|
|
||||
|---|---|---|
|
||||
|**存储**|**Thread Local Storage (TLS)**|`thread_local uint64_t`,无锁,纳秒级访问。|
|
||||
|**载体**|**`BaseEvent` 继承**|所有事件自动携带 `trace_id` 字段。|
|
||||
|**跨线程**|**智能闭包捕获 (Smart Capture)**|`EventBus` 在入队/出队时自动 Switch Context。|
|
||||
|**作用域**|**RAII Guard**|`TraceContextGuard` 保证作用域内的上下文一致性和退出后的还原。|
|
||||
|
||||
---
|
||||
|
||||
**下一步行动**:
|
||||
|
||||
我们已经搞定了“大喇叭(总线)”和“条形码(TraceID)”。接下来要定的是“握手暗号” —— **2.3.3 生命周期编排与状态同步协议**。
|
||||
|
||||
在这个系统中,模块启动不是简单的 `start()`,它涉及依赖检查、顺序控制和状态回执。比如,`SignalProcessor` 启动前必须确认 GPU 资源就绪,启动后必须告诉调度器“我好了”。
|
||||
|
||||
**提问**:您是否确认 **“TLS + 智能闭包捕获”** 的上下文传递基线?确认后我们将进入 2.3.3。
|
||||
@@ -0,0 +1,102 @@
|
||||
---
|
||||
tags: []
|
||||
date created: 星期五, 十一月 21日 2025, 2:27:11 下午
|
||||
date modified: 星期五, 十一月 21日 2025, 2:52:14 下午
|
||||
---
|
||||
|
||||
# 2.3.3 生命周期编排与状态同步协议 (Lifecycle Orchestration & State Synchronization)
|
||||
|
||||
遵循三阶段模型,我们深入探讨 **2.3.3 生命周期编排与状态同步协议 (Lifecycle Orchestration & State Synchronization)**。
|
||||
|
||||
这是控制平面的核心业务流程。如果说事件总线是“电话线”,那么本节我们要规定的是“通话规矩”:调度器(指挥官)如何下达开机命令,模块(士兵)如何反馈执行结果,以及如何确保全员步调一致。
|
||||
|
||||
## 一、 约束输入与对齐 (Constraints & Alignment)
|
||||
|
||||
基于 `05_任务调度器设计.md` 和前序基线,我们面临以下硬性约束:
|
||||
|
||||
1. **决策权集中**:`TaskScheduler` 是唯一的生命周期决策者。模块严禁擅自启动或停止,必须响应调度器的指令。
|
||||
2. **异步闭环**:由于模块的初始化(如 GPU 上下文创建、网络绑定)可能耗时较长(> 10ms),**严禁**在事件回调中阻塞执行。协议必须是 **“异步指令 -> 后台执行 -> 异步回执”** 的闭环模式。
|
||||
3. **依赖有序**:启动必须遵循 `DependencyGraph` 的拓扑正序,停止遵循逆序。
|
||||
4. **可观测性**:所有生命周期事件必须携带 `TraceID`,以便追踪“是谁触发了这次启动”。
|
||||
|
||||
---
|
||||
|
||||
## 二、 权衡分析与选项呈现 (Trade-off Matrix)
|
||||
|
||||
### 议题 1:指令交互模式 (Command Interaction Model)
|
||||
|
||||
|**选项**|**A. 同步调用 (Direct Method Call)**|**B. 异步事件 + 超时机制 (Async Event + Timeout) (推荐)**|
|
||||
|---|---|---|
|
||||
|**机制**|调度器直接调用 `module->start()`。|调度器发布 `StartModuleEvent`,启动定时器,等待 `ModuleRunningEvent`。|
|
||||
|**阻塞性**|**高**。如果模块 `start()` 卡死,调度器也会卡死,导致整个控制面瘫痪。|**无**。调度器发完指令就去处理别的(如响应心跳),不会被卡住。|
|
||||
|**超时处理**|困难。需要多线程强杀。|**简单**。定时器触发后,如果没收到回执,直接判定启动失败并回滚。|
|
||||
|**适用场景**|简单的函数库调用。|**分布式/微服务架构的标准解**(即使是进程内)。|
|
||||
|
||||
### 议题 2:状态同步与一致性 (State Consistency)
|
||||
|
||||
|**选项**|**A. 乐观信任 (Trust Event)**|**B. 双重确认 (Event + Query) (推荐)**|
|
||||
|---|---|---|
|
||||
|**机制**|调度器只根据收到的 `ModuleRunningEvent` 更新内部状态表。|调度器收到 Event 更新状态,**同时**定期(如每 1 秒)调用 `module->getState()` 核对。|
|
||||
|**风险**|**状态漂移**。如果 Event 丢失(极少见但可能),调度器会以为模块还在运行,实际上它可能已崩溃。|**健壮**。能自动修复“幽灵状态”,确保监控视图的真实性。|
|
||||
|**开销**|零。|低(原子变量读取)。|
|
||||
|
||||
---
|
||||
|
||||
## 三、 基线确立与实施规范
|
||||
|
||||
为了确保系统在无人值守环境下的绝对可靠性,我们确立 **B. 异步事件 + 超时机制** 和 **B. 双重确认** 为基线。
|
||||
|
||||
### 1. 核心事件定义基线
|
||||
|
||||
所有事件必须继承自 2.3.2 确立的 `BaseEvent` 以携带 `TraceID`。
|
||||
|
||||
- **指令事件 (Commands)** - 由调度器发布,模块订阅:
|
||||
- `StartModuleEvent { string module_name; Config config_patch; }`
|
||||
- `StopModuleEvent { string module_name; bool force; }`
|
||||
- `PauseModuleEvent { string module_name; }`
|
||||
- **回执事件 (Feedbacks)** - 由模块发布,调度器订阅:
|
||||
- `ModuleRunningEvent { string module_name; }`
|
||||
- `ModuleStoppedEvent { string module_name; }`
|
||||
- `ModuleFailedEvent { string module_name; ErrorCode code; }`
|
||||
|
||||
### 2. 握手协议时序基线 (Sequence Flow)
|
||||
|
||||
这是“启动一个模块”的标准操作流程(SOP):
|
||||
|
||||
1. **指令下发**:调度器发布 `StartModuleEvent(Target="SignalProcessor")`,并将模块状态标记为 `STARTING`。同时,**启动一个 5 秒(可配置)的看门狗定时器**。
|
||||
2. **异步执行**:`SignalProcessor` 收到事件,**不应在回调中直接干活**,而是将“启动任务”提交给自己的工作线程(或 `std::thread`),立即返回。这保证了调度器线程不被阻塞。
|
||||
3. **任务执行**:`SignalProcessor` 的工作线程执行 `cudaFree(0)`、分配内存池等耗时操作。
|
||||
4. **回执上报**:
|
||||
|
||||
- **成功**:发布 `ModuleRunningEvent`。
|
||||
- **失败**:发布 `ModuleFailedEvent`。
|
||||
|
||||
5. **闭环确认**:
|
||||
|
||||
- **正常**:调度器收到 `ModuleRunningEvent`,取消定时器,将状态标记为 `RUNNING`,并触发下一个依赖模块的启动。
|
||||
- **超时**:定时器先触发。调度器判定启动失败,发布 `StopModuleEvent(force=true)` 进行清理,并进入故障恢复流程。
|
||||
|
||||
### 3. 状态机一致性基线
|
||||
|
||||
- **双重账本**:
|
||||
- **账本 A (调度器侧)**:`ModuleRegistry` 中的状态表,用于决策。
|
||||
- **账本 B (模块侧)**:模块内部的 `std::atomic<State>`,用于执行。
|
||||
- **同步规则**:
|
||||
- **写操作**:必须通过“指令 - 回执”流程修改。
|
||||
- **读操作**:调度器每秒执行一次 `SystemHealthCheck`,对比 账本 A 和 账本 B。如果发现不一致(如调度器认为 `RUNNING` 但模块是 `STOPPED`),触发 `StateMismatchEvent` 告警,并以**模块侧(真实世界)** 为准进行状态修正(Self-Healing)。
|
||||
|
||||
---
|
||||
|
||||
## 总结与下一步行动
|
||||
|
||||
我们确立了 **2.3.3 生命周期编排与状态同步协议** 的基线:
|
||||
|
||||
1. **协议**:**全异步 + 超时看门狗**。
|
||||
2. **一致性**:**事件驱动更新 + 定期主动核对**。
|
||||
3. **依赖**:严格遵循 DAG 拓扑序。
|
||||
|
||||
下一步建议:
|
||||
|
||||
模块启动之后,难免会遇到运行时错误。这就涉及到 2.3.4 故障传播与恢复信令 (Fault Propagation & Recovery Signaling)。我们需要定义:当一个模块“挂了”的时候,它怎么“优雅地”告诉调度器?调度器又如何指挥其他模块进行“无感恢复”?
|
||||
|
||||
**提问**:您是否确认 **“异步指令 + 超时闭环”** 的生命周期协议基线?确认后我们将深入 2.3.4。
|
||||
@@ -0,0 +1,122 @@
|
||||
---
|
||||
tags: []
|
||||
date created: 星期五, 十一月 21日 2025, 3:14:02 下午
|
||||
date modified: 星期一, 十一月 24日 2025, 4:31:32 下午
|
||||
---
|
||||
|
||||
# 2.3.4 故障传播与恢复信令 (Fault Propagation & Recovery Signaling)
|
||||
|
||||
这是系统的“求救与自愈”机制。在 2.3.3 中,我们定义了正常的生老病死;而 2.3.4 则要定义当模块“意外暴毙”时,系统如何避免全面崩溃,并有序地重新站起来。
|
||||
|
||||
特别需要注意的是,根据您的 **附件:生产环境架构加固指南**,我们引入了父进程 Watchdog。因此,本节的故障处理必须划清界限:**逻辑故障归调度器管,进程崩溃归 Watchdog 管**。
|
||||
|
||||
## 一、 约束输入与对齐 (Constraints & Alignment)
|
||||
|
||||
基于设计文档,我们需对齐以下硬性约束:
|
||||
|
||||
1. **职责边界**:
|
||||
|
||||
- **模块职责**:严禁在内部无限重试致命错误(如 GPU ECC 错误),必须立即停止并上报。
|
||||
- **调度器职责**:作为唯一决策者,负责编排恢复流程。
|
||||
- **Watchdog 职责**:仅在整个进程 Segfault 或调度器死锁时介入。
|
||||
|
||||
2. **依赖感知**:恢复不能只重启故障模块,必须先暂停其上游的数据流,防止积压导致 OOM(内存溢出)。
|
||||
3. **防风暴**:必须集成熔断机制,防止一个持续故障的模块引发系统无限重启循环。
|
||||
|
||||
---
|
||||
|
||||
## 二、 权衡分析与选项呈现 (Trade-off Matrix)
|
||||
|
||||
### 议题 1:故障上报内容的标准化 (Fault Reporting Standard)
|
||||
|
||||
|**选项**|**A. 仅错误码 (Error Code Only)**|**B. 丰富上下文 (Rich Context) (推荐)**|
|
||||
|---|---|---|
|
||||
|**机制**|`ModuleFailedEvent { int code; }`|`ModuleFailedEvent { ErrorCode code; string reason; Snapshot state; TraceID trace; }`|
|
||||
|**诊断能力**|**低**。日志里只能看到 "Error 1001",无法知道当时 GPU 显存剩多少,或者正在处理哪一帧。|**高**。携带了案发现场的快照和 TraceID,能直接关联到导致崩溃的那条控制指令。|
|
||||
|**传输开销**|极低。|低(拷贝几个字符串和结构体)。|
|
||||
|**决策支持**|弱。调度器只能无脑重启。|**强**。调度器可根据 `reason` 决定是立即重启,还是降级运行。|
|
||||
|
||||
### 议题 2:恢复编排策略 (Recovery Orchestration)
|
||||
|
||||
|**选项**|**A. 激进重启 (Aggressive Restart)**|**B. 依赖感知流水线 (Dependency-Aware Pipeline) (推荐)**|
|
||||
|---|---|---|
|
||||
|**机制**|收到故障立即调用 `module->stop(); module->start();`。|`Pause Upstream` -> `Stop Faulty` -> `Start Faulty` -> `Resume Upstream`。|
|
||||
|**数据安全性**|**低**。上游模块还在疯狂推数据,故障模块重启期间,中间队列瞬间爆满,导致丢包或内存溢出。|**高**。先截断水源,再修水管,修好再放水。|
|
||||
|**复杂度**|低。|中。需要 `DependencyGraph` 支持。|
|
||||
|
||||
---
|
||||
|
||||
## 三、 基线确立与实施规范
|
||||
|
||||
为了实现“不仅能活过来,而且不丢数据”的智能恢复,我们确立 **B. 丰富上下文** 和 **B. 依赖感知流水线** 为基线。
|
||||
|
||||
### 1. 故障事件信封基线 (`ModuleFailedEvent`)
|
||||
|
||||
我们定义一个标准化的故障事件结构,作为所有模块的“遗言”。
|
||||
|
||||
```cpp
|
||||
struct ModuleFailedEvent : public BaseEvent {
|
||||
std::string module_name;
|
||||
ErrorCode error_code;
|
||||
bool is_hardware_fault; // 提示调度器:硬件坏了重启也没用,不如报警
|
||||
std::string debug_info; // 包含显存状态、队列深度等现场快照
|
||||
|
||||
// 构造时自动捕获 TraceID
|
||||
ModuleFailedEvent(string name, ErrorCode code, string info)
|
||||
: module_name(name), error_code(code), debug_info(info) {
|
||||
is_hardware_fault = IsHardwareError(code);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### 2. 恢复信令时序基线 (Recovery Signaling Sequence)
|
||||
|
||||
这是调度器收到 `ModuleFailedEvent` 后的标准**四步走**恢复协议。以 `SignalProcessor` (信号处理) 崩溃为例,其上游是 `DataReceiver` (数据接收)。
|
||||
|
||||
1. **止血 (Pause Upstream)**:
|
||||
|
||||
- 调度器发布 `PauseDataFlowEvent(target="DataReceiver")`。
|
||||
- `DataReceiver` 响应:停止向中间队列 `push` 数据,暂时丢弃新数据或存入 RingBuffer,并回复 `DataFlowPausedEvent`。
|
||||
|
||||
2. **清理 (Stop Faulty)**:
|
||||
|
||||
- 调度器发布 `StopModuleEvent(target="SignalProcessor", force=true)`。
|
||||
- `SignalProcessor` 响应:尝试释放 GPU 资源,清理线程。回复 `ModuleStoppedEvent`。
|
||||
|
||||
3. **重启 (Restart Faulty)**:
|
||||
|
||||
- 调度器发布 `StartModuleEvent(target="SignalProcessor")`。
|
||||
- `SignalProcessor` 响应:重新初始化 CUDA 上下文,申请内存。回复 `ModuleRunningEvent`。
|
||||
|
||||
4. **恢复 (Resume Upstream)**:
|
||||
|
||||
- 调度器发布 `ResumeDataFlowEvent(target="DataReceiver")`。
|
||||
- `DataReceiver` 响应:恢复向中间队列推送数据。系统恢复正常。
|
||||
|
||||
### 3. 熔断器集成基线 (Circuit Breaker)
|
||||
|
||||
为了防止“垂死挣扎”引发日志爆炸和 CPU 满载,必须在调度器侧实施熔断。
|
||||
|
||||
- **策略**:针对每个模块维护一个滑动窗口(如 60 秒)。
|
||||
- **阈值**:若窗口内收到 **3 次** `ModuleFailedEvent`。
|
||||
- **动作**:
|
||||
- 标记该模块状态为 `BROKEN`。
|
||||
- 不再执行上述恢复流程。
|
||||
- 发布 `SystemDegradedEvent`(系统降级)或 `SystemFailedEvent`(全系统停机)。
|
||||
- 记录一条 `FATAL` 日志,呼叫运维人员。
|
||||
|
||||
---
|
||||
|
||||
## 总结与下一步行动
|
||||
|
||||
我们确立了 **2.3.4 故障传播与恢复信令** 的基线:
|
||||
|
||||
1. **协议**:**丰富上下文上报**(带 TraceID 和现场快照)。
|
||||
2. **编排**:**依赖感知的四步恢复法**(Pause -> Stop -> Restart -> Resume)。
|
||||
3. **防护**:**三振出局**的熔断机制。
|
||||
|
||||
下一步建议:
|
||||
|
||||
系统能跑能修了,但在资源紧张(比如 GPU 满载)的时候,如何让重要任务(如显控界面渲染)优先运行?这就涉及到 2.3.5 资源仲裁与抢占式优先级控制。这是一个涉及“权力分配”的关键接口。
|
||||
|
||||
**提问**:您是否确认 **“依赖感知四步恢复 + 熔断器”** 的故障处理基线?确认后我们将深入 2.3.5。
|
||||
@@ -0,0 +1,130 @@
|
||||
---
|
||||
tags: []
|
||||
date created: 星期五, 十一月 21日 2025, 3:15:00 下午
|
||||
date modified: 星期一, 十一月 24日 2025, 4:31:51 下午
|
||||
---
|
||||
|
||||
# 2.3.5 系统负载保护与热节流控制 (System Load Protection & Thermal Throttling)
|
||||
|
||||
## 一、 约束输入与对齐 (Constraints & Alignment)
|
||||
|
||||
基于变更后的架构目标,我们需对齐以下硬性约束:
|
||||
|
||||
1. **触发源单一性**:仅响应**物理传感器告警**(温度、功耗)。显控 UI 操作不再作为触发源。
|
||||
2. **响应滞后性 (Hysteresis)**:热惯性是缓慢的。控制逻辑必须包含**迟滞(Hysteresis)**机制,防止在阈值附近频繁切换导致系统震荡(Flapping)。
|
||||
3. **执行确定性**:节流动作必须是**确定性的降速**(如每帧固定休眠),而非不确定的丢包。丢包应由上游的背压机制(Backpressure)自然处理,而不是由计算模块主动丢弃。
|
||||
|
||||
---
|
||||
|
||||
## 二、 权衡分析与选项呈现 (Trade-off Matrix)
|
||||
|
||||
### 议题 1:降级执行策略 (Throttling Execution)
|
||||
|
||||
|**选项**|**A. 丢弃任务 (Task Dropping)**|**B. 动态降频 (DVFS / Clock Gating)**|**C. 软件占空比控制 (Software Duty Cycle) (推荐)**|
|
||||
|---|---|---|---|
|
||||
|**机制**|直接丢弃输入的 `DataContext`,不进行计算。|调用驱动 API (`ixsmi`) 强制降低 GPU 时钟频率。|在主处理循环中插入 `sleep()`,人为制造流水线气泡。|
|
||||
|**热效应**|**极好**。GPU 完全空闲。|**好**。降低电压和频率。|**可控**。通过调整休眠时长精确控制负载率。|
|
||||
|**副作用**|**数据断层**。破坏目标跟踪的连续性。|**依赖驱动**。智铠 SDK 可能未开放用户态调频接口,且响应慢。|**延迟增加**。但数据流保持连续,仅吞吐下降。|
|
||||
|**适用性**|仅限 Level 3 (紧急停机)。|固件层保护(最后一道防线)。|**应用层首选**。通用、简单、不丢数据。|
|
||||
|
||||
### 议题 2:控制回路设计 (Control Loop)
|
||||
|
||||
|**选项**|**A. 简单阈值 (Simple Threshold)**|**B. 迟滞比较器 (Hysteresis Comparator) (推荐)**|
|
||||
|---|---|---|
|
||||
|**机制**|>90°C 降速,<90°C 恢复。|>90°C 降速 (Trigger),<80°C 恢复 (Release)。|
|
||||
|**稳定性**|**差**。会在 90°C 附近反复震荡,导致风扇啸叫和电流波动。|**高**。确保系统冷却到安全区间后才恢复全速。|
|
||||
|
||||
---
|
||||
|
||||
## 三、 基线确立与实施规范
|
||||
|
||||
为了在极端工况下保护硬件同时维持最低限度的业务连续性,我们确立 **C. 软件占空比控制** + **B. 迟滞比较器** 为基线。
|
||||
|
||||
### 1. 交互协议基线 (Protocol)
|
||||
|
||||
定义一套闭环的保护协议:
|
||||
|
||||
- **告警事件 (Alert)** - 由 `MonitoringModule` 发布:
|
||||
- `SystemOverloadEvent { Type: THERMAL | POWER; Level: WARNING | CRITICAL; Value: float; }`
|
||||
- _WARNING_: 接近阈值(如 85°C)。
|
||||
- _CRITICAL_: 超过阈值(如 95°C)。
|
||||
- **指令事件 (Command)** - 由 `ResourceCoordinator` 发布:
|
||||
- `SetComputeThrottleEvent { ThrottleLevel level; TraceID trace_id; }`
|
||||
|
||||
### 2. 节流分级定义 (Throttle Levels)
|
||||
|
||||
|**等级**|**定义 (Level)**|**行为规范 (Behavior)**|**目标负载 (GPU Usage)**|**触发场景**|
|
||||
|---|---|---|---|---|
|
||||
|**L0**|**NO_THROTTLE**|全速运行,无额外休眠。|100% (Max)|温度 < 80°C|
|
||||
|**L1**|**LIGHT**|每帧处理后休眠 **5ms**。|~80%|80°C < 温度 < 90°C|
|
||||
|**L2**|**HEAVY**|每帧处理后休眠 **20ms**。|~50%|温度 > 90°C|
|
||||
|**L3**|**SUSPEND**|**暂停计算**。停止从队列取数据(触发背压)。|0% (Idle)|温度 > 95°C (濒临宕机)|
|
||||
|
||||
### 3. 迟滞控制逻辑 (Hysteresis Logic)
|
||||
|
||||
- **位置**:`TaskScheduler` 中的 `ResourceCoordinator` 组件。
|
||||
- **逻辑**:
|
||||
|
||||
```cpp
|
||||
// 伪代码:迟滞状态机
|
||||
void onTemperatureUpdate(float temp) {
|
||||
static State state = NORMAL;
|
||||
|
||||
switch (state) {
|
||||
case NORMAL:
|
||||
if (temp > 90.0) { state = THRORRLE_L2; publish(LEVEL_2); }
|
||||
else if (temp > 85.0) { state = THRORRLE_L1; publish(LEVEL_1); }
|
||||
break;
|
||||
case THRORRLE_L1:
|
||||
if (temp > 90.0) { state = THRORRLE_L2; publish(LEVEL_2); }
|
||||
else if (temp < 75.0) { state = NORMAL; publish(LEVEL_0); } // 迟滞回落
|
||||
break;
|
||||
case THRORRLE_L2:
|
||||
if (temp < 80.0) { state = THRORRLE_L1; publish(LEVEL_1); } // 迟滞回落
|
||||
break;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 模块侧实现规范
|
||||
|
||||
- **组件**:在 `SignalProcessor` 中新增 `ThrottleController` 类。
|
||||
- **侵入点**:`ExecutionEngine` 的主循环末尾。
|
||||
- **实现**:
|
||||
|
||||
```cpp
|
||||
void ExecutionEngine::run() {
|
||||
while (running_) {
|
||||
// 1. 获取数据 & 2. 提交计算 (…原逻辑…)
|
||||
|
||||
// 3. 节流控制点 (新增)
|
||||
throttle_controller_.applyDelay();
|
||||
}
|
||||
}
|
||||
|
||||
void ThrottleController::applyDelay() {
|
||||
// 读取原子变量,避免锁竞争
|
||||
auto level = current_level_.load(std::memory_order_relaxed);
|
||||
if (level == L1) std::this_thread::sleep_for(5ms);
|
||||
else if (level == L2) std::this_thread::sleep_for(20ms);
|
||||
else if (level == L3) std::this_thread::sleep_for(100ms); // 轮询等待
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 总结与下一步行动
|
||||
|
||||
我们确立了 **2.3.5 系统负载保护** 的基线:
|
||||
|
||||
1. **机制**:**软件级休眠 (Duty Cycle)**,牺牲延迟换取降温。
|
||||
2. **策略**:**四级节流 + 迟滞控制**,防止系统震荡。
|
||||
3. **触发**:仅限 **物理过载**。
|
||||
|
||||
下一步建议:
|
||||
|
||||
控制面的“指挥”和“保护”都做好了。现在我们要解决最具挑战性的“动态调整”问题 —— 2.3.6 两阶段配置热更新协议。
|
||||
|
||||
比如,如何在雷达正在运转时,安全地把 CFAR 检测阈值从 15dB 改为 13dB,或者切换波束扫描策略,而不需要重启整个进程?这需要一套严密的事务协议。
|
||||
|
||||
**提问**:您是否确认 **“四级节流 + 迟滞控制”** 的保护基线?确认后我们将深入 2.3.6。
|
||||
@@ -0,0 +1,138 @@
|
||||
---
|
||||
tags: []
|
||||
date created: 星期五, 十一月 21日 2025, 3:40:21 下午
|
||||
date modified: 星期一, 十一月 24日 2025, 4:32:10 下午
|
||||
---
|
||||
|
||||
# 2.3.6 两阶段配置热更新协议 (Two-Phase Configuration Hot-Reload Protocol)
|
||||
|
||||
遵循三阶段模型,我们深入探讨 **2.3.6 两阶段配置热更新协议 (Two-Phase Configuration Hot-Reload Protocol)**。
|
||||
|
||||
这是控制平面的“精细手术”机制。在雷达系统运行过程中,动态调整参数(如 CFAR 阈值、波束扫描范围)是一项高风险操作。如果配置变更导致某些模块崩溃,或者不同模块对同一参数的理解不一致,系统就会陷入“脑裂”或瘫痪。
|
||||
|
||||
因此,我们必须引入类似数据库事务的 **两阶段提交 (2PC)** 机制,确保配置更新要么**全员成功**,要么**全员回滚**。
|
||||
|
||||
## 一、 约束输入与对齐 (Constraints & Alignment)
|
||||
|
||||
基于系统的高可靠性要求,我们需对齐以下硬性约束:
|
||||
|
||||
1. **事务原子性 (Atomicity)**:配置变更必须是一个原子操作。严禁出现“模块 A 用了新参数,模块 B 还在用旧参数”的混合状态。
|
||||
2. **非阻塞验证 (Non-blocking Validation)**:验证阶段不能阻塞业务数据流。模块应在后台检查新配置的合法性(如内存是否足够、参数是否越界)。
|
||||
3. **无锁切换 (Lock-free Switch)**:配置生效的瞬间(Commit)必须极其迅速,不能让读取配置的业务线程(如 `SignalProcessor` 的 Worker)发生锁竞争。
|
||||
|
||||
---
|
||||
|
||||
## 二、 权衡分析与选项呈现 (Trade-off Matrix)
|
||||
|
||||
### 议题 1:更新协议模型 (Update Protocol)
|
||||
|
||||
|**选项**|**A. 单阶段通知 (Fire-and-Forget)**|**B. 两阶段提交 (2PC / Prepare-Commit) (推荐)**|
|
||||
|---|---|---|
|
||||
|**机制**|`ConfigManager` 直接发布 `ConfigChangedEvent`。模块收到即生效。|**Phase 1**: `ValidateRequest` -> 收集投票。<br><br> <br><br>**Phase 2**: 全员通过 -> `CommitCommand`;否则 -> `Abort`。|
|
||||
|**风险**|**高**。如果新配置导致模块 A 崩溃,或者模块 B 拒绝接受(如参数越界),系统将处于部分更新的**不一致状态**,且无法自动回滚。|**低**。所有模块都有机会在生效前“一票否决”。确保了变更的安全性。|
|
||||
|**延迟**|低。|高(两轮 RTT)。但在控制面(秒级交互)完全可接受。|
|
||||
|**适用性**|日志级别调整等无关痛痒的配置。|**核心业务参数调整的标准解**。|
|
||||
|
||||
### 议题 2:配置数据访问模型 (Access Model)
|
||||
|
||||
|**选项**|**A. 互斥锁保护 (Mutex Lock)**|**B. 双缓冲/原子指针 (Double Buffering / RCU) (推荐)**|
|
||||
|---|---|---|
|
||||
|**机制**|业务线程每次读配置前加锁:`lock(); val = config.val; unlock();`|业务线程持有 `shared_ptr<Config>`。更新时,`ConfigManager` 原子替换全局指针指向新对象。|
|
||||
|**性能**|**差**。高频读取(如每秒 10k 数据包)会导致严重的锁竞争,甚至阻塞更新线程。|**极佳**。读取无锁(仅增加引用计数或直接解引用),写入原子替换。|
|
||||
|**一致性**|强一致。|**最终一致**。旧线程继续用旧配置跑完当前帧,新线程用新配置。天然隔离了事务。|
|
||||
|
||||
---
|
||||
|
||||
## 三、 基线确立与实施规范
|
||||
|
||||
为了保障运行时变更的绝对安全,我们确立 **B. 两阶段提交** + **B. 原子指针 (RCU 风格)** 为基线。
|
||||
|
||||
### 1. 交互协议时序基线 (Sequence)
|
||||
|
||||
定义三个核心事件:
|
||||
|
||||
- `ValidateConfigEvent { ConfigID id; ConfigPatch patch; TraceID trace_id; }`
|
||||
- `ConfigValidationResultEvent { ConfigID id; string module; bool success; string reason; }`
|
||||
- `CommitConfigEvent { ConfigID id; }`
|
||||
|
||||
**时序流程**:
|
||||
|
||||
1. **发起 (Prepare)**:`ConfigManager` 收到 API 网关的变更请求,发布 `ValidateConfigEvent`。
|
||||
2. **投票 (Vote)**:
|
||||
|
||||
- 各模块(`SignalProcessor`, `DataReceiver`)在后台校验新参数(例如:检查新 `threshold` 是否 > 0,检查显存是否够用)。
|
||||
- 模块发布 `ConfigValidationResultEvent` 反馈结果(Yes/No)。
|
||||
|
||||
3. **决策 (Decide)**:
|
||||
|
||||
- `ConfigManager` 收集回执。设定超时时间(如 1s)。
|
||||
- **若全票通过**:发布 `CommitConfigEvent`。
|
||||
- **若有反对或超时**:记录错误日志,向 API 网关返回失败,流程终止(不发布 Commit,系统保持原状)。
|
||||
|
||||
4. **提交 (Commit)**:
|
||||
|
||||
- 模块收到 `CommitConfigEvent`,执行原子指针替换 (`std::atomic_store`),新配置即刻生效。
|
||||
|
||||
### 2. 模块侧实现规范 (C++14 RCU Pattern)
|
||||
|
||||
这是实现“无锁热更新”的关键代码范式。
|
||||
|
||||
- **配置持有**:
|
||||
|
||||
```cpp
|
||||
// 模块内部
|
||||
std::shared_ptr<const ModuleConfig> current_config_;
|
||||
std::mutex config_mutex_; // 仅用于保护指针的替换操作,不保护读取
|
||||
```
|
||||
|
||||
- **业务读取 (Hot Path)**:
|
||||
|
||||
```cpp
|
||||
void processData() {
|
||||
// 1. 获取当前配置的快照 (引用计数+1)
|
||||
// 在这一帧处理期间,即使外部更新了,config_ptr 指向的内容也不会变,保证了单帧内的逻辑一致性
|
||||
std::shared_ptr<const ModuleConfig> config_ptr = std::atomic_load(¤t_config_);
|
||||
|
||||
// 2. 使用配置
|
||||
float threshold = config_ptr->cfar_threshold;
|
||||
// …
|
||||
} // 3. config_ptr 析构,旧配置引用计数-1。如果归零,自动释放内存。
|
||||
```
|
||||
|
||||
- **更新提交 (Cold Path)**:
|
||||
|
||||
```cpp
|
||||
void onCommitConfig(const CommitConfigEvent& event) {
|
||||
// 1. 构建新配置对象
|
||||
auto new_config = std::make_shared<ModuleConfig>(*current_config_);
|
||||
new_config->applyPatch(event.patch);
|
||||
|
||||
// 2. 原子替换
|
||||
std::atomic_store(¤t_config_, new_config);
|
||||
|
||||
// 3. 完成。旧配置对象会随着所有持有它的业务线程结束而被动释放。
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 异常处理基线
|
||||
|
||||
- **超时机制**:`ConfigManager` 在发起 `Validate` 后必须启动定时器。如果超时未收到所有模块的回执,视为**验证失败**。
|
||||
- **版本对齐**:事件中必须携带 `ConfigID`(版本号或哈希)。模块在 Commit 时必须校验 ID 是否匹配,防止乱序到达的旧指令覆盖新指令。
|
||||
|
||||
---
|
||||
|
||||
## 总结与下一步行动
|
||||
|
||||
我们确立了 **2.3.6 两阶段配置热更新** 的基线:
|
||||
|
||||
1. **协议**:**投票 - 提交 (2PC)**,拒绝部分更新。
|
||||
2. **实现**:**RCU (Read-Copy-Update)** 模式,读侧无锁,写侧原子替换。
|
||||
3. **安全**:**超时自动回滚**。
|
||||
|
||||
下一步建议:
|
||||
|
||||
至此,控制指令的通道全部打通。最后,我们需要关注“只读”的数据流 —— 2.3.7 性能指标遥测通道。
|
||||
|
||||
业务模块如何高频地(如每秒 100 次)上报性能指标(FPS、吞吐量),而完全不影响主业务线程的性能?
|
||||
|
||||
**提问**:您是否确认 **“2PC + RCU 无锁替换”** 的热更新基线?确认后我们将完成 2.3 节的最后一个议题 2.3.7。
|
||||
134
系统基座文件/2/2.3/2.3.7 性能指标遥测通道 (Performance Telemetry Channel).md
Normal file
134
系统基座文件/2/2.3/2.3.7 性能指标遥测通道 (Performance Telemetry Channel).md
Normal file
@@ -0,0 +1,134 @@
|
||||
---
|
||||
tags: []
|
||||
date created: 星期五, 十一月 21日 2025, 3:54:54 下午
|
||||
date modified: 星期一, 十一月 24日 2025, 4:32:30 下午
|
||||
---
|
||||
|
||||
# 2.3.7 性能指标遥测通道 (Performance Telemetry Channel)
|
||||
|
||||
遵循三阶段模型,我们深入探讨 **2.3.7 性能指标遥测通道 (Performance Telemetry Channel)**。
|
||||
|
||||
这是系统的“听诊器”。正如您所嘱咐,这个模块必须兼具**健壮性**(绝不因为发体检报告而把干活的人累死)和**丰富性**(体检报告不能只说“活着”,而要包含详细的心电图、血氧等指标)。
|
||||
|
||||
在雷达每秒处理数万脉冲的高压环境下,简单的“打印日志”或“直接发事件”都会瞬间压垮 CPU。我们必须采用**工业级遥测架构**。
|
||||
|
||||
## 一、 约束输入与对齐 (Constraints & Alignment)
|
||||
|
||||
基于高频实时系统的特性,我们需对齐以下硬性约束:
|
||||
|
||||
1. **零干扰 (Zero Interference)**:业务线程(如信号处理)更新指标时,必须是**无锁 (Lock-free)**、**无系统调用 (No Syscall)**、**无堆内存分配 (No Alloc)** 的。任何锁竞争都会导致处理抖动。
|
||||
2. **数据丰富性 (Richness)**:不仅需要简单的计数(Counter),还需要瞬时值(Gauge)和分布统计(Histogram,用于计算 P99 延迟)。
|
||||
3. **故障隔离 (Isolation)**:如果监控模块卡死或事件总线堵塞,业务线程必须能自动丢弃指标数据,绝不能被阻塞(Backpressure needs to be lossy for telemetry)。
|
||||
|
||||
---
|
||||
|
||||
## 二、 权衡分析与选项呈现 (Trade-off Matrix)
|
||||
|
||||
### 议题 1:数据上报模型 (Reporting Model)
|
||||
|
||||
|**选项**|**A. 立即推送 (Fire-and-Forget)**|**B. 线程本地聚合 + 定期刷新 (TLS Aggregation) (推荐)**|
|
||||
|---|---|---|
|
||||
|**机制**|每次 `counter++` 就 `publishAsync` 一个事件。|业务线程只更新本地变量 (`thread_local`)。后台定时器每秒收集一次并打包发送。|
|
||||
|**开销**|**极高**。每秒触发数万次事件总线入队操作,导致上下文切换风暴。|**极低**。热路径上仅是一次内存自增指令 (`INC`)。|
|
||||
|**实时性**|实时。|准实时(秒级延迟)。但在监控场景下完全可接受。|
|
||||
|**健壮性**|低。总线易过载。|**高**。将高频数据降频为低频快照。|
|
||||
|
||||
### 议题 2:统计数据结构 (Metric Data Structure)
|
||||
|
||||
|**选项**|**A. 简单原子变量 (Simple Atomic)**|**B. 多维带标签指标 (Tagged Multi-dimensional) (推荐)**|
|
||||
|---|---|---|
|
||||
|**机制**|全局 `std::atomic<int> g_packet_count;`|`Metrics::Counter("packet_recv", {{"channel", "1"}})->inc();`|
|
||||
|**丰富度**|**低**。只能看总数,无法区分通道、流或具体错误码。|**高**。支持维度下钻(Drill-down),能精确定位是哪个通道在丢包。|
|
||||
|**性能**|高。|需优化。若每次查找 Map 会慢,需结合 **句柄缓存 (Handle Caching)** 技术。|
|
||||
|
||||
---
|
||||
|
||||
## 三、 基线确立与实施规范
|
||||
|
||||
为了达成“既要马儿跑,又要马儿不吃草”的效果,我们确立 **B. TLS 聚合** + **B. 多维指标体系** 为基线。
|
||||
|
||||
### 1. 核心架构:双层缓冲遥测系统
|
||||
|
||||
这是一个**读写分离**的设计:
|
||||
|
||||
- **业务层 (Writer)**:只通过 `ThreadLocal` 句柄极速写入,无锁。
|
||||
- **收集层 (Collector)**:`MetricRegistry` 定期(如 1Hz)遍历所有线程的 TLS,执行原子快照(Snapshot),生成 `MetricsUpdateEvent`。
|
||||
|
||||
### 2. 丰富指标类型定义 (Rich Metric Types)
|
||||
|
||||
我们在 `TelemetryClient` 中提供三种核心原语:
|
||||
|
||||
1. **Counter (计数器)**:
|
||||
|
||||
- _用途_:累计吞吐量、错误总数。
|
||||
- _特性_:只增不减。
|
||||
- _实现_:`std::atomic<uint64_t>`。
|
||||
|
||||
2. **Gauge (仪表盘)**:
|
||||
|
||||
- _用途_:队列深度、内存占用、当前温度。
|
||||
- _特性_:可增可减,只关心瞬时值。
|
||||
- _实现_:`std::atomic<int64_t>` 或 `std::atomic<double>`。
|
||||
|
||||
3. **Histogram (直方图)**:
|
||||
|
||||
- _用途_:**P99 延迟**、Kernel 执行耗时分布。
|
||||
- _特性_:统计数据落入不同区间的次数。
|
||||
- _实现_:**固定分桶 (Fixed Buckets)**。
|
||||
- _健壮性设计_:严禁使用动态扩容的 `std::vector`。预分配一组原子计数器(如 `<1ms`, `1-5ms`, `5-10ms`, `>10ms`)。这避免了热路径上的内存分配。
|
||||
|
||||
### 3. 无锁高性能实现规范 (Hot-Path Optimization)
|
||||
|
||||
为了让业务代码写得爽且快,我们引入 **Static Handle** 模式。
|
||||
|
||||
- **业务代码示例**:
|
||||
|
||||
```cpp
|
||||
void SignalProcessor::processFrame() {
|
||||
// 1. 获取句柄 (仅第一次调用时有哈希查找开销,之后是极速指针访问)
|
||||
static auto* latency_hist = Telemetry::GetHistogram("proc_latency_us", {{"module", "dsp"}});
|
||||
static auto* packet_cnt = Telemetry::GetCounter("packets_processed");
|
||||
|
||||
auto start = Now();
|
||||
// … 业务逻辑 …
|
||||
auto duration = Now() - start;
|
||||
|
||||
// 2. 更新指标 (热路径:仅涉及原子操作,耗时 < 10ns)
|
||||
packet_cnt->inc();
|
||||
latency_hist->observe(duration);
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 遥测协议与健壮性保障
|
||||
|
||||
- **事件定义**:
|
||||
|
||||
```cpp
|
||||
struct MetricsUpdateEvent : public BaseEvent {
|
||||
// 使用扁平化 Map 传输快照,减少序列化开销
|
||||
// Key: "proc_latency_us{module=dsp,bucket=1-5ms}" -> Value: 1024
|
||||
std::unordered_map<std::string, double> metrics_snapshot;
|
||||
};
|
||||
```
|
||||
|
||||
- **收集与发送 (Collector Thread)**:
|
||||
- **频率**:默认 **1Hz**。
|
||||
- **兜底策略 (健壮性核心)**:
|
||||
- **TryLock**: 收集线程在抓取快照时,使用 `try_lock`。如果业务线程正在极其罕见地初始化指标(持有锁),收集线程**直接放弃本次采集**,而不是阻塞等待。宁可丢一个点,不可卡顿系统。
|
||||
- **Backpressure**: 发送 `MetricsUpdateEvent` 时,使用 `EventBus::publishAsync`。如果事件总线队列已满(监控模块处理不过来),**直接丢弃该事件**。监控数据允许有损,业务数据不行。
|
||||
|
||||
---
|
||||
|
||||
## 总结:2.3 章节最终基线图谱
|
||||
|
||||
至此,我们完成了 **2.3 内部控制平面通信接口** 的全方位设计。这套神经系统既有雷霆手段(同步抢占),又有细腻心思(全链路追踪),还有强健体魄(无锁遥测)。
|
||||
|
||||
|**接口领域**|**核心基线**|**关键技术点**|
|
||||
|---|---|---|
|
||||
|**2.3.1 总线架构**|**混合双通道 (Sync/Async)**|`publishSync` (指令) vs `publishAsync` (状态)|
|
||||
|**2.3.2 追踪传递**|**TLS + 智能闭包捕获**|`TraceContextGuard`, 跨线程自动传递|
|
||||
|**2.3.3 生命周期**|**异步指令 + 超时闭环**|`StartModuleEvent` -> `ModuleRunningEvent`|
|
||||
|**2.3.4 故障恢复**|**依赖感知四步法**|Pause -> Stop -> Restart -> Resume|
|
||||
|**2.3.5 资源保护**|**四级热节流 + 迟滞控制**|温度触发,软件占空比 (`sleep`) 降温|
|
||||
|**2.3.6 热更新**|**2PC + RCU**|投票 -> 提交,原子指针替换配置|
|
||||
|**2.3.7 性能遥测**|**TLS 聚合 + 定期快照**|`Static Handle` 缓存,无锁热路径,有损发送|
|
||||
@@ -0,0 +1,28 @@
|
||||
---
|
||||
tags: []
|
||||
aliases:
|
||||
- 2.4 外部目标数据分发协议 (External Target Data Distribution Protocol)
|
||||
date created: 星期一, 十一月 24日 2025, 11:40:08 晚上
|
||||
date modified: 星期一, 十一月 24日 2025, 11:40:45 晚上
|
||||
---
|
||||
|
||||
# 2.4 外部目标数据分发协议 (External Target Data Distribution Protocol)
|
||||
|
||||
- **覆盖范围**:定义核心处理服务器(通过 `DisplayController`)向外部独立显控终端分发高实时性业务数据(如航迹、点迹)的**网络通信契约**。鉴于显控端采用轻量级 2D 渲染,本协议不再包含针对 UI 交互的流控逻辑,而是专注于**全速、单向、无阻塞**的数据推送,仅在接收到系统级热保护指令时执行被动节流。
|
||||
- **2.4.1 传输层拓扑与套接字模型 (Transport Layer Topology & Socket Model)**
|
||||
- **核心指向**:定义数据传输的物理载体。采用 **UDP 单播 (Unicast)** 模式,由服务器作为发送方,向单一客户端推送。强制使用 **非阻塞 (Non-blocking) Socket** 配合 `epoll` 边缘触发模式。鉴于已移除 UI 抢占逻辑,Socket 发送缓冲区 (`SO_SNDBUF`) 应配置为**最大可用值**(如 8MB+),以吸收网络抖动,确保在计算核心全速运转时网络层不成为瓶颈。
|
||||
- **2.4.2 业务数据序列化规范 (Business Data Serialization Specification)**
|
||||
- **核心指向**:定义跨网络二进制格式。继续强制使用 **Google Protobuf (v3)**。数据包根对象 `TrackDataBatch` 必须包含**全链路追踪 ID (`TraceID`)**。由于取消了任务切分,数据包的生成频率将与雷达脉冲处理周期(CPI)严格同步,不再出现因被抢占而导致的“微批次(Micro-batch)”碎片化数据包。
|
||||
- **2.4.3 丢包检测与时序完整性机制 (Packet Loss Detection & Sequencing Integrity)**
|
||||
- **核心指向**:定义数据一致性策略。协议头包含单调递增的 **`batch_sequence_id`**。客户端对于乱序包执行**立即丢弃**策略。由于后端不再因 UI 操作而暂停,客户端应预期收到**极其平稳**的数据流;任何超过 2 个周期的静默都应被客户端判定为“网络故障”而非“后端繁忙”,并触发重连告警。
|
||||
- **2.4.4 热节流响应与流量整形 (Thermal Throttling Response & Traffic Shaping)**
|
||||
- **核心指向**:**(基于 ECN 修正)** 定义在系统过热时的降级行为。当 `DisplayController` 收到 `SetComputeThrottleEvent`(热保护指令)时,必须在网络发送层执行**主动丢包**或**发送间隔插入(Gap Insertion)**,以减少网卡中断和总线功耗。例如,在 `Level 2` 节流状态下,仅发送关键航迹数据(Confirmed Tracks),丢弃所有点迹(Plots)和调试数据,从而降低系统整体热负荷。
|
||||
- **2.4.5 端到端延迟遥测 (End-to-End Latency Telemetry)**
|
||||
- **核心指向**:定义性能监控闭环。数据包必须携带 **“数据生成时间戳”**。客户端计算 **Glass-to-Glass Latency** 并回传。此指标现在主要用于监控网络链路质量和散热系统的有效性(即观察热节流是否导致了延迟显著增加),而非用于调节 UI 渲染优先级。
|
||||
|
||||
---
|
||||
|
||||
**变更说明 (基于 ECN-2025-001):**
|
||||
1. **移除**:移除了所有关于“为了 UI 响应性而暂停数据发送”的描述。
|
||||
2. **新增**:**2.4.4 热节流响应**。这是新架构下唯一合法的“主动降速”场景。
|
||||
3. **调整**:在 **2.4.1** 中强调了 Socket 缓冲区配置为“最大值”,因为不再需要担心缓冲区积压影响 UI 线程(UI 线程已与计算/发送线程物理解耦且互不干扰)。
|
||||
@@ -0,0 +1,98 @@
|
||||
---
|
||||
tags: []
|
||||
date created: 星期一, 十一月 24日 2025, 4:14:06 下午
|
||||
date modified: 星期一, 十一月 24日 2025, 4:48:23 下午
|
||||
---
|
||||
|
||||
# 2.4.1 传输层拓扑与套接字模型 (Transport Layer Topology & Socket Model)
|
||||
|
||||
## 1. 传输拓扑:UDP 单播直连 (UDP Unicast Direct)
|
||||
|
||||
- **基线决策**:采用 **UDP 单播 (Unicast)**,由 `DisplayController` 绑定本地网卡,直接向配置的显控终端 IP 发送数据。
|
||||
- **设计论证**:
|
||||
- **去中心化**:避免了组播(Multicast)对交换机 IGMP Snooping 配置的依赖,降低部署复杂度。
|
||||
- **无状态**:相比 TCP,UDP 无需维护连接状态(三次握手/四次挥手),消除 `TIME_WAIT` 资源占用,最适合“发后即忘(Fire-and-Forget)”的遥测场景。
|
||||
- **隔离性**:点对点发送天然隔离了网络风暴,若存在多个显控终端,建议通过应用层复制分发(Application-Layer Fanout),而非网络层组播,以实现对不同终端的差异化流控(例如:给指挥屏发全量数据,给监控屏发降级数据)。
|
||||
|
||||
## 2. 套接字模型:非阻塞 + 边缘触发 (Non-blocking + Epoll ET)
|
||||
|
||||
为了支撑高频率(如 100Hz+)的数据刷新而不阻塞业务线程,必须采用全异步 I/O 模型。
|
||||
|
||||
- **模式基线**:**Non-blocking Socket** + **Epoll Edge Triggered (ET)**。
|
||||
- **核心策略:“写优先,等其次” (Write-First, Wait-Later)**
|
||||
- **传统误区**:很多设计是先 `epoll_wait` 等到 `EPOLLOUT` 再写。这在高吞吐场景下是低效的,因为 99% 的时间网络都是可写的,无谓的 `epoll_wait` 增加了系统调用开销。
|
||||
- **基线逻辑**:
|
||||
|
||||
1. **直接发送**:业务线程或发送线程直接调用 `sendto()`。
|
||||
2. **处理拥塞**:仅当 `sendto()` 返回 `EAGAIN` / `EWOULDBLOCK`(内核缓冲区满)时,才将 Socket 加入 `epoll` 监听 `EPOLLOUT` 事件。
|
||||
3. **恢复发送**:当 `epoll` 通知可写时,继续发送剩余数据,并立即移除监听,回归“直接发送”模式。
|
||||
|
||||
## 3. 关键配置参数深度解析 (Parameter Tuning)
|
||||
|
||||
这里我们不硬定“8MB”,而是给出**基于场景的计算逻辑**。
|
||||
|
||||
### 3.1 发送缓冲区 (`SO_SNDBUF`):突发吸收器
|
||||
|
||||
- **设计目标**:吸收 **微突发 (Micro-burst)**。雷达数据通常是脉冲式的,一个 CPI 处理完会瞬间产生大量点迹/航迹数据。Socket 缓冲区必须足够大,能完全容纳“最大可能的单次突发数据量”,让网卡以物理线速慢慢发出去,而不是让应用程序阻塞。
|
||||
- 计算公式:
|
||||
|
||||
$$
|
||||
\text{Target Buffer} = \text{Max\_Burst\_Size} \times \text{Safety\_Factor}$$
|
||||
|
||||
- _Max_Burst_Size_:假设最大工况下,一帧(CPI)产生的点迹 + 航迹 + 回波切片总大小。例如:1000 个点迹 (x 64B) + 200 航迹 (x 128B) + 回波切片 (256KB) $\approx$ 350KB。
|
||||
- _Safety_Factor_:考虑到 OS 调度抖动,可能连续积压 2-3 帧数据。取系数 4。
|
||||
- _计算结果_:$350\text{KB} \times 4 \approx 1.4\text{MB}$。
|
||||
|
||||
- **配置建议**:
|
||||
- **基线值**:动态计算或设置为 **4MB - 8MB**(留有充足余量)。对于现代服务器内存而言,这个开销极低,但收益(零阻塞)巨大。
|
||||
- **实施**:`setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &size, …)`。注意 Linux 内核会将设置值翻倍以用于元数据,实际可用空间即为设定值。
|
||||
|
||||
### 3.2 服务质量标记 (`IP_TOS` / DSCP):网络特权
|
||||
|
||||
- **设计目标**:告诉交换机和路由器,这股数据流是“VIP”,在网络拥塞时优先转发,最后丢弃。
|
||||
- **参数选择**:
|
||||
- **DSCP (Differentiated Services Code Point)**:推荐使用 **EF (Expedited Forwarding, 0x2E)** 或 **CS6 (0x30)**。这是仅次于网络控制信令(CS7)的最高优先级,通常用于 VoIP 语音或实时控制数据。
|
||||
- **实施**:
|
||||
|
||||
```cpp
|
||||
int tos = 0xB8; // DSCP EF (101110) << 2
|
||||
setsockopt(fd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos));
|
||||
```
|
||||
|
||||
### 3.3 路径 MTU 发现 (PMTU Discovery):避免分片
|
||||
|
||||
- **现状**:虽然采集链路用了 JUMBO Frame (9000),但**显控链路通常经过普通交换机或公网,MTU 往往限制在 1500**。
|
||||
- **策略**:
|
||||
- **强制禁止分片 (`IP_MTU_DISCOVER` = `IP_PMTUDISC_DO`)**:让内核在数据包超过 MTU 时直接报错 `EMSGSIZE`,而不是默默分片(IP 分片会严重降低 UDP 可靠性,一个分片丢失导致整个包作废)。
|
||||
- **应用层分包**:`DisplayController` 必须根据实际 MTU(如 1472 字节 Payload)在应用层进行切片(Fragmentation)。这与我们后续 2.4.2 的“Atomic Batch”设计不冲突,一个 Batch 可以被切成多个 UDP 包发送,只要包含相同的 `BatchID` 和 `SequenceID` 即可。
|
||||
|
||||
## 4. 高效线程模型 (The "Pumper" Thread)
|
||||
|
||||
为了彻底隔离网络抖动对计算核心的影响,我们确立 **独立发送线程** 模型。
|
||||
|
||||
- **架构**:
|
||||
- **生产者**:`SignalProcessor` 计算完毕,将 `DetectionResult` 对象放入一个 **SPSC (单生产单消费) 无锁队列**。
|
||||
- **消费者 (DisplayController IO Thread)**:
|
||||
- 绑定到非关键 CPU 核心(如 Core 14)。
|
||||
- 死循环从队列取数据。
|
||||
- 执行 Protobuf 序列化。
|
||||
- 执行 `sendto()`。
|
||||
- 若 `EAGAIN`,则 `epoll_wait()`。
|
||||
- **收益**:
|
||||
- **序列化卸载**:Protobuf 序列化是 CPU 密集型操作,将其移出信号处理主线程,保护了核心计算资源。
|
||||
- **故障隔离**:即使网络断开、Socket 缓冲区塞满,影响的也只是这个 IO 线程(队列满后丢弃新数据),绝不会阻塞 FFT/CFAR 运算。
|
||||
|
||||
---
|
||||
|
||||
## 总结:2.4.1 基线图谱
|
||||
|
||||
|**配置项**|**核心基线**|**设计意图**|
|
||||
|---|---|---|
|
||||
|**拓扑**|**UDP Unicast**|简单、高效、无状态。|
|
||||
|**I/O 模型**|**Non-blocking + Epoll ET**|" 写优先 " 策略,最大化吞吐,最小化系统调用。|
|
||||
|**发送缓冲**|**动态计算 (Max Burst x 4)**|约 4MB-8MB。吸收脉冲式突发,防止应用层阻塞。|
|
||||
|**QoS**|**DSCP EF / CS6**|确保在交换机侧的转发优先级。|
|
||||
|**分片策略**|**禁止 IP 分片 + 应用层切分**|避免 IP 分片重组风险,提升丢包恢复能力。|
|
||||
|**线程模型**|**独立 IO 线程 + SPSC 队列**|隔离网络抖动与序列化开销,保护计算核心。|
|
||||
|
||||
**提问**:您是否确认 **“非阻塞 +Epoll ET”** 模型以及 **“独立线程序列化与发送”** 的架构基线?确认后我们将进入 **2.4.2 业务数据序列化规范**,讨论数据具体怎么“打包”。
|
||||
@@ -0,0 +1,142 @@
|
||||
---
|
||||
tags: []
|
||||
date created: 星期一, 十一月 24日 2025, 4:21:43 下午
|
||||
date modified: 星期一, 十一月 24日 2025, 4:38:46 下午
|
||||
---
|
||||
|
||||
# 2.4.2 业务数据序列化规范 (Business Data Serialization Specification)
|
||||
|
||||
## 1. 技术选型基线:Protocol Buffers V3
|
||||
|
||||
- **基线决策**:继续沿用 **Google Protobuf v3**。
|
||||
- **论证**:Protobuf 提供的 **Schema Evolution(模式演进)** 能力对于雷达这种生命周期长达数年的系统至关重要。它允许我们在未来添加新的字段(如“目标分类置信度”或“干扰源类型”)而不破坏旧版显控软件的兼容性。
|
||||
|
||||
## 2. 根消息结构定义 (Root Schema Definition)
|
||||
|
||||
基于 **TDP-2.4-DIST**(分布式补丁)和 **ECN-2025-001**(显控扁平化),我们需要对 `04_序列化与网络协议.md` 中定义的 `TrackDataBatch` 进行**重大升级**。
|
||||
|
||||
以下是最终确立的 `.proto` 契约:
|
||||
|
||||
```Protocol Buffers
|
||||
syntax = "proto3";
|
||||
package radar.ipc;
|
||||
|
||||
// 2.4.2 核心契约:原子业务数据批次
|
||||
message TrackDataBatch {
|
||||
// --- 传输层元数据 ---
|
||||
|
||||
// [分布式核心] 站点/阵面唯一标识 (Station ID)
|
||||
// 必须由配置管理模块注入,用于显控端区分数据源
|
||||
uint32 station_id = 1;
|
||||
|
||||
// [丢包检测] 批次序列号 (单调递增)
|
||||
// 仅在单个 Station 内连续。显控端需按 StationID 分别统计丢包率。
|
||||
uint64 batch_sequence_id = 2;
|
||||
|
||||
// [完整性] 全链路校验和 (CRC32c)
|
||||
// 覆盖除本字段外的所有数据。
|
||||
uint32 checksum = 3;
|
||||
|
||||
// --- 业务层元数据 ---
|
||||
|
||||
// [时序基准] 数据生成时间戳 (UTC Microseconds)
|
||||
// 必须基于总控授时校正后的软时钟,严禁使用本地 Uptime。
|
||||
// 显控端据此进行多站数据的时空对齐。
|
||||
uint64 timestamp_us = 4;
|
||||
|
||||
// [可观测性] 全链路追踪 ID
|
||||
// 继承自处理该 CPI 数据的 TraceContext
|
||||
uint64 trace_id = 5;
|
||||
|
||||
// [流控状态] 当前节流等级 (反馈给显控端)
|
||||
// 0=全速, 1=轻微, 2=严重。显控端可据此提示用户"当前数据已降级"。
|
||||
uint32 throttle_level = 6;
|
||||
|
||||
// --- 业务负载 (Atomic Payload) ---
|
||||
|
||||
// 确认航迹 (高价值,节流时优先保留)
|
||||
repeated TrackMessage tracks = 7;
|
||||
|
||||
// 原始点迹 (低价值,量大,节流时优先丢弃)
|
||||
repeated PlotMessage plots = 8;
|
||||
|
||||
// 系统状态/心跳信息 (可选,用于带外健康监测)
|
||||
SystemStatusMessage system_status = 9;
|
||||
}
|
||||
|
||||
// 单个航迹定义 (精简版,详细物理含义见 02_核心数据结构.md)
|
||||
message TrackMessage {
|
||||
uint64 track_id = 1;
|
||||
TrackStatus status = 2; // TENTATIVE, CONFIRMED, COAST, LOST
|
||||
|
||||
// 状态向量 (位置+速度)
|
||||
// 建议统一使用 WGS84 (经纬高) 或 地心笛卡尔坐标 (ECEF)
|
||||
// 以避免极坐标在分布式融合时的歧义
|
||||
double pos_x = 3;
|
||||
double pos_y = 4;
|
||||
double pos_z = 5;
|
||||
double vel_x = 6;
|
||||
double vel_y = 7;
|
||||
double vel_z = 8;
|
||||
|
||||
// 协方差矩阵 (可选,用于高级融合)
|
||||
repeated float covariance = 9;
|
||||
}
|
||||
```
|
||||
|
||||
## 3. 序列化策略:原子批次 (Atomic Batch) Vs 微批次 (Micro-batch)
|
||||
|
||||
- **旧设计遗留问题**:在之前的设计讨论中(未实施),为了配合“抢占式调度”,曾考虑将一个 CPI 的数据切碎发送。
|
||||
- **新基线 (ECN-2025-001)**:
|
||||
- **策略**:**原子批次 (Atomic Batch)**。
|
||||
- **定义**:一个 Protobuf `TrackDataBatch` 消息 **严格对应一个雷达处理周期 (CPI)** 的所有产出物。
|
||||
- **行为**:`DisplayController` 等待 `DataProcessor` 输出完整的航迹列表后,一次性打包。
|
||||
- **收益**:显控端逻辑大幅简化。收到一个包,就是一帧完整的态势图,无需处理“半帧”数据或进行复杂的跨包重组。
|
||||
|
||||
## 4. C++ 与 Protobuf 的映射效率优化
|
||||
|
||||
虽然 Protobuf 很快,但从 C++ `struct TrackData` 到 Protobuf `message TrackMessage` 的转换仍然涉及内存拷贝和遍历。为了在 100Hz+ 的刷新率下不成为瓶颈:
|
||||
|
||||
- **预分配 (Reserve)**:在构建 `TrackDataBatch` 时,根据 C++ `vector` 的大小,预先调用 Protobuf 的 `MutableRepeatedPtrField::Reserve()`,避免多次内存重分配。
|
||||
- **移动语义 (Move Semantics)**:Protobuf v3 在 C++ 中支持 `std::move`。如果序列化是在独立的 I/O 线程中进行(见 2.4.1),我们可以考虑直接接管数据所有权,但通常 Protobuf 生成的代码需要 Setter 拷贝。
|
||||
- _优化技巧_:对于包含大量数据的字段(如点迹列表),如果在 C++ 端也使用了类似 Protobuf 的内存布局(连续内存),可以使用 `Arena Allocation` 或自定义反射来加速,但在本系统中,**简单的字段赋值配合编译器优化(-O3)通常已足够,不必过度优化**。
|
||||
|
||||
## 5. 节流与数据剪裁 (Throttling & Pruning)
|
||||
|
||||
基于 **2.3.5 热节流控制**,序列化模块必须实现**内容感知剪裁**:
|
||||
|
||||
```cpp
|
||||
void DisplayController::serializeAndSend(const TrackDataPacket& packet) {
|
||||
radar::ipc::TrackDataBatch pb_batch;
|
||||
|
||||
// 1. 填充元数据 (StationID, TraceID, Timestamp…)
|
||||
|
||||
// 2. 获取当前节流等级 (通过原子变量读取)
|
||||
auto level = throttle_level_.load();
|
||||
pb_batch.set_throttle_level(level);
|
||||
|
||||
// 3. 内容剪裁逻辑
|
||||
if (level < 2) {
|
||||
// Level 0/1: 发送全量数据
|
||||
for (const auto& plot : packet.plots) { /* 转换点迹 */ }
|
||||
}
|
||||
|
||||
// 始终发送航迹 (除非 Level 3 暂停)
|
||||
for (const auto& track : packet.tracks) { /* 转换航迹 */ }
|
||||
|
||||
// 4. 序列化并发送
|
||||
// …
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 总结:2.4.2 基线图谱
|
||||
|
||||
| **规范项** | **核心基线** | **变更说明 (vs 旧文档)** |
|
||||
| ------- | ----------------------- | ------------------- |
|
||||
| **格式** | **Protobuf v3** | 保持不变。 |
|
||||
| **标识** | **StationID + TraceID** | 新增 StationID 支持分布式。 |
|
||||
| **时序** | **UTC Timestamp** | 取代本地时间,支持总控同步。 |
|
||||
| **粒度** | **Atomic CPI Batch** | 废弃微批次,简化接收端逻辑。 |
|
||||
| **流控** | **内容剪裁 (Pruning)** | 根据节流等级动态丢弃 Plot 字段。 |
|
||||
@@ -0,0 +1,120 @@
|
||||
---
|
||||
tags: []
|
||||
date created: 星期一, 十一月 24日 2025, 4:25:06 下午
|
||||
date modified: 星期一, 十一月 24日 2025, 4:29:22 下午
|
||||
---
|
||||
|
||||
# 2.4.3 丢包检测与时序完整性机制 (Packet Loss Detection & Sequencing Integrity)
|
||||
|
||||
这是显控终端的“网络听诊器”。在单向 UDP 传输模式下,显控端不仅是被动的数据消费者,更是链路质量的唯一评判者。它需要利用 2.4.2 定义的协议头字段,实时诊断出“谁丢包了”、“延迟多少”、“服务器是否过热”,并将这些隐性故障转化为显性的运维指标。
|
||||
|
||||
## 一、 约束输入与对齐 (Constraints & Alignment)
|
||||
|
||||
基于 TDP-2.4-DIST(分布式补丁)和 ECN-2025-001(显控扁平化),我们需对齐以下硬性约束:
|
||||
|
||||
1. **多源独立性**:丢包检测必须**按站点 (StationID) 隔离**。站点 A 的网络抖动绝不能触发站点 B 的告警。
|
||||
2. **时钟假设**:显控终端作为系统的一部分,假设已通过 NTP/PTP 与总控服务器同步。因此,`timestamp_us` 可用于计算绝对的端到端延迟(Glass-to-Glass Latency)。
|
||||
3. **处理策略**:显控端对于乱序包执行**立即丢弃**策略,以保证态势图的实时性。
|
||||
|
||||
---
|
||||
|
||||
## 二、 权衡分析与选项呈现 (Trade-off Matrix)
|
||||
|
||||
### 议题 1:丢包判决逻辑 (Loss Judgment Logic)
|
||||
|
||||
|**选项**|**A. 简单间隙检测 (Simple Gap)**|**B. 统计窗口平滑 (Sliding Window Stats) (推荐)**|
|
||||
|---|---|---|
|
||||
|**机制**|只要 `Seq != Last + 1` 就报警。|维护 1 秒滑动窗口。统计窗口内的丢包率 (PLR)。仅当 `PLR > 阈值` 时报警。|
|
||||
|**灵敏度**|**极高**。偶发的单个丢包也会导致界面闪烁告警,造成“狼来了”效应。|**适中**。过滤掉偶发的网络毛刺,关注持续性的链路质量恶化。|
|
||||
|**适用性**|调试模式。|**生产环境标准解**。|
|
||||
|
||||
### 议题 2:时序异常处理 (Timing Anomaly)
|
||||
|
||||
|**选项**|**A. 仅基于序列号 (Seq Only)**|**B. 序列号 + 时间戳双重校验 (Seq + Time) (推荐)**|
|
||||
|---|---|---|
|
||||
|**机制**|序列号递增即接收。|序列号递增 **且** `Timestamp > LastTimestamp`。|
|
||||
|**风险**|无法检测“服务器时钟回跳”或“重放攻击”。|**高健壮性**。防止因服务器端时钟故障导致的逻辑混乱。|
|
||||
|
||||
---
|
||||
|
||||
## 三、 基线确立与实施规范
|
||||
|
||||
为了构建一个“可诊断”的显控终端,我们确立 **B. 统计窗口平滑** + **B. 双重校验** 为基线。
|
||||
|
||||
### 1. 诊断上下文状态机 (Diagnostic Context)
|
||||
|
||||
显控端必须为**每一个**检测到的 `station_id` 维护一个独立的诊断上下文:
|
||||
|
||||
```cpp
|
||||
struct StationDiagnosticContext {
|
||||
uint32_t station_id;
|
||||
|
||||
// 序列跟踪
|
||||
uint64_t last_seq_id = 0;
|
||||
uint64_t last_timestamp_us = 0;
|
||||
|
||||
// 统计窗口 (1s)
|
||||
uint32_t expected_packets = 0;
|
||||
uint32_t lost_packets = 0;
|
||||
|
||||
// 链路状态
|
||||
bool connected = false;
|
||||
uint64_t last_seen_local_us = 0; // 用于检测静默/断连
|
||||
};
|
||||
```
|
||||
|
||||
### 2. 核心诊断逻辑基线
|
||||
|
||||
显控端收到 `TrackDataBatch` 后的标准处理流水线:
|
||||
|
||||
- **步骤 1:源识别**
|
||||
- 读取 `station_id`。
|
||||
- 在 Map 中查找对应的 `StationDiagnosticContext`。若无,则创建新上下文(视为新站点上线)。
|
||||
- **步骤 2:时序完整性检查 (Sequencing)**
|
||||
- **乱序/重复**:`if (batch.seq <= ctx.last_seq)` -> **立即丢弃**。记录 `Out-of-Order` 计数。
|
||||
- **丢包**:`if (batch.seq > ctx.last_seq + 1)` -> **判定为丢包**。
|
||||
- 丢包数 `loss = batch.seq - ctx.last_seq - 1`。
|
||||
- 更新统计:`ctx.lost_packets += loss`。
|
||||
- **更新**:`ctx.last_seq = batch.seq`。
|
||||
- **步骤 3:端到端延迟遥测 (Latency Telemetry)**
|
||||
- **计算**:`latency = Now() - batch.timestamp_us`。
|
||||
- **诊断**:
|
||||
- 若 `latency > 200ms`(典型阈值):标记为 **"High Latency"**。
|
||||
- 若 `latency < 0`:标记为 **"Clock Skew"**(时钟不同步警告)。
|
||||
- **步骤 4:服务器健康感知 (Server Health)**
|
||||
- 读取 `throttle_level`。
|
||||
- **可视化**:
|
||||
- `Level 0`:绿色(正常)。
|
||||
- `Level 1`:黄色(轻微发热)。显控端右上角显示小图标提示。
|
||||
- `Level 2`:红色(严重过热)。显控端弹出 Toast 提示“数据已降级”。
|
||||
|
||||
### 3. 断连判定机制 (Dead Link Detection)
|
||||
|
||||
- **机制**:显控端需运行一个独立的 1Hz 定时器,遍历所有 `StationDiagnosticContext`。
|
||||
- **判据**:`if (Now() - ctx.last_seen_local_us > 2000ms)`。
|
||||
- **动作**:
|
||||
- 将该 Station 标记为 **"Disconnected"**。
|
||||
- 在态势图上灰置或清除该站点的所有目标。
|
||||
- 触发声音告警。
|
||||
|
||||
---
|
||||
|
||||
## 总结:2.4.3 基线图谱
|
||||
|
||||
|**诊断维度**|**字段来源**|**判定逻辑**|**显控端行为**|
|
||||
|---|---|---|---|
|
||||
|**丢包 (Loss)**|`batch_sequence_id`|`Current > Last + 1`|统计丢包率,不补发,仅告警。|
|
||||
|**乱序 (Reorder)**|`batch_sequence_id`|`Current <= Last`|**静默丢弃**,防止画面回跳。|
|
||||
|**延迟 (Latency)**|`timestamp_us`|`LocalTime - Timestamp`|超过阈值(如 200ms)提示“数据滞后”。|
|
||||
|**断连 (Dead)**|(本地接收时间)|`Silence > 2s`|标记站点离线,清空目标。|
|
||||
|**过热 (Thermal)**|`throttle_level`|`Level > 0`|UI 显性提示用户“服务器过热/数据降级”。|
|
||||
|
||||
下一步行动:
|
||||
|
||||
至此,我们已经完整定义了数据如何发(2.4.1/2.4.2)以及如何收(2.4.3)。2.4.4 热节流响应 在之前的讨论中已经涵盖(作为 Server 端行为),2.4.5 的核心也在本节的延迟遥测中定义了。
|
||||
|
||||
**建议**:我们可以结束 **2.4 外部目标数据分发协议** 的讨论,进入 **2.5 数据结构定义与序列化规范**(虽然我们在 2.4.2 中已经定义了 Proto,但可能还需要补充 C++ 内部结构体定义),或者直接进入 **第三章:异构计算架构与资源调度**。
|
||||
|
||||
鉴于我们在 2.4.2 中已经非常详细地定义了 `.proto`,且 `02_核心数据结构.md` 已经提供了 C++ 结构体定义,我建议**跳过 2.5**(除非您有特定结构体需要深究),直接进入 **第三章**,去解决最硬核的计算资源分配问题。
|
||||
|
||||
**提问**:您是否确认 **2.4.3 的多站独立诊断基线**?确认后我们是否直接进入 **第三章**?
|
||||
@@ -0,0 +1,121 @@
|
||||
---
|
||||
tags: []
|
||||
aliases:
|
||||
- 2.4.4 热节流响应与流量整形 (Thermal Throttling Response & Traffic Shaping)
|
||||
date created: 星期一, 十一月 24日 2025, 4:33:19 下午
|
||||
date modified: 星期一, 十一月 24日 2025, 4:38:23 下午
|
||||
---
|
||||
|
||||
# 2.4.4 热节流响应与流量整形 (Thermal Throttling Response & Traffic Shaping)
|
||||
|
||||
这是数据网关的“被动防御机制”。不同于 2.3.5 中 `SignalProcessor` 的**主动降速**(通过 `sleep` 减少计算热量),`DisplayController` 在此处的任务是通过**减少数据发送量**和**降低发送频率**,来降低 CPU 的序列化开销、PCIe 总线功耗以及网卡的中断频率,从而辅助系统整体降温。
|
||||
|
||||
## 一、 约束输入与对齐 (Constraints & Alignment)
|
||||
|
||||
基于 ECN-2025-001 和之前的讨论,我们需对齐以下硬性约束:
|
||||
|
||||
1. **被动执行**:`DisplayController` 不感知温度,只响应 `SetComputeThrottleEvent` 指令。
|
||||
2. **原子性保持**:即使在节流状态下,发送出去的 `TrackDataBatch` 仍然必须是**原子批次**。严禁将一个 CPI 的数据拆成两半发送(例如发一半航迹),这会破坏显控端的一致性。
|
||||
3. **优先级明确**:
|
||||
|
||||
- **P0 (保命)**:确认航迹 (Confirmed Tracks)。这是雷达存在的意义。
|
||||
- **P1 (辅助)**:未确认航迹 (Tentative)、系统状态。
|
||||
- **P2 (冗余)**:点迹 (Plots)、调试日志、原始回波切片。
|
||||
|
||||
---
|
||||
|
||||
## 二、 权衡分析与选项呈现 (Trade-off Matrix)
|
||||
|
||||
### 议题:流量整形策略 (Shaping Strategy)
|
||||
|
||||
|**选项**|**A. 仅内容剪裁 (Content Pruning)**|**B. 仅频率抽稀 (Frame Skipping)**|**C. 混合降级 (Hybrid Degradation) (推荐)**|
|
||||
|---|---|---|---|
|
||||
|**机制**|保持 100Hz 发送频率,但把包里的点迹删掉,包变小了。|保持包内容完整,但每隔一帧发一帧(降为 50Hz)。|**轻度过热**时剪裁内容;**重度过热**时既剪裁内容又抽稀频率。|
|
||||
|**热收益**|**中**。减少了序列化开销和带宽,但网卡中断频率没变(依然是 100Hz)。|**高**。网卡中断减半,序列化开销减半。|**极高**。全方位降低负载。|
|
||||
|**显控体验**|**流畅但空洞**。画面依然丝滑,只是点迹没了。|**卡顿但丰富**。画面有顿挫感,但信息量全。|**平滑过渡**。优先保流畅,实在不行再保生存。|
|
||||
|
||||
---
|
||||
|
||||
## 三、 基线确立与实施规范
|
||||
|
||||
为了在“保护硬件”和“维持态势感知”之间取得最佳平衡,我们确立 **C. 混合降级** 为基线。
|
||||
|
||||
### 1. 节流等级映射表 (Throttling Mapping Table)
|
||||
|
||||
我们定义各级节流状态下,`DisplayController` 的具体行为规范:
|
||||
|
||||
|**节流等级 (Throttle Level)**|**发送频率 (Frequency)**|**内容策略 (Content Policy)**|**显控端表现**|
|
||||
|---|---|---|---|
|
||||
|**L0 (NO_THROTTLE)**|**100% (全速)**|**全量** (航迹 + 点迹 + 状态 + 调试)|正常显示。|
|
||||
|**L1 (LIGHT)**|**100% (全速)**|**剪裁 P2** (丢弃点迹、调试信息)。保留所有航迹。|画面流畅,但背景点迹消失。提示 "Data Pruned"。|
|
||||
|**L2 (HEAVY)**|**50% (二抽一)**|**剪裁 P1+P2** (仅保留**确认航迹**)。|画面帧率减半 (50Hz),仅显示核心目标。提示 "Low Rate"。|
|
||||
|**L3 (SUSPEND)**|**0% (暂停)**|**停止发送** (仅发心跳包维持连接)。|画面静止/黑屏。提示 "Server Overheat"。|
|
||||
|
||||
### 2. 实现规范:发送间隔插入 (Gap Insertion)
|
||||
|
||||
为了实现 L2 级别的降频,我们不能依赖上游 `SignalProcessor` 的降速(虽然它也在降,但我们要在网络层做双重保险)。
|
||||
|
||||
- **位置**:`DisplayController` 的独立 IO 线程循环中。
|
||||
- **逻辑**:维护一个 `frame_counter`。
|
||||
- **代码范式**:
|
||||
|
||||
```cpp
|
||||
void DisplayController::ioLoop() {
|
||||
uint64_t frame_counter = 0;
|
||||
|
||||
while (running_) {
|
||||
// 1. 从队列取数据
|
||||
TrackDataPacket packet;
|
||||
if (!queue_.pop(packet)) continue; // Non-blocking pop or wait
|
||||
|
||||
// 2. 获取当前节流等级 (原子读取,无锁)
|
||||
auto level = current_throttle_level_.load(std::memory_order_relaxed);
|
||||
|
||||
// 3. L3 级熔断:直接丢弃,仅维持心跳
|
||||
if (level == Level::SUSPEND) {
|
||||
sendHeartbeat();
|
||||
continue;
|
||||
}
|
||||
|
||||
// 4. L2 级抽稀:Gap Insertion (每2帧丢1帧)
|
||||
frame_counter++;
|
||||
if (level == Level::HEAVY && (frame_counter % 2 != 0)) {
|
||||
continue; // Drop this frame completely
|
||||
}
|
||||
|
||||
// 5. L1/L2 级内容剪裁 (在序列化前执行,节省CPU)
|
||||
if (level >= Level::LIGHT) {
|
||||
packet.plots.clear(); // 丢弃点迹
|
||||
packet.debug_info.clear();
|
||||
}
|
||||
if (level >= Level::HEAVY) {
|
||||
// 仅保留确认航迹
|
||||
packet.tracks.erase(
|
||||
std::remove_if(packet.tracks.begin(), packet.tracks.end(),
|
||||
[](const Track& t){ return t.status != CONFIRMED; }),
|
||||
packet.tracks.end());
|
||||
}
|
||||
|
||||
// 6. 序列化并发送 (Protobuf Encode -> sendto)
|
||||
serializeAndSend(packet);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 流量整形对丢包检测的影响 (2.4.3 联动)
|
||||
|
||||
- **问题**:如果在 L2 级别主动抽稀(丢弃偶数帧),显控端会不会误判为“丢包”?
|
||||
- **解决方案**:
|
||||
- **序列号连续性**:`batch_sequence_id` 是在**序列化步骤(第 6 步)**生成的。
|
||||
- **结论**:被主动丢弃的帧(第 4 步)根本不会获得序列号。因此,发送出去的数据包序列号**依然是连续的**。显控端不会误报丢包,只会感知到数据刷新变慢(两次更新的时间间隔变大)。这是非常优雅的设计。
|
||||
|
||||
---
|
||||
|
||||
## 总结:2.4.4 基线图谱
|
||||
|
||||
|**策略维度**|**核心基线**|**设计意图**|
|
||||
|---|---|---|
|
||||
|**响应模式**|**被动执行**|听从 `ResourceCoordinator` 指挥,不独立决策。|
|
||||
|**L1 策略**|**内容剪裁 (Pruning)**|牺牲次要数据,保流畅度,降序列化开销。|
|
||||
|**L2 策略**|**频率抽稀 (Skipping)**|牺牲刷新率,保核心数据,降中断和总线功耗。|
|
||||
|**序列号**|**后置生成**|确保主动丢弃不破坏传输层的序列连续性。|
|
||||
111
系统基座文件/2/2.4/2.4.5 端到端延迟遥测 (End-to-End Latency Telemetry).md
Normal file
111
系统基座文件/2/2.4/2.4.5 端到端延迟遥测 (End-to-End Latency Telemetry).md
Normal file
@@ -0,0 +1,111 @@
|
||||
---
|
||||
tags: []
|
||||
aliases:
|
||||
- 2.4.5 端到端延迟遥测 (End-to-End Latency Telemetry)
|
||||
date created: 星期一, 十一月 24日 2025, 4:49:40 下午
|
||||
date modified: 星期一, 十一月 24日 2025, 4:50:25 下午
|
||||
---
|
||||
|
||||
# 2.4.5 端到端延迟遥测 (End-to-End Latency Telemetry)
|
||||
|
||||
这是系统的“后视镜”。它不再用于实时的流控反馈(因为抢占逻辑已移除),而是作为核心的**运维监控指标 (Observability Metric)**。它回答了一个终极问题:“现在的网络和负载状况下,用户看到的数据到底是多久以前的?”
|
||||
|
||||
## 一、 约束输入与对齐 (Constraints & Alignment)
|
||||
|
||||
基于 TDP-2.4-DIST(分布式补丁)和之前确立的基线,我们需对齐以下硬性约束:
|
||||
|
||||
1. **时钟基准 (Time Reference)**:所有计算必须基于**统一的 UTC 时间**。服务器(发送端)和显控(接收端)均已通过总控服务器授时(NTP/PTP),误差假设在可控范围内(如 < 10ms)。
|
||||
2. **闭环路径 (Feedback Path)**:显控端计算出的延迟数据,需要回传给服务器端的 `MonitoringModule`,以便在统一的监控大屏上展示。
|
||||
3. **统计意义 (Statistical Significance)**:单帧的延迟没有意义(可能是网络抖动),我们关注的是 **P99 延迟** 和 **平均延迟** 的趋势。
|
||||
|
||||
---
|
||||
|
||||
## 二、 权衡分析与选项呈现 (Trade-off Matrix)
|
||||
|
||||
### 议题 1:回传通道选择 (Feedback Channel)
|
||||
|
||||
|**选项**|**A. 双向 UDP (Bidirectional UDP)**|**B. 带外 TCP/HTTP (Out-of-Band HTTP) (推荐)**|
|
||||
|---|---|---|
|
||||
|**机制**|显控端通过 UDP 向服务器的特定端口发回 `Ack/Stats` 包。|显控端调用服务器提供的 REST API 上报统计数据。|
|
||||
|**实时性**|**高**。|**低**(秒级)。|
|
||||
|**耦合度**|**高**。需要在 `DisplayController` 中增加接收逻辑,破坏了其“单向数据泵”的纯粹性。|**低**。直接复用 `API Gateway` 和 `MonitoringModule` 的现有接口。|
|
||||
|**适用性**|实时流控(已废弃)。|**长周期监控与健康分析**。|
|
||||
|
||||
### 议题 2:指标计算策略 (Calculation Strategy)
|
||||
|
||||
|**选项**|**A. 瞬时值上报 (Instant Reporting)**|**B. 客户端聚合 (Client-Side Aggregation) (推荐)**|
|
||||
|---|---|---|
|
||||
|**机制**|每收到一包数据,就计算 `Now - Ts` 并上报。|客户端维护 1 分钟的 Histogram,计算 P99/Avg,定期上报汇总值。|
|
||||
|**带宽开销**|**高**。回传流量巨大,浪费网络。|**极低**。每分钟仅几百字节。|
|
||||
|**价值**|包含噪声,难以分析。|**高价值**。直接反映一段时间内的服务质量 (QoS)。|
|
||||
|
||||
---
|
||||
|
||||
## 三、 基线确立与实施规范
|
||||
|
||||
为了构建一个闭环的监控体系,我们确立 **B. 带外 HTTP 回传** + **B. 客户端聚合** 为基线。
|
||||
|
||||
### 1. 核心指标定义 (Metrics Definition)
|
||||
|
||||
- **E2E Latency (Glass-to-Glass)**: $\text{Latency} = T_{Client\_Recv} - T_{Server\_Gen}$
|
||||
- $T_{Server\_Gen}$: `TrackDataBatch.timestamp_us` (UTC)。
|
||||
- $T_{Client\_Recv}$: 显控端收到 UDP 包时的本地系统时间 (UTC)。
|
||||
- **意义**:
|
||||
- **正常范围**:< 50ms (局域网) / < 200ms (广域网)。
|
||||
- **异常推断**:如果 Latency 突然飙升但没有丢包,说明发生了 **Bufferbloat (缓冲区膨胀)**,可能是 2.4.4 的节流机制介入过晚,或者网络设备队列堵塞。
|
||||
|
||||
### 2. 显控端实现规范 (Client-Side Implementation)
|
||||
|
||||
显控端应启动一个后台任务,负责统计和上报:
|
||||
|
||||
```cpp
|
||||
// 伪代码:客户端遥测聚合器
|
||||
class TelemetryAggregator {
|
||||
// 统计窗口数据 (1Hz 刷新)
|
||||
std::vector<uint64_t> latency_samples;
|
||||
uint64_t packet_loss_count = 0;
|
||||
|
||||
public:
|
||||
void onPacketReceived(const TrackDataBatch& batch) {
|
||||
uint64_t now = GetUtcTimeUs();
|
||||
int64_t latency = now - batch.timestamp_us;
|
||||
|
||||
// 过滤掉因为时钟不同步导致的负值
|
||||
if (latency > 0) latency_samples.push_back(latency);
|
||||
}
|
||||
|
||||
// 每 60 秒执行一次上报
|
||||
void reportLoop() {
|
||||
while (running) {
|
||||
sleep(60);
|
||||
|
||||
// 1. 计算统计值
|
||||
double avg = CalculateAvg(latency_samples);
|
||||
double p99 = CalculateP99(latency_samples);
|
||||
|
||||
// 2. 构建 JSON 报告
|
||||
json report = {
|
||||
{"station_id", station_id},
|
||||
{"latency_p99_ms", p99 / 1000.0},
|
||||
{"latency_avg_ms", avg / 1000.0},
|
||||
{"packet_loss_rate", CalculateLossRate()}
|
||||
};
|
||||
|
||||
// 3. 调用 API 上报 (复用 05_数据完整性与可靠性.md 定义的接口)
|
||||
HttpPost("http://server_ip:8080/api/v1/metrics/client", report);
|
||||
|
||||
// 4. 重置窗口
|
||||
latency_samples.clear();
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### 3. 服务端处理基线
|
||||
|
||||
- **接收者**:`API Gateway` 接收 HTTP POST 请求。
|
||||
- **路由**:转换为 `ClientMetricsUpdateEvent` 发布到事件总线。
|
||||
- **消费者**:`MonitoringModule` 订阅该事件,并将指标存储到时序数据库(如 Prometheus/InfluxDB),用于生成 Grafana 仪表盘。
|
||||
- **告警**:如果 `latency_p99 > 500ms` 持续 3 分钟,触发 **"Network Degradation"** 告警。
|
||||
|
||||
---
|
||||
29
系统基座文件/2/2.5/2.5 数据结构定义与序列化规范.md
Normal file
29
系统基座文件/2/2.5/2.5 数据结构定义与序列化规范.md
Normal file
@@ -0,0 +1,29 @@
|
||||
---
|
||||
tags: []
|
||||
aliases:
|
||||
- 2.5 数据结构定义与序列化规范 (Data Structure Definition & Serialization Specification)
|
||||
date created: 星期一, 十一月 24日 2025, 5:21:10 下午
|
||||
date modified: 星期一, 十一月 24日 2025, 5:21:21 下午
|
||||
---
|
||||
|
||||
# 2.5 数据结构定义与序列化规范 (Data Structure Definition & Serialization Specification)
|
||||
|
||||
- **覆盖范围**:定义系统内外部数据交互的**静态契约**。该规范严格区分 **“内部原生对象(In-Memory Native Objects)”** 与 **“外部传输契约(On-Wire Contracts)”**,并界定两者之间的**转换边界**。内部关注极致的计算性能(SIMD 对齐、零拷贝),外部关注跨语言/跨平台的互操作性(Protobuf)。
|
||||
- **2.5.1 内部高性能业务对象模型 (Internal High-Performance Business Object Model)**
|
||||
- **核心指向**:定义在 `DataReceiver` -> `SignalProcessor` -> `DataProcessor` 流水线中流转的 C++ 原生结构体(DTO)。涵盖 `DetectionResult`(点迹)和 `TrackData`(航迹)的内存布局设计,强制使用 **POD (Plain Old Data)** 类型,并应用 `alignas(16/32)` 以适配 **SIMD (AVX/NEON)** 向量化指令优化,严禁在核心计算路径上使用虚函数或复杂对象。
|
||||
- **2.5.2 内部控制事件模式定义 (Internal Control Event Schema Definition)**
|
||||
- **核心指向**:定义在 `EventBus` 上流转的控制信令结构。所有事件必须继承自 `BaseEvent`,并强制包含 **全链路追踪 ID (`TraceID`)** 和 **高精度时间戳**。事件负载(Payload)必须保持轻量(通常仅包含状态码、配置键值对或对象 ID),严禁携带大块业务数据(如 I/Q 波形),以保障控制平面的低延迟响应。
|
||||
- **2.5.3 外部数据交换契约 (External Data Exchange Contract)**
|
||||
- **核心指向**:定义系统向外部(显控终端、API 网关)输出数据的接口定义语言 (IDL)。强制选用 **Google Protobuf (v3)** 作为唯一标准。涵盖 `.proto` 文件的版本管理规范(语义化版本控制),以及字段的 **向前/向后兼容性** 设计原则(如使用 `optional` 字段,保留 `reserved` 标识符),确保前后端可独立演进。
|
||||
- **2.5.4 零拷贝数据容器规范 (Zero-Copy Data Container Specification)**
|
||||
- **核心指向**:定义承载内部业务对象的通用包装器 `DataPacket<T>`。涵盖其 **Header** 的标准化元数据(序列号、源模块、TraceID),以及 **Payload** 的所有权管理机制——必须使用 `std::unique_ptr` 配合 **自定义删除器 (Custom Deleter)**,以实现内存块在生命周期结束时的自动归还(回收到 `MemoryPool`),彻底消除内存泄漏风险。
|
||||
- **2.5.5 序列化边界与映射策略 (Serialization Boundary & Mapping Strategy)**
|
||||
- **核心指向**:定义“内部对象”转换为“外部格式”的**唯一合法位置**。明确规定 **仅在 `DisplayController`(数据网关)** 和 **`ApiCommandService`(API 响应)** 处进行序列化操作。涵盖从 C++ Struct 到 Protobuf Message 的字段映射逻辑(Mapping Logic),以及在边界处进行 **数据清洗与脱敏** 的安全规范。
|
||||
|
||||
---
|
||||
|
||||
**核心变更点解析:**
|
||||
|
||||
1. **分离定义**:明确将 2.5.1 (内部高性能 C++) 与 2.5.3 (外部 Protobuf) 分开。旧设计可能在内部也混用了序列化对象,这是性能杀手,新设计予以纠正。
|
||||
2. **容器规范**:新增 2.5.4,将 `DataPacket<T>` 和智能指针管理提升为核心规范,这是“零拷贝”落地的关键载体。
|
||||
3. **边界管控**:新增 2.5.5,显式定义“序列化边界”。这是架构师视角的管控点,防止开发人员在内部模块(如信号处理)随意进行序列化导致性能劣化。
|
||||
@@ -0,0 +1,170 @@
|
||||
---
|
||||
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 计算效率,确保数据进入算法时是“最可口”的形态。
|
||||
@@ -0,0 +1,177 @@
|
||||
---
|
||||
tags: []
|
||||
aliases:
|
||||
- 2.5.2 内部控制事件模式定义 (Internal Control Event Schema Definition)
|
||||
date created: 星期一, 十一月 24日 2025, 10:39:09 晚上
|
||||
date modified: 星期一, 十一月 24日 2025, 10:56:20 晚上
|
||||
---
|
||||
|
||||
# 2.5.2 内部控制事件模式定义 (Internal Control Event Schema Definition)
|
||||
|
||||
**基线核心宗旨**:**“Type-Safe & Traceable (类型安全与可追溯)”**。
|
||||
内部控制事件不跨越进程边界(不涉及 Protobuf 序列化),它们是纯粹的 C++ 运行时对象。设计重心在于利用 C++ 类型系统防止误用,并强制携带全链路追踪上下文。
|
||||
|
||||
-----
|
||||
|
||||
## 1\. 核心设计契约 (Design Contracts)
|
||||
|
||||
1. **强制继承 (Mandatory Inheritance)**:
|
||||
|
||||
- 所有在 `EventBus` 上流转的事件 **必须** 继承自 `BaseEvent`。
|
||||
- **理由**:确保所有事件天然携带 `TraceID` 和 `Timestamp`,实现无死角的审计与追踪。
|
||||
|
||||
2. **轻量级负载 (Lightweight Payload)**:
|
||||
|
||||
- **禁止**:在事件对象中直接嵌入大块内存(如 `vector<float>` 原始波形数据)。
|
||||
- **允许**:状态码、配置参数、资源句柄(智能指针)、元数据摘要。
|
||||
- **理由**:控制平面应保持低延迟。大块数据应通过数据面(DataQueue)流转,控制面仅传递“指向数据的指针”或“操作指令”。
|
||||
|
||||
3. **值语义与移动优化 (Value Semantics & Move Semantics)**:
|
||||
|
||||
- 事件对象默认按**值传递**(Copy/Move)。
|
||||
- 必须支持移动构造(Move Constructor),以便高效地在 `EventBus` 的异步队列中转移。
|
||||
|
||||
-----
|
||||
|
||||
## 2\. 根模式定义 (Root Schema)
|
||||
|
||||
这是所有控制信令的“父类契约”。
|
||||
|
||||
```cpp
|
||||
/**
|
||||
* @brief 所有内部控制事件的基类
|
||||
* @note 即使是空事件,大小也至少为 16 字节 (TraceID + Timestamp)
|
||||
*/
|
||||
struct BaseEvent {
|
||||
// --- 上下文核心 (Context Core) ---
|
||||
|
||||
// 全链路追踪 ID (从 TraceContext 自动捕获或继承)
|
||||
uint64_t trace_id;
|
||||
|
||||
// 事件生成时的精确时间 (UTC Microseconds)
|
||||
// 用于计算控制指令在队列中的排队延迟 (Queueing Latency)
|
||||
uint64_t timestamp_us;
|
||||
|
||||
// --- 构造基线 ---
|
||||
BaseEvent() {
|
||||
// 自动注入上下文,确保业务代码无需手动填写
|
||||
trace_id = TraceContext::getCurrentId();
|
||||
timestamp_us = Clock::nowInUs();
|
||||
}
|
||||
|
||||
virtual ~BaseEvent() = default; // 允许作为基类指针传递(如果需要统一日志处理)
|
||||
};
|
||||
```
|
||||
|
||||
-----
|
||||
|
||||
## 3\. 核心事件分类定义 (Concrete Schemas)
|
||||
|
||||
基于 **2.3 章节** 确立的各个控制子系统,我们定义以下四类核心事件。
|
||||
|
||||
### 3.1 生命周期与编排类 (Lifecycle & Orchestration)
|
||||
|
||||
服务于 **2.3.3** 和 **2.3.4**。这类事件对**可靠性**要求最高。
|
||||
|
||||
```cpp
|
||||
// 指令:启动模块
|
||||
struct StartModuleEvent : public BaseEvent {
|
||||
std::string target_module; // 目标模块名
|
||||
// 可选:携带启动参数或配置覆盖
|
||||
// std::map<string, string> overrides;
|
||||
};
|
||||
|
||||
// 回执:模块运行中
|
||||
struct ModuleRunningEvent : public BaseEvent {
|
||||
std::string module_name;
|
||||
};
|
||||
|
||||
// 告警:模块故障 (Rich Context from 2.3.4)
|
||||
struct ModuleFailedEvent : public BaseEvent {
|
||||
std::string module_name;
|
||||
ErrorCode error_code; // 标准化错误码
|
||||
bool is_hardware_fault; // 是否硬件故障 (决定是否熔断)
|
||||
std::string debug_info; // 现场快照 (堆栈、显存状态等)
|
||||
};
|
||||
```
|
||||
|
||||
### 3.2 资源保护与仲裁类 (Resource & Safety)
|
||||
|
||||
服务于 **2.3.5**。这类事件对**响应速度**要求最高。
|
||||
|
||||
```cpp
|
||||
// 告警:系统过载 (由监控模块发出)
|
||||
struct SystemOverloadEvent : public BaseEvent {
|
||||
enum class Type { THERMAL, POWER, MEMORY };
|
||||
enum class Level { WARNING, CRITICAL };
|
||||
|
||||
Type type;
|
||||
Level level;
|
||||
float current_value; // e.g., 95.5 (Celsius)
|
||||
};
|
||||
|
||||
// 指令:执行节流 (由调度器发出)
|
||||
struct SetComputeThrottleEvent : public BaseEvent {
|
||||
enum class Level { NO_THROTTLE, LIGHT, HEAVY, SUSPEND };
|
||||
Level level;
|
||||
// 接收方收到后,需立即调整 sleep 策略或丢包策略
|
||||
};
|
||||
```
|
||||
|
||||
### 3.3 配置热更新类 (Configuration Transaction)
|
||||
|
||||
服务于 **2.3.6**。这类事件需要承载**复杂负载**(配置数据)。
|
||||
|
||||
```cpp
|
||||
// 事务:配置变更补丁
|
||||
// 采用 RCU 模式,负载通过 shared_ptr 传递,避免深拷贝
|
||||
struct ValidateConfigEvent : public BaseEvent {
|
||||
uint64_t config_version;
|
||||
// 使用智能指针传递复杂的配置树,确保事件本身轻量
|
||||
std::shared_ptr<const ConfigPatch> patch;
|
||||
};
|
||||
|
||||
// 提交:确认变更
|
||||
struct CommitConfigEvent : public BaseEvent {
|
||||
uint64_t config_version; // 必须匹配 Validate 中的版本
|
||||
};
|
||||
```
|
||||
|
||||
### 3.4 性能遥测类 (Telemetry)
|
||||
|
||||
服务于 **2.3.7**。这类事件**吞吐量最大**,必须极致精简。
|
||||
|
||||
```cpp
|
||||
// 遥测:指标快照
|
||||
struct MetricsUpdateEvent : public BaseEvent {
|
||||
// 使用扁平化 Map 传输快照
|
||||
// Key 命名规范: "metric_name{tag=val,…}"
|
||||
std::unordered_map<std::string, double> metrics;
|
||||
|
||||
// 优化建议:
|
||||
// 如果 std::unordered_map 造成频繁 malloc,
|
||||
// 可考虑使用预分配的 vector<MetricPair> 或类似 SmallVector 的优化容器
|
||||
};
|
||||
```
|
||||
|
||||
-----
|
||||
|
||||
## 4\. 模式设计自检 (Design Checklist)
|
||||
|
||||
| 检查项 | 符合标准 | 设计意图 |
|
||||
| :--- | :--- | :--- |
|
||||
| **继承关系** | ✅ Yes | 所有事件均继承自 `BaseEvent`,保证 TraceID 存在。 |
|
||||
| **内存所有权** | ✅ Yes | 复杂对象(如 ConfigPatch)使用 `shared_ptr` 传递,避免总线拷贝大对象。 |
|
||||
| **类型安全** | ✅ Yes | 避免使用 `void*` 或 `EventId` 整数流,利用 C++ 类型系统进行分发。 |
|
||||
| **序列化无关** | ✅ Yes | 结构体中包含 `std::string` 等非 POD 类型,明确不直接用于网络传输(需经由 2.5.3 转换)。 |
|
||||
|
||||
-----
|
||||
|
||||
**总结**:
|
||||
**2.5.2** 确立了控制平面的“通用语言”。这套定义确保了控制指令在进程内传输时:
|
||||
|
||||
1. **可追踪**(自带 TraceID)。
|
||||
2. **低延迟**(避免大内存拷贝)。
|
||||
3. **强类型**(编译期检查错误)。
|
||||
|
||||
这为 2.3 节定义的所有控制逻辑提供了坚实的数据结构支撑。
|
||||
209
系统基座文件/2/2.5/2.5.3 外部数据交换契约 (External Data Exchange Contract).md
Normal file
209
系统基座文件/2/2.5/2.5.3 外部数据交换契约 (External Data Exchange Contract).md
Normal file
@@ -0,0 +1,209 @@
|
||||
---
|
||||
tags: []
|
||||
date created: 星期一, 十一月 24日 2025, 11:09:27 晚上
|
||||
date modified: 星期一, 十一月 24日 2025, 11:09:40 晚上
|
||||
---
|
||||
|
||||
# 2.5.3 外部数据交换契约 (External Data Exchange Contract)
|
||||
|
||||
**基线核心宗旨**:**“Contract First, Backwards Compatible (契约优先,向后兼容)”**。
|
||||
外部接口定义语言 (IDL) 一旦发布,即视为法律条文,严禁随意修改。任何变更必须遵循严格的版本控制和兼容性法则,以确保服务器升级不会导致显控终端崩溃。
|
||||
|
||||
-----
|
||||
|
||||
## 1\. 核心设计契约 (Design Contracts)
|
||||
|
||||
1. **唯一标准 (Single Standard)**:
|
||||
|
||||
- 强制使用 **Google Protocol Buffers v3 (proto3)**。
|
||||
- **理由**:proto3 移除了 `required` 字段,天然支持向后兼容(缺失字段使用默认值),极其适合迭代快速的分布式系统。
|
||||
|
||||
2. **包版本化 (Package Versioning)**:
|
||||
|
||||
- 所有 `.proto` 文件必须定义在带版本的包命名空间下,例如 `package radar.external.v1;`。
|
||||
- **理由**:当发生破坏性变更(Breaking Change)时,可以通过引入 `v2` 包来实现并存,而不是破坏现有 `v1` 客户端。
|
||||
|
||||
3. **显式类型 (Explicit Typing)**:
|
||||
|
||||
- 时间戳必须显式注明单位(后缀 `_us` / `_ms`)。
|
||||
- 枚举值(Enum)的第一个字段必须是 `UNKNOWN` 或 `UNSPECIFIED`(值为 0),作为默认零值安全网。
|
||||
|
||||
-----
|
||||
|
||||
## 2\. 协议文件组织 (File Organization)
|
||||
|
||||
为了规范管理,`.proto` 文件应按以下目录结构组织,并作为独立的构建目标(Target):
|
||||
|
||||
```text
|
||||
project_root/
|
||||
└── protos/
|
||||
└── radar/
|
||||
└── external/
|
||||
└── v1/
|
||||
├── common.proto # 共享基础类型 (Point, Vector3)
|
||||
├── track_data.proto # 核心业务数据 (Track, Plot, Batch)
|
||||
└── system_status.proto # 系统健康状态
|
||||
```
|
||||
|
||||
-----
|
||||
|
||||
## 3\. 核心模式定义基线 (Schema Baseline)
|
||||
|
||||
基于 **2.4.2** 确立的逻辑结构,将其固化为工程级的 Protobuf 定义。
|
||||
|
||||
### 3.1 基础类型定义 (`common.proto`)
|
||||
|
||||
```protobuf
|
||||
syntax = "proto3";
|
||||
package radar.external.v1;
|
||||
|
||||
// WGS84 地理坐标 (用于多站融合)
|
||||
message GeoPoint {
|
||||
double latitude = 1; // 纬度 (度)
|
||||
double longitude = 2; // 经度 (度)
|
||||
double altitude = 3; // 海拔 (米)
|
||||
}
|
||||
|
||||
// 笛卡尔状态向量 (用于高精度跟踪)
|
||||
message StateVector {
|
||||
// ECEF 或 站心坐标系,具体由配置约定
|
||||
double pos_x = 1;
|
||||
double pos_y = 2;
|
||||
double pos_z = 3;
|
||||
double vel_x = 4;
|
||||
double vel_y = 5;
|
||||
double vel_z = 6;
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 核心业务数据 (`track_data.proto`)
|
||||
|
||||
```protobuf
|
||||
syntax = "proto3";
|
||||
package radar.external.v1;
|
||||
|
||||
import "radar/external/v1/common.proto";
|
||||
import "radar/external/v1/system_status.proto";
|
||||
|
||||
// 2.4.2 确立的原子批次
|
||||
message TrackDataBatch {
|
||||
// --- Header ---
|
||||
uint32 station_id = 1; // 站台ID
|
||||
uint64 batch_sequence_id = 2; // 批次序号
|
||||
uint32 checksum = 3; // CRC32c 校验和
|
||||
uint64 timestamp_us = 4; // UTC 时间戳 (微秒)
|
||||
uint64 trace_id = 5; // 全链路追踪ID
|
||||
uint32 throttle_level = 6; // 当前热节流等级 (0-3)
|
||||
|
||||
// --- Payload ---
|
||||
repeated TrackMessage tracks = 7; // 确认航迹
|
||||
repeated PlotMessage plots = 8; // 原始点迹 (可被剪裁)
|
||||
|
||||
// 系统状态快照 (可选,通常低频发送或仅在变更时发送)
|
||||
SystemStatusMessage system_status = 9;
|
||||
}
|
||||
|
||||
message TrackMessage {
|
||||
uint64 track_id = 1;
|
||||
|
||||
enum Status {
|
||||
STATUS_UNSPECIFIED = 0;
|
||||
STATUS_TENTATIVE = 1;
|
||||
STATUS_CONFIRMED = 2;
|
||||
STATUS_COAST = 3;
|
||||
STATUS_LOST = 4;
|
||||
}
|
||||
Status status = 2;
|
||||
|
||||
StateVector state = 3; // 运动状态
|
||||
repeated float covariance = 4; // 协方差矩阵 (对角线或下三角)
|
||||
|
||||
// 扩展属性
|
||||
float probability = 5; // 存在概率
|
||||
int32 classification = 6; // 目标分类 ID
|
||||
}
|
||||
|
||||
message PlotMessage {
|
||||
uint32 batch_id = 1; // 关联的 CPI ID
|
||||
uint32 range_idx = 2;
|
||||
uint32 doppler_idx = 3;
|
||||
float snr = 4;
|
||||
float azimuth_rad = 5;
|
||||
float range_m = 6;
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 系统状态定义 (`system_status.proto`)
|
||||
|
||||
这是显控端感知服务器“健康度”的依据。
|
||||
|
||||
```protobuf
|
||||
syntax = "proto3";
|
||||
package radar.external.v1;
|
||||
|
||||
message SystemStatusMessage {
|
||||
enum HealthState {
|
||||
HEALTH_UNKNOWN = 0;
|
||||
HEALTH_OK = 1;
|
||||
HEALTH_DEGRADED = 2; // 降级 (如热节流中)
|
||||
HEALTH_CRITICAL = 3; // 严重故障 (部分模块离线)
|
||||
}
|
||||
HealthState overall_health = 1;
|
||||
|
||||
// 关键资源指标
|
||||
float gpu_temp_celsius = 2;
|
||||
float gpu_util_percent = 3;
|
||||
float mem_usage_percent = 4;
|
||||
|
||||
// 模块级状态 (简报)
|
||||
map<string, string> module_states = 5; // e.g. {"SignalProcessor": "RUNNING"}
|
||||
}
|
||||
```
|
||||
|
||||
-----
|
||||
|
||||
## 4\. 演进与兼容性法则 (Evolution Laws)
|
||||
|
||||
在项目全生命周期中,**严禁**违反以下法则:
|
||||
|
||||
1. **禁止修改 Tag (Never Change Tags)**:
|
||||
|
||||
- 一旦字段 `uint32 station_id = 1;` 发布,Tag `1` 永远属于 `station_id`。即使该字段被废弃,Tag `1` 也不能分配给新字段。
|
||||
|
||||
2. **保留机制 (Reserved Strategy)**:
|
||||
|
||||
- 当删除字段时,必须使用 `reserved` 关键字锁定 Tag 和 Name,防止未来误用。
|
||||
- *示例*:
|
||||
|
||||
```protobuf
|
||||
message TrackMessage {
|
||||
// uint32 old_field = 5; // Deleted
|
||||
reserved 5;
|
||||
reserved "old_field";
|
||||
}
|
||||
```
|
||||
|
||||
3. **默认值假设 (Default Value Assumption)**:
|
||||
|
||||
- 接收端(显控)**不能**依赖字段的存在性(`has_field`),必须处理字段缺失(即值为 0/false/empty)的情况。
|
||||
- *设计推论*:如果 `0` 具有特殊业务含义(如 `range = 0` 表示雷达正中心),则必须小心。通常建议业务值的有效范围避开 `0`,或者使用 `oneof` 包装以获得存在性检查能力(但这会增加开销,非必要不推荐)。
|
||||
|
||||
-----
|
||||
|
||||
## 5\. 构建集成规范 (Build Integration)
|
||||
|
||||
- **代码生成**:使用 CMake 的 `protobuf_generate_cpp` 命令,在构建时自动生成 `.pb.h` 和 `.pb.cc`。
|
||||
- **不可修改**:生成的 C++ 代码严禁人工修改,且不应提交到 Git 仓库(应作为构建产物)。
|
||||
- **库依赖**:对外发布 SDK 时,应提供编译好的 `.proto` 文件或预编译的静态库,而不是让第三方依赖具体的 Protobuf 版本(尽量减少 DLL Hell)。
|
||||
|
||||
-----
|
||||
|
||||
## 总结
|
||||
|
||||
**2.5.3 章节基线** 已确立为:
|
||||
|
||||
1. **Standard**: Protobuf v3, Semantic Versioning (`v1`).
|
||||
2. **Schema**: `TrackDataBatch` (Atomic), `GeoPoint` (WGS84).
|
||||
3. **Compatibility**: Strict `reserved` policy, Explicit Zero Values.
|
||||
|
||||
这套契约不仅定义了数据格式,更定义了**团队协作的边界**——后端开发人员在修改 `.proto` 文件时,必须像修改法律条文一样谨慎。
|
||||
@@ -0,0 +1,176 @@
|
||||
---
|
||||
tags: []
|
||||
date created: 星期一, 十一月 24日 2025, 11:19:42 晚上
|
||||
date modified: 星期一, 十一月 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)
|
||||
|
||||
1. **仅移动语义 (Move-Only Semantics)**:
|
||||
|
||||
- `DataPacket<T>` 的拷贝构造函数和拷贝赋值操作符必须被 **显式删除 (`= delete`)**。
|
||||
- **理由**:防止开发者无意中触发深拷贝,破坏零拷贝原则;防止多个对象持有同一块物理内存的所有权,导致 Double Free。
|
||||
|
||||
2. **统一元数据 (Unified Metadata)**:
|
||||
|
||||
- 所有流转的数据包,无论其负载是原始波形还是航迹,都必须携带相同的 Header 结构。
|
||||
- **理由**:使得中间件(如调度器、监控探针)可以在不解析 Payload 的情况下读取 `TraceID` 和 `Timestamp`。
|
||||
|
||||
3. **自动回收 (Self-Reclamation)**:
|
||||
|
||||
- 容器析构时,必须自动触发 Payload 的资源释放(归还内存池或释放堆内存)。
|
||||
- **理由**:消除内存泄漏风险,简化模块内的错误处理逻辑(异常安全)。
|
||||
|
||||
-----
|
||||
|
||||
## 2\. 通用容器定义 (Generic Definition)
|
||||
|
||||
```cpp
|
||||
/**
|
||||
* @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`。
|
||||
|
||||
<!-- end list -->
|
||||
|
||||
```cpp
|
||||
// 定义特定的删除器
|
||||
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` 的默认分配器性能通常可以接受,暂不引入对象池以降低复杂度)。
|
||||
|
||||
<!-- end list -->
|
||||
|
||||
```cpp
|
||||
// 别名定义
|
||||
// 使用 2.5.1 定义的对齐容器
|
||||
using DetectionPayload = AlignedVector<DetectionResult>;
|
||||
using DetectionPacket = DataPacket<DetectionPayload>;
|
||||
|
||||
using TrackPayload = AlignedVector<TrackData>;
|
||||
using TrackPacket = DataPacket<TrackPayload>;
|
||||
```
|
||||
|
||||
-----
|
||||
|
||||
## 4\. 辅助工厂方法 (Factory Helpers)
|
||||
|
||||
为了简化 Header 的填充(防止开发者忘记填 TraceID),应提供工厂函数。
|
||||
|
||||
```cpp
|
||||
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 章节基线** 已确立为:
|
||||
|
||||
1. **Generic Envelope**:统一的 `DataPacket<T>` 模板,强制包含 Header。
|
||||
2. **Move-Only**:通过删除拷贝构造函数,物理上杜绝深拷贝。
|
||||
3. **Hybrid Payload Strategy**:
|
||||
- 大内存(Raw):`unique_ptr` + `CustomDeleter` (Pool)。
|
||||
- 小内存(Result):`std::vector` (Move Semantics)。
|
||||
|
||||
这一设计确保了数据在“集装箱”里流转时,既安全(不会泄露),又轻快(只有指针在动)。
|
||||
@@ -0,0 +1,138 @@
|
||||
---
|
||||
tags: []
|
||||
date created: 星期一, 十一月 24日 2025, 11:25:58 晚上
|
||||
date modified: 星期一, 十一月 24日 2025, 11:28:29 晚上
|
||||
---
|
||||
|
||||
# 2.5.5 序列化边界与映射策略 (Serialization Boundary & Mapping Strategy)
|
||||
|
||||
**基线核心宗旨**:**“Strict Boundary, Explicit Mapping (严格边界,显式映射)”**。
|
||||
严禁在核心计算流水线(Signal/Data Processor)中引入 Protobuf 或 JSON。序列化是 IO 密集型和 CPU 密集型的结合体,必须被隔离在系统的边缘。
|
||||
|
||||
-----
|
||||
|
||||
## 1\. 边界定义契约 (Boundary Contracts)
|
||||
|
||||
我们确立系统中仅有的两个**合法序列化边界 (Legal Serialization Boundaries)**:
|
||||
|
||||
### 1.1 数据面边界:`DisplayController`
|
||||
|
||||
- **输入**:`TrackDataPacket` (C++ Aligned Vector)。
|
||||
- **输出**:`TrackDataBatch` (Protobuf Binary)。
|
||||
- **职责**:高频、批量、单向转换。
|
||||
- **违规判定**:任何在 `SignalProcessor` 或 `DataProcessor` 中出现 `#include "track_data.pb.h"` 的代码均视为架构违规。
|
||||
|
||||
### 1.2 控制面边界:`ApiCommandService`
|
||||
|
||||
- **输入**:内部状态(如 `SystemStateMachine::State`)或配置对象。
|
||||
- **输出**:HTTP Response (JSON/Protobuf)。
|
||||
- **职责**:低频、按需、双向转换。
|
||||
|
||||
-----
|
||||
|
||||
## 2\. 字段映射逻辑 (Mapping Logic)
|
||||
|
||||
这是将 **2.5.1 (Internal POD)** 转换为 **2.5.3 (External Proto)** 的具体法则。由于内部使用定长数组 (`float state[8]`) 而外部使用语义化字段 (`pos_x, vel_x`),这里需要显式的转换逻辑。
|
||||
|
||||
### 2.1 映射表 (Mapping Table)
|
||||
|
||||
| 内部字段 (C++ `TrackData`) | 外部字段 (Proto `TrackMessage`) | 转换逻辑 |
|
||||
| :--- | :--- | :--- |
|
||||
| `track_id` | `track_id` | 直接赋值 |
|
||||
| `status` | `status` | Enum 转换 (C++ Enum -\> Proto Enum) |
|
||||
| `state[0]` | `pos_x` | `state[0]` (单位转换:米 -\> 米) |
|
||||
| `state[1]` | `pos_y` | `state[1]` |
|
||||
| `state[2]` | `pos_z` | `state[2]` |
|
||||
| `state[3]` | `vel_x` | `state[3]` |
|
||||
| `state[4]` | `vel_y` | `state[4]` |
|
||||
| `state[5]` | `vel_z` | `state[5]` |
|
||||
| `state[6.]` (Padding) | **N/A** | **丢弃** (内部对齐填充不导出) |
|
||||
| `covariance_diag[8]` | `covariance` (repeated) | 遍历数组赋值,截断填充部分 |
|
||||
| `_reserved[5]` | **N/A** | **丢弃** (保留字段不导出) |
|
||||
|
||||
### 2.2 实现范式:转换器模式 (Converter Pattern)
|
||||
|
||||
建议实现一个静态的 `TypeConverter` 类,将转换逻辑集中管理,避免散落在 `DisplayController` 的业务逻辑中。
|
||||
|
||||
```cpp
|
||||
// TypeConverter.h
|
||||
class TypeConverter {
|
||||
public:
|
||||
// 单个对象转换:In-Place 填充以减少内存分配
|
||||
static void ToProto(const TrackData& src, radar::ipc::TrackMessage* dst) {
|
||||
dst->set_track_id(src.track_id);
|
||||
dst->set_status(static_cast<radar::ipc::TrackMessage_Status>(src.status));
|
||||
|
||||
// 展开定长数组
|
||||
dst->set_pos_x(src.state[0]);
|
||||
dst->set_pos_y(src.state[1]);
|
||||
dst->set_pos_z(src.state[2]);
|
||||
dst->set_vel_x(src.state[3]);
|
||||
dst->set_vel_y(src.state[4]);
|
||||
dst->set_vel_z(src.state[5]);
|
||||
|
||||
// 协方差:仅拷贝有效数据
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
dst->add_covariance(src.covariance_diag[i]);
|
||||
}
|
||||
|
||||
// 其他属性…
|
||||
}
|
||||
|
||||
// 批量转换
|
||||
static void ToProtoBatch(const std::vector<TrackData>& src_list,
|
||||
radar::ipc::TrackDataBatch* dst_batch) {
|
||||
// 预分配内存,避免 push_back 导致的扩容
|
||||
dst_batch->mutable_tracks()->Reserve(src_list.size());
|
||||
|
||||
for (const auto& track : src_list) {
|
||||
ToProto(track, dst_batch->add_tracks());
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
-----
|
||||
|
||||
## 3\. 数据清洗与脱敏规范 (Sanitization & Scrubbing)
|
||||
|
||||
并非所有内部数据都有资格通过“海关”。为了安全和带宽效率,必须执行清洗。
|
||||
|
||||
### 3.1 敏感数据清洗
|
||||
|
||||
- **内存地址**:内部调试用的 `void* user_data` 或 `debug_ptr` **绝对禁止**序列化。
|
||||
- **原始标识符**:如果内部使用了哈希表索引或临时 Slot ID,必须转换为全局唯一的 `UUID` 或 `TrackID`。
|
||||
|
||||
### 3.2 精度截断 (Precision Truncation)
|
||||
|
||||
- **内部**:为了计算精度,内部可能使用 `double` 或高精度浮点。
|
||||
- **外部**:若显控仅需显示到厘米级,且带宽紧张,可在转换时进行截断或转为 `float`(Protobuf 中 `double` 占 8 字节,`float` 占 4 字节)。
|
||||
- *基线决策*:目前 2.5.3 定义为 `double`,暂不截断,保持精度。
|
||||
|
||||
### 3.3 节流感知清洗 (Throttle-Aware Scrubbing)
|
||||
|
||||
- **机制**:结合 **2.4.4 热节流**,转换器应接受 `throttle_level` 参数。
|
||||
- **逻辑**:
|
||||
|
||||
```cpp
|
||||
static void ToProtoBatch(…, int throttle_level) {
|
||||
if (throttle_level >= 2) {
|
||||
// Level 2: 仅导出确认航迹,且丢弃协方差矩阵以节省带宽
|
||||
// 这是一个深度优化的例子:在序列化阶段就决定不拷贝某些字段
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
-----
|
||||
|
||||
## 总结:2.5 章节整体基线图谱
|
||||
|
||||
至此,**2.5 数据结构定义与序列化规范** 已全部完成。我们构建了一套“内外有别、边界清晰”的数据模型。
|
||||
|
||||
| 领域 | 核心基线 | 关键技术点 |
|
||||
| :--- | :--- | :--- |
|
||||
| **2.5.1 内部对象** | **High-Perf POD** | `alignas(32)`, 定长数组,SIMD 友好。 |
|
||||
| **2.5.2 内部事件** | **BaseEvent 继承** | 强制 `TraceID`, 轻量级负载。 |
|
||||
| **2.5.3 外部契约** | **Protobuf v3** | 语义化版本,向后兼容,StationID 支持。 |
|
||||
| **2.5.4 数据容器** | **Move-Only Packet** | `DataPacket<T>`, `unique_ptr` 所有权管理。 |
|
||||
| **2.5.5 转换边界** | **Explicit Converter** | 静态映射类,节流感知,边界清洗。 |
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
tags:
|
||||
aliases:
|
||||
- 2.6 时序同步与数据一致性 (Timing Synchronization & Data Coherence)
|
||||
date created: 星期一, 十一月 24日 2025, 11:50:10 晚上
|
||||
date modified: 星期一, 十一月 24日 2025, 11:50:24 晚上
|
||||
---
|
||||
|
||||
# 2.6 时序同步与数据一致性 (Timing Synchronization & Data Coherence)
|
||||
|
||||
- **覆盖范围**:定义系统的时间基准获取方式、数据流打点策略以及跨模块处理时的时间对齐逻辑。涵盖从硬件层面的 PTP/GPS 同步,到软件层面的 CPI(相干处理间隔)对齐,以及航迹预测中的时间外推算法,确保系统在微秒级精度下的时空一致性。
|
||||
- **2.6.1 高精度统一时钟源架构 (High-Precision Unified Clock Architecture)**
|
||||
- **核心指向**:定义系统时间的唯一真值来源。优先采用 **PTP (IEEE 1588v2)** 协议通过网口同步至 GPS/北斗授时服务器,实现亚微秒级的时间同步精度。涵盖在 PTP 不可用时的 **NTP 回退策略**,以及利用 CPU **TSC (Time Stamp Counter)** 寄存器作为高频计时源的校准逻辑,防止系统时间跳变(Time Jump)导致的逻辑错误。
|
||||
- **2.6.2 多级数据打点策略 (Multi-Level Timestamping Strategy)**
|
||||
- **核心指向**:定义数据包时间戳的生成位置与精度分级。首选网卡硬件 **TSU (Timestamp Unit)** 生成的入站时间戳(Ingress Timestamp),次选内核网络栈的 `SO_TIMESTAMP` 软件时间戳。在 `DataReceiver` 封装 `RawDataPacket` 时,强制将此硬件/内核时间戳固化为数据的 **“诞生时间” (Generation Time)**,并在后续全链路中保持不变。
|
||||
- **2.6.3 相干处理间隔对齐机制 (CPI Alignment Mechanism)**
|
||||
- **核心指向**:针对信号处理模块的特殊时序要求。定义如何根据雷达 **PRF (脉冲重复频率)** 和 **波位编码**,将连续到达的 UDP 数据包在内存池中重组为严格对齐的 **CPI 数据块**。涵盖处理网络抖动导致的脉冲到达时间波动(Jitter)的缓冲策略,确保 FFT 和多普勒处理时的数据在时间域上严格相干。
|
||||
- **2.6.4 航迹外推与异步测量融合 (Track Extrapolation & Asynchronous Measurement Fusion)**
|
||||
- **核心指向**:针对数据处理模块的时空一致性逻辑。定义在进行数据关联(Data Association)时,如何将上一时刻($t_{k-1}$)的航迹状态,基于运动模型精确外推至当前测量时刻($t_k$)。涵盖处理乱序到达(Out-of-Order)量测数据的**延迟关联**或**丢弃策略**,确保卡尔曼滤波的更新步基于单调递增的时间轴。
|
||||
- **2.6.5 全链路延迟审计与抖动监控 (End-to-End Latency Auditing & Jitter Monitoring)**
|
||||
- **核心指向**:定义系统实时性的度量标准。利用 `DataPacket` 头部携带的诞生时间戳,在流水线的每个关键节点(接收、信号处理完成、航迹更新完成、网关发送)计算 **驻留时间 (Residence Time)**。监控模块需实时统计各阶段的延迟分布,一旦发现处理抖动超过 CPI 周期的一定比例(如 10%),立即触发性能告警或热节流保护。
|
||||
@@ -0,0 +1,114 @@
|
||||
---
|
||||
tags: []
|
||||
date created: 星期三, 十一月 26日 2025, 9:41:51 晚上
|
||||
date modified: 星期三, 十一月 26日 2025, 9:54:07 晚上
|
||||
---
|
||||
|
||||
# 2.6.1 高精度统一时钟源架构 (High-Precision Unified Clock Architecture)
|
||||
|
||||
**基线核心宗旨**:**“Hardware PTP as Truth, Software TSC for Speed (硬件 PTP 为真值,软件 TSC 为速度)”**。
|
||||
我们构建一个分层的时间架构:底层依赖 **IEEE 1588v2 (PTP)** 锁定物理时钟,应用层利用 **CPU TSC** 实现零系统调用的纳秒级打点,并通过**动态校准回路**将两者对齐。
|
||||
|
||||
-----
|
||||
|
||||
## 1. 约束输入与对齐 (Constraints & Alignment)
|
||||
|
||||
基于分布式雷达组网(TDP-2.4-DIST)和实时性要求,我们需对齐以下硬性约束:
|
||||
|
||||
1. **同步精度 (P0)**:多站协同要求时间同步误差 **\< 1µs**。传统的 NTP(毫秒级)无法满足,必须使用硬件辅助的 PTP。
|
||||
2. **硬件依赖 (Hardware)**:
|
||||
- **网卡**:必须支持 **Hardware Timestamping** (IEEE 1588 PHY)。(需确认您的网迅网卡或采集卡是否支持,若不支持需回退到软件 PTP,精度降至 10-100µs)。
|
||||
- **交换机**:局域网交换机应支持 **PTP Transparent Clock (TC)** 或 **Boundary Clock (BC)** 模式,以消除排队抖动。
|
||||
3. **单调性 (Monotonicity)**:系统内部逻辑(如卡尔曼滤波 `dt` 计算)严禁出现“时光倒流”。即使外部时钟源发生跳变(Step),内部时钟也必须平滑过渡(Slew)。
|
||||
|
||||
-----
|
||||
|
||||
## 2. 权衡分析与选项呈现 (Trade-off Matrix)
|
||||
|
||||
### 议题 1:同步协议栈选型
|
||||
|
||||
| 选项 | A. NTP (Network Time Protocol) | B. Software PTP | C. Hardware PTP **(推荐)** |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **机制** | 纯软件协议,通过统计学算法消除抖动。 | 使用 PTP 协议,但打点在驱动层/协议栈层。 | **PHY 芯片打点**。完全消除 OS 调度和协议栈延迟。 |
|
||||
| **精度** | 1ms - 10ms。 | 10µs - 100µs。 | **\< 1µs (亚微秒级)**。 |
|
||||
| **适用性** | 日志记录、非实时业务。 | 低成本组网。 | **相控阵雷达、高频交易、工业自动化**。 |
|
||||
|
||||
### 议题 2:应用层计时源 (Application Timing Source)
|
||||
|
||||
| 选项 | A. `clock_gettime` (vDSO) | B. `rdtsc` 指令 (TSC) **(推荐)** |
|
||||
| :--- | :--- | :--- |
|
||||
| **机制** | Linux 标准 API,读取内核维护的时间结构体。 | 直接读取 CPU 内部的 Time Stamp Counter 寄存器。 |
|
||||
| **开销** | **低 (\~20-50ns)**。vDSO 避免了陷入内核,但仍有内存访问开销。 | **极低 (\~5-10ns)**。纯寄存器操作,流水线几乎无停顿。 |
|
||||
| **缺陷** | 仍有微小开销,且受制于内核的时钟更新策略。 | **原生不支持绝对时间**。TSC 只是一个开机后的滴答计数,且不同 CPU 核之间可能微弱不同步(现代 CPU 已通过 `constant_tsc` 解决)。 |
|
||||
| **结论** | 通用场景首选。 | **高频热路径(如每秒百万次打点)首选**。需配合软件校准。 |
|
||||
|
||||
-----
|
||||
|
||||
## 3. 基线确立与实施规范
|
||||
|
||||
为了达成亚微秒级同步并支持高频打点,我们确立 **Hardware PTP + TSC 软时钟** 为基线。
|
||||
|
||||
### 3.1. 物理层同步架构 (PTP Topology)
|
||||
|
||||
- **Grandmaster (GM)**:总控服务器或专用的 GPS/北斗授时仪,作为 PTP 主时钟。
|
||||
- **Slave Nodes**:雷达处理服务器(DataReceiver 所在节点)。
|
||||
- **软件栈**:使用 Linux 标准工具集 **`linuxptp`**。
|
||||
- **`ptp4l`**:负责将 **网卡 PHC (PTP Hardware Clock)** 同步到 GM。
|
||||
- **`phc2sys`**:负责将 **系统时钟 (System Clock / CLOCK\_REALTIME)** 同步到 网卡 PHC。
|
||||
|
||||
**运维配置基线 (`/etc/linuxptp/ptp4l.conf`)**:
|
||||
|
||||
```txt
|
||||
[global]
|
||||
time_stamping hardware
|
||||
delay_mechanism E2E
|
||||
network_transport UDPv4
|
||||
# 关键:在锁定前允许跳变,锁定后仅微调
|
||||
step_threshold 1.0
|
||||
first_step_threshold 0.00002
|
||||
```
|
||||
|
||||
## 2. 应用层软时钟设计 (TSC-based Soft Clock)
|
||||
|
||||
在 C++ 应用层,我们封装一个 `HighPrecisionClock` 类,利用 TSC 提供极速时间戳,同时后台线程负责将其“锚定”到 PTP 时间上。
|
||||
|
||||
- **核心原理**:
|
||||
|
||||
$$
|
||||
T_{current} = T_{base} + \frac{(TSC_{current} - TSC_{base})}{Frequency}$$
|
||||
|
||||
* $T_{base}$ 和 $TSC_{base}$ 是最近一次校准时的基准对。
|
||||
* $Frequency$ 是 CPU 主频(需通过校准测得精确值,而非标称值)。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
* **校准线程 (Calibration Thread)**:
|
||||
- **频率**:每 1 秒运行一次。
|
||||
- **逻辑**:
|
||||
1. 原子操作同时读取当前 `clock_gettime(REALTIME)` ($T_{new}$) 和 `rdtsc` ($TSC_{new}$)。
|
||||
2. 更新全局原子变量 `BasePair` = {$T_{new}$, $TSC_{new}$}。
|
||||
3. **平滑策略**:如果发现 PTP 时间发生了跳变(Step),不要立即更新 $T_{base}$ 导致时间倒流,而是调整 $Frequency$ 让时间“快跑”或“慢跑”以追赶(Slew),保证单调性。
|
||||
|
||||
### 3. 异常处理与回退 (Fallback)
|
||||
|
||||
- **PTP 失锁检测**:监控模块需监听 `ptp4l` 的 `rms` (Root Mean Square) 偏差值。
|
||||
- 若 `rms > 10µs` 持续 5 秒,标记 **"Time Sync Degraded"**。
|
||||
- **NTP 回退**:如果 PTP 完全不可用(网卡不支持或链路故障),自动回退到 NTP。
|
||||
- **实现**:`chronyd` 配置 `noselect`,仅当 PTP 服务停止时接管。
|
||||
- **标记**:此时数据包中的 `timestamp_us` 精度下降,`TrackDataBatch` 的状态字段应置位 `TIME_PRECISION_LOW`。
|
||||
|
||||
-----
|
||||
|
||||
## 总结:2.6.1 基线图谱
|
||||
|
||||
| 组件 | 核心基线 | 关键技术点 |
|
||||
| :--- | :--- | :--- |
|
||||
| **时间源 (Truth)** | **GPS/北斗 -\> PTP GM** | 绝对时间真值。 |
|
||||
| **同步协议** | **IEEE 1588v2 (Hardware)** | `ptp4l` + `phc2sys`,亚微秒精度。 |
|
||||
| **应用层 API** | **TSC Soft Clock** | `rdtsc` + 动态校准,纳秒级开销。 |
|
||||
| **单调性保障** | **软件平滑 (Slewing)** | 禁止时间倒流,通过频率微调消除偏差。 |
|
||||
|
||||
**提问**:您是否确认 **“硬件 PTP 同步 + TSC 软时钟封装”** 的架构基线?如果确认,我们将进入 **2.6.2 多级数据打点策略**,定义这个高精度时间戳具体“打”在哪里。
|
||||
@@ -0,0 +1,125 @@
|
||||
---
|
||||
tags: []
|
||||
date created: 星期三, 十一月 26日 2025, 9:49:46 晚上
|
||||
date modified: 星期三, 十一月 26日 2025, 10:06:18 晚上
|
||||
---
|
||||
|
||||
# 2.6.2 多级数据打点策略 (Multi-Level Timestamping Strategy)
|
||||
|
||||
**基线核心宗旨**:**“As Early As Possible (越早越好)”**。
|
||||
时间戳必须尽可能在物理层(PHY)或链路层(MAC)生成,以消除操作系统中断调度、驱动处理和协议栈拷贝带来的**不定长抖动(Jitter)**。
|
||||
|
||||
-----
|
||||
|
||||
## 1. 约束输入与对齐 (Constraints & Alignment)
|
||||
|
||||
基于 2.1.1 审计结果(网迅 WX1860AL4 网卡)和 2.6.1 确立的时钟基线,我们需要对齐以下约束:
|
||||
|
||||
1. **硬件能力 (Hardware Cap)**:
|
||||
- **网迅 NIC**:需确认驱动是否支持 `SO_TIMESTAMPING` 接口读取硬件 RX 时间戳。通常企业级网卡(Intel X710/Mellanox)均支持,国产网卡需实测验证。若不支持,需回退到内核软打点。
|
||||
2. **协议支持 (Protocol)**:
|
||||
- UDP 数据包本身不携带“发送时间”(除非应用层协议写了)。因此我们依赖的是**接收端打点 (Ingress Timestamping)**。
|
||||
3. **数据结构关联**:
|
||||
- 生成的纳秒级时间戳必须填入 `RawDataPacket` 的 Header,并最终映射到 Protobuf 的 `timestamp_us`。
|
||||
|
||||
-----
|
||||
|
||||
## 2. 权衡分析与选项呈现 (Trade-off Matrix)
|
||||
|
||||
我们按照“离物理线路的距离”定义三个打点层级:
|
||||
|
||||
| 选项 | A. 用户态打点 (User-space) | B. 内核软打点 (Kernel SW) **(基线)** | C. 硬件硬打点 (Hardware HW) **(理想)** |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **生成位置** | `recvmsg()` 返回后的应用层代码。 | 网卡驱动收到中断,SKB (Socket Buffer) 创建时刻。 | 网卡 PHY 芯片接收到前导码(Preamble)时刻。 |
|
||||
| **抖动来源** | **极大**。受 CPU 调度、软中断处理、内存拷贝影响,抖动可达 10-100µs。 | **中等**。受中断响应延迟影响,抖动约 1-10µs。 | **极小**。几乎无抖动 (\< 100ns)。 |
|
||||
| **PTP 依赖** | 依赖系统时钟 (`CLOCK_REALTIME`)。 | 依赖系统时钟。 | 依赖网卡 PHC (PTP Hardware Clock) 与系统时钟的同步 (`phc2sys`)。 |
|
||||
| **实现复杂度**| 低。调用 `Clock::now()` 即可。 | 中。需配置 Socket 选项并解析辅助数据 (`CMSG`)。 | 高。需硬件支持,且需处理 PHC 到 UTC 的转换。 |
|
||||
| **适用场景** | 功能调试、非实时业务。 | **当前国产环境最稳妥的基线**。 | 相控阵雷达、高频交易。 |
|
||||
|
||||
-----
|
||||
|
||||
## 3. 基线确立与实施规范
|
||||
|
||||
考虑到国产网卡驱动的成熟度风险,我们确立 **“优先硬件,兜底内核,严禁用户态”** 的分级策略。
|
||||
|
||||
### 3.1 策略分级定义 (Hierarchy Definition)
|
||||
|
||||
- **Priority 1 (HW)**: 尝试启用 **`SO_TIMESTAMPING_RX_HARDWARE`**。
|
||||
- 如果网卡支持,这是绝对真值。
|
||||
- *注意*:硬件时间戳通常是 PHC 时间(TAI),需在用户态根据 `ptp4l` 的 offset 转换为 UTC。
|
||||
- **Priority 2 (SW)**: 回退到 **`SO_TIMESTAMPNS`** (内核接收时间)。
|
||||
- 这是**工程基线**。它反映了数据包进入 Linux 网络栈的第一时间点,消除了应用程序调度的延迟。
|
||||
- **Priority 3 (App)**: **严禁作为生产标准**。仅在上述两者皆失败时,使用 2.6.1 定义的 `HighPrecisionClock::now()` 补救,并标记数据质量为 `LOW_PRECISION`。
|
||||
|
||||
### 3.2 实现规范:辅助数据解析 (CMSG Parsing)
|
||||
|
||||
在 `DataReceiver` 的 I/O 线程中,必须改造 `recvmmsg` 的调用方式,以提取内核附带的时间戳元数据。
|
||||
|
||||
- **Socket 配置**:
|
||||
|
||||
```cpp
|
||||
int flags = SO_TIMESTAMPNS; // 请求内核软件时间戳 (纳秒级)
|
||||
// 如果确认网卡支持硬件打点,则改为:
|
||||
// int flags = SOF_TIMESTAMPING_RX_HARDWARE | SOF_TIMESTAMPING_RAW_HARDWARE;
|
||||
setsockopt(sockfd, SOL_SOCKET, SO_TIMESTAMPING, &flags, sizeof(flags));
|
||||
```
|
||||
|
||||
- **接收逻辑 (核心代码范式)**:
|
||||
|
||||
```cpp
|
||||
void UdpReceiver::receiveLoop() {
|
||||
struct mmsghdr msgs[BATCH_SIZE];
|
||||
struct iovec iovecs[BATCH_SIZE];
|
||||
char cmsg_buf[BATCH_SIZE][256]; // 存放辅助数据的缓冲区
|
||||
|
||||
// … 初始化 msgs, iovecs, cmsg …
|
||||
|
||||
int n = recvmmsg(sockfd, msgs, BATCH_SIZE, 0, nullptr);
|
||||
|
||||
for (int i = 0; i < n; ++i) {
|
||||
uint64_t timestamp_ns = 0;
|
||||
|
||||
// 解析辅助数据 (Control Message)
|
||||
struct cmsghdr *cmsg;
|
||||
for (cmsg = CMSG_FIRSTHDR(&msgs[i].msg_hdr); cmsg; cmsg = CMSG_NXTHDR(&msgs[i].msg_hdr, cmsg)) {
|
||||
// 优先提取硬件时间戳,若无则提取软件时间戳
|
||||
if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_TIMESTAMPNS) {
|
||||
struct timespec *ts = (struct timespec *)CMSG_DATA(cmsg);
|
||||
timestamp_ns = ts->tv_sec * 1000000000ULL + ts->tv_nsec;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 兜底:如果内核没给时间戳,立刻用 TSC 软时钟打点
|
||||
if (timestamp_ns == 0) {
|
||||
timestamp_ns = HighPrecisionClock::now();
|
||||
}
|
||||
|
||||
// 固化:写入 RawDataPacket Header
|
||||
// 这一刻起,这个时间戳就是数据的"法定出生时间"
|
||||
auto packet = MakePacket(std::move(payload), seq_id);
|
||||
packet.header.timestamp_us = timestamp_ns / 1000;
|
||||
// … 推送队列 …
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 全链路时间戳传递 (Propagation)
|
||||
|
||||
这个“出生时间戳” (`timestamp_us`) 必须在系统中神圣不可侵犯:
|
||||
|
||||
- **接收端 (`DataReceiver`)**:生成并写入 `RawDataPacket.header`。
|
||||
- **处理端 (`SignalProcessor`)**:继承该时间戳。虽然 FFT 处理是在 5ms 后进行的,但数据的物理意义依然是“那个时刻的回波”。
|
||||
- **输出端 (`DisplayController`)**:将该时间戳写入 Protobuf 的 `timestamp_us` 字段发送给显控。
|
||||
- *注意*:显控端看到的延迟 = `Now() - timestamp_us`。这个延迟包含了:`内核排队 + 信号处理耗时 + 航迹关联耗时 + 序列化耗时 + 网络传输耗时`。这正是我们想要的**真实物理延迟**。
|
||||
|
||||
-----
|
||||
|
||||
## 总结:2.6.2 基线图谱
|
||||
|
||||
| 维度 | 核心基线 | 技术细节 |
|
||||
| :--- | :--- | :--- |
|
||||
| **打点源** | **内核软打点 (`SO_TIMESTAMPNS`)** | 兼顾精度与兼容性,消除用户态抖动。 |
|
||||
| **理想源** | **硬件打点 (HW RX)** | 若网卡支持,优先升级至此 (需实测)。 |
|
||||
| **获取方式** | **`recvmmsg` + `CMSG`** | 从 Socket 辅助数据中提取,而非调用 `time()`。 |
|
||||
| **语义** | **生成时间 (Generation Time)** | 代表信号到达系统的物理时刻,全链路透传,禁止修改。 |
|
||||
146
系统基座文件/2/2.6/2.6.3 相干处理间隔对齐机制 (CPI Alignment Mechanism).md
Normal file
146
系统基座文件/2/2.6/2.6.3 相干处理间隔对齐机制 (CPI Alignment Mechanism).md
Normal file
@@ -0,0 +1,146 @@
|
||||
---
|
||||
tags: []
|
||||
date created: 星期三, 十一月 26日 2025, 10:25:55 晚上
|
||||
date modified: 星期三, 十一月 26日 2025, 10:28:06 晚上
|
||||
---
|
||||
|
||||
# 2.6.3 相干处理间隔对齐机制 (CPI Alignment Mechanism)
|
||||
|
||||
-----
|
||||
|
||||
## TL;DR
|
||||
|
||||
本节确立了 **“原地乱序重组 (In-Place Scatter Assembly)”** 的基线策略。
|
||||
利用 **脉冲索引 (Pulse Index)** 直接计算内存偏移量,将网络包数据直接写入预分配的页锁定内存(Pinned Memory),实现**零拷贝组装**。配合 **“空间/时间双重触发”** 的提交机制和 **“有限零填充”** 的容错策略,在抗网络抖动与保障实时性之间取得平衡。
|
||||
|
||||
-----
|
||||
|
||||
## 一、 约束输入与对齐 (Constraints & Alignment)
|
||||
|
||||
基于前序章节(2.1, 2.2, 2.6.2),我们面临以下硬性约束:
|
||||
|
||||
1. **数据形态**:
|
||||
- **输入**:UDP JUMBO Frame (9KB)。每个包可能包含 1 个或多个脉冲的回波数据,带有 `BatchID` (CPI ID) 和 `PulseIndex`。
|
||||
- **输出**:符合 **2.2.6** 定义的显存布局 `[Channel][Pulse][Range]` (Padding 对齐)。
|
||||
2. **时序特性**:
|
||||
- 网络抖动(Jitter)客观存在,数据包到达顺序不一定严格均等。
|
||||
- 算法要求:多普勒处理(FFT)要求脉冲间隔(PRI)是均匀的。如果数据包晚到,我们不能“等”太久,否则会阻塞流水线;如果包丢了,矩阵就“穿孔”了。
|
||||
3. **内存模型**:必须在 **2.2.1** 确立的 `MemoryPool`(页锁定内存)中直接操作,避免额外的 `memcpy`。
|
||||
|
||||
-----
|
||||
|
||||
## 二、 权衡分析与选项呈现 (Trade-off Matrix)
|
||||
|
||||
### 议题 1:组装策略 (Assembly Strategy)
|
||||
|
||||
| 选项 | A. 顺序追加 + 排序 (Append & Sort) | B. 原地乱序重组 (In-Place Scatter) **(推荐)** |
|
||||
| :--- | :--- | :--- |
|
||||
| **机制** | 收到一个包就 push\_back 到 buffer,凑够数量后按 `PulseIndex` 排序整理内存。 | 根据包头的 `PulseIndex` 直接计算目标地址偏移 `offset`,写入最终位置。 |
|
||||
| **内存拷贝** | **2 次** (接收 -\>临时 Buf-\>排序后 Buf)。 | **1 次** (接收 -\>最终 Buf)。 |
|
||||
| **乱序容忍** | 好,但整理开销大。 | **极佳**。先到的包占坑,后到的包填坑,无视顺序。 |
|
||||
| **CPU 开销** | 高 (排序算法)。 | **极低** (O(1) 地址计算)。 |
|
||||
|
||||
### 议题 2:CPI 提交触发机制 (Commit Trigger)
|
||||
|
||||
| 选项 | A. 严格计数触发 (Strict Count) | B. 严格时间触发 (Strict Timer) | C. 混合双触发 (Hybrid) **(推荐)** |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **机制** | 必须收齐 N 个脉冲才提交。少一个就死等。 | 无论收多少,每隔 T 毫秒强制提交一次。 | **收齐 N 个** OR **首包到达后超时 T\_max**,满足其一即提交。 |
|
||||
| **实时性** | **差**。一个包丢了会导致该 CPI 永远卡死,甚至阻塞后续 CPI。 | **中**。可能切断正在传输的完整 CPI。 | **高**。既保证了完整性,又防止了死锁。 |
|
||||
| **丢包处理** | 无法处理。 | 截断。 | **自适应**。超时未齐则标记为“残缺 CPI”。 |
|
||||
|
||||
### 议题 3:残缺数据处理 (Incomplete Data Policy)
|
||||
|
||||
当触发超时提交时,CPI 矩阵中会有“空洞”(丢包导致的缺失脉冲)。
|
||||
|
||||
| 选项 | A. 整块丢弃 (Drop All) | B. 零填充 (Zero Padding) **(推荐)** |
|
||||
| :--- | :--- | :--- |
|
||||
| **机制** | 只要不完整,整个 CPI 扔掉。 | 缺失的脉冲位置填 0,强行送入 GPU。 |
|
||||
| **算法影响** | **断续**。航迹会中断,但没噪音。 | **信噪比下降**。FFT 旁瓣升高,可能产生假目标。 |
|
||||
| **工程价值** | 简单,适合严苛场景。 | **鲁棒**。偶尔丢 1-2 个包不影响大局,通过 CFAR 阈值抑制旁瓣。 |
|
||||
|
||||
-----
|
||||
|
||||
## 三、 基线确立与实施规范
|
||||
|
||||
为了在 1GbE 的物理限制下榨干性能,我们确立 **B. 原地乱序重组** + **C. 混合双触发** 为基线。
|
||||
|
||||
### 1\. 数据结构:智能 CPI 容器 (`AssemblyBuffer`)
|
||||
|
||||
我们在 `DataReceiver` 中维护一个“正在组装”的 CPI 池。
|
||||
|
||||
```cpp
|
||||
struct AssemblyBuffer {
|
||||
// 基础元数据
|
||||
uint64_t batch_id = 0;
|
||||
uint64_t first_packet_time_us = 0; // 首包到达时间 (用于超时判定)
|
||||
uint32_t packets_received = 0; // 已收包计数
|
||||
uint32_t total_packets_expected = 0; // 预期总包数 (由波位参数决定)
|
||||
|
||||
// 内存指针 (指向 MemoryPool 中的一块 Pinned Memory)
|
||||
// 物理布局严格遵循 [Channel][Pulse][Range]
|
||||
Complex* data_ptr = nullptr;
|
||||
|
||||
// 位图 (Bitmap):用于快速标记哪些 Pulse 已经收到了
|
||||
// 相比 vector<bool> 更快,且方便检查完整性
|
||||
std::bitset<MAX_PULSES_PER_CPI> reception_mask;
|
||||
|
||||
// 状态
|
||||
enum State { IDLE, ASSEMBLING, READY, DAMAGED };
|
||||
State state = IDLE;
|
||||
};
|
||||
```
|
||||
|
||||
### 2\. 原地重组逻辑 (In-Place Assembly Logic)
|
||||
|
||||
这是 **I/O 线程** 的核心循环逻辑:
|
||||
|
||||
1. **接收**:`recvmmsg` 收到一个 UDP 包。
|
||||
2. **解析**:读取包头,获取 `BatchID` (CPI ID) 和 `PulseIndex`。
|
||||
3. **寻址**:
|
||||
- 若 `BatchID` 是新的,从 `MemoryPool` 申请一块新 `AssemblyBuffer`。
|
||||
- 若 `BatchID` 已存在,命中缓存。
|
||||
4. **计算偏移 (关键)**:
|
||||
- 利用 **2.2.6** 确定的 Pitch (行对齐步长)。
|
||||
- `TargetAddr = BufferBase + (PulseIndex * Pitch_Bytes)`。
|
||||
5. **写入**:将 UDP Payload 直接 `memcpy` 到 `TargetAddr`。
|
||||
- *注*:这是数据进入系统的**唯一一次拷贝**。
|
||||
6. **标记**:`reception_mask.set(PulseIndex)`;`packets_received++`。
|
||||
|
||||
### 3\. 提交与超时策略 (Commit & Timeout)
|
||||
|
||||
我们需要一个**低频**(如 1kHz)的检查器(可以在 I/O 线程的空闲间隙运行,或由定时器触发):
|
||||
|
||||
- **完整性检查**:`if (packets_received == total_packets_expected)` -\> **立即提交** (Push to GPU Queue)。
|
||||
- **超时检查**:`if (Now() - first_packet_time_us > MAX_JITTER_WINDOW_US)` -\> **强制提交**。
|
||||
- *基线值*:`MAX_JITTER_WINDOW_US` 建议设为 **CPI 时长的 10%**(例如 CPI 为 5ms,则抖动容忍窗为 500us)。
|
||||
|
||||
### 4\. 容错与填充规范
|
||||
|
||||
当 **强制提交** 发生时(即丢包):
|
||||
|
||||
- **丢包率判定**:
|
||||
- 若 `LossRate < 5%` (如 128 个脉冲丢了 \< 6 个):执行 **零填充 (Zero Padding)**。将 `reception_mask` 中为 0 的位置对应的内存置零(`memset`)。标记数据质量为 `DEGRADED`。
|
||||
- 若 `LossRate >= 5%`:**整块丢弃**。FFT 在缺失大量数据时结果不可信。记录 `WARNING` 日志。
|
||||
|
||||
-----
|
||||
|
||||
## 四、 自我反驳与边界修正 (Self-Rebuttal)
|
||||
|
||||
- **反驳 1**:原地乱序重组需要预知 `PulseIndex`,如果 UDP 包头里没有这个字段怎么办?
|
||||
- *修正*:协议层(2.1.2)必须强制要求 FPGA/DPU 打包时带上 `PulseIndex`。如果前端不可控,则必须在 `DataReceiver` 维护一个软件计数器,但这会失去抗乱序能力,属于**降级方案**。
|
||||
- **反驳 2**:`memset` 清零操作在大内存(如 64MB)下也是耗时的。
|
||||
- *修正*:我们使用了 `MemoryPool`。在归还 Block 时,是否需要清零?不,我们在**申请 Block 时不清零**,而是依赖**全覆盖写入**。因此,对于没收到的脉冲,**必须显式清零**,否则会残留上一个 CPI 的脏数据(鬼影目标)。
|
||||
- *优化*:只对 `reception_mask` 为 0 的那些行(Pitch)做 `memset`,而不是整个 Buffer。
|
||||
|
||||
-----
|
||||
|
||||
## 总结:2.6.3 基线图谱
|
||||
|
||||
| 维度 | 核心基线 | 关键技术点 |
|
||||
| :--- | :--- | :--- |
|
||||
| **组装模式** | **In-Place Scatter (原地乱序)** | `Addr = Base + PulseIdx * Pitch`,O(1) 复杂度。 |
|
||||
| **内存操作** | **Single Copy (单次拷贝)** | 从 UDP 接收缓冲区直接到 Pinned Memory。 |
|
||||
| **提交触发** | **Hybrid (满额即发 / 超时强发)** | 抖动窗口建议为 CPI 时长的 10%。 |
|
||||
| **容错策略** | **Conditional Padding (条件填充)** | 丢包 \< 5% 补零;\> 5% 丢弃。需显式清除脏数据。 |
|
||||
|
||||
-----
|
||||
@@ -0,0 +1,164 @@
|
||||
---
|
||||
tags: []
|
||||
date created: 星期三, 十一月 26日 2025, 10:30:58 晚上
|
||||
date modified: 星期三, 十一月 26日 2025, 10:48:13 晚上
|
||||
---
|
||||
|
||||
# 2.6.4 航迹外推与异步测量融合 (Track Extrapolation & Asynchronous Measurement Fusion)
|
||||
|
||||
-----
|
||||
|
||||
这是数据处理模块(DataProcessor)的**时空校准器**。
|
||||
在 2.6.2 中,我们给每个 CPI 数据打上了高精度的“出生时间戳” ($t_{meas}$)。
|
||||
在 2.6.3 中,我们完成了数据的物理拼装。
|
||||
现在,算法核心面临的问题是:**当前的航迹停留在 $t_{track}$ 时刻,而新来的量测数据产生于 $t_{meas}$ 时刻。两者在时间上不对齐,如何在空间上进行关联?**
|
||||
|
||||
对于相控阵雷达,波束调度是灵活的,数据到达是异步的,传统的“按扫描周期(Scan-based)”更新逻辑已经失效,必须转向 **“按量测驱动(Measurement-Driven)”** 的异步滤波机制。
|
||||
|
||||
-----
|
||||
|
||||
## 一、 约束输入与对齐 (Constraints & Alignment)
|
||||
|
||||
基于前序基线,我们面临以下硬性约束:
|
||||
|
||||
1. **时间真值**:所有计算必须基于 **2.6.2** 确立的 `timestamp_us`(物理生成时间)。严禁使用 `Now()`(处理时间)进行滤波,否则会将“处理延迟”和“排队延迟”错误地耦合进运动模型,导致速度估计偏差。
|
||||
2. **数据单调性**:物理世界的时间是单调递增的。但网络传输可能导致 UDP 包乱序,使得 $t_{meas}$ 偶尔出现回退。
|
||||
3. **计算模型**:内部对象遵循 **2.5.1** 定义的 `TrackData` (POD, `float state[8]`)。
|
||||
|
||||
-----
|
||||
|
||||
## 二、 权衡分析与选项呈现 (Trade-off Matrix)
|
||||
|
||||
### 议题 1:时间对齐策略 (Alignment Strategy)
|
||||
|
||||
| 选项 | A. 统一同步到当前时刻 (Sync to Now) | B. 航迹外推到量测时刻 (Extrapolate to Meas) **(推荐)** |
|
||||
| :--- | :--- | :--- |
|
||||
| **机制** | 将所有航迹和新量测都外推到 `Now()` 或固定的 `T_tick`,在同一时间切片上做关联。 | 保持量测不动(因为它是真值),将航迹状态外推 $\Delta t = t_{meas} - t_{track}$,在 $t_{meas}$ 处做关联。 |
|
||||
| **精度** | **中**。引入了“处理延迟”的外推误差。 | **高**。利用了最原始的测量时间,物理意义最严谨。 |
|
||||
| **适用性** | 机械扫描雷达(整圈更新)。 | **相控阵雷达/异步多传感器融合**。 |
|
||||
| **副作用** | 航迹时间戳随系统时钟更新。 | 航迹时间戳随量测时间更新。 |
|
||||
|
||||
### 议题 2:乱序量测处理 (OOSM - Out of Sequence Measurement)
|
||||
|
||||
当 $t_{meas} < t_{track}$ 时(即新收到的数据比航迹当前状态还老):
|
||||
|
||||
| 选项 | A. 状态回溯滤波 (Retrodiction) | B. 缓冲重排 (Buffering) | C. 直接丢弃 (Drop) **(推荐)** |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **机制** | 保存历史状态快照,回滚到 $t_{meas}$ 更新后再推回来。 | 在接收端设置缓冲窗(如 50ms),排序后再送入算法。 | **拒收**“来自过去”的数据。 |
|
||||
| **复杂度** | **极高**。内存和计算开销翻倍。 | **中**。增加系统整体延迟。 | **极低**。 |
|
||||
| **适用性** | 数据极稀疏的场景(如空管雷达)。 | 对延迟不敏感的离线系统。 | **高更新率雷达**。丢掉一帧数据对跟踪影响微乎其微。 |
|
||||
|
||||
-----
|
||||
|
||||
## 三、 基线确立与实施规范
|
||||
|
||||
为了适配相控阵体制并保证微秒级一致性,我们确立 **B. 航迹外推到量测时刻** + **C. 直接丢弃乱序** 为基线。
|
||||
|
||||
### 1\. 核心算法:异步外推更新 (Asynchronous Update)
|
||||
|
||||
在关联阶段(Association),针对每一个待匹配的航迹 $Tr_i$,执行以下逻辑:
|
||||
|
||||
1. **时间差计算 (Time Difference)**
|
||||
|
||||
计算量测时间 ($t_{meas}$) 与当前航迹时间 ($t_{track}$) 的偏差,转换为秒:
|
||||
|
||||
$$
|
||||
\Delta t = (t_{meas} - t_{track}) \times 10^{-6}
|
||||
$$
|
||||
|
||||
- $t_{meas}$: 新到达的量测数据时间戳 (单位: 微秒)
|
||||
- $t_{track}$: 航迹当前状态的时间戳 (单位: 微秒)
|
||||
|
||||
2. **状态外推方程 (State Extrapolation)**
|
||||
|
||||
基于时间差 $\Delta t$ 将航迹状态向前推演:
|
||||
|
||||
$$
|
||||
\hat{x}_{k|k-1} = F(\Delta t) \cdot \hat{x}_{k-1|k-1}
|
||||
$$
|
||||
|
||||
- $\hat{x}_{k|k-1}$: 外推后的预测状态向量
|
||||
- $F(\Delta t)$: 状态转移矩阵 (如 CV/CA 模型,随 $\Delta t$ 变化)
|
||||
- $\hat{x}_{k-1|k-1}$: 上一时刻的更新状态
|
||||
|
||||
3. **残差计算 (Innovation Calculation)**
|
||||
|
||||
计算实际量测值与预测观测值之间的差异:
|
||||
|
||||
$$
|
||||
\tilde{y} = z_{meas} - H \cdot \hat{x}_{k|k-1}
|
||||
$$
|
||||
|
||||
- $\tilde{y}$: 修正后的残差 (Innovation)
|
||||
- $z_{meas}$: 转换后的笛卡尔坐标量测值
|
||||
- $H$: 观测矩阵
|
||||
|
||||
---
|
||||
|
||||
### 3\. 代码实现范式 (C++ Optimized)
|
||||
|
||||
利用 2.5.1 定义的定长数组和 SIMD 对齐特性:
|
||||
|
||||
```cpp
|
||||
// 2.5.1 TrackData (State[8]: x, y, z, vx, vy, vz, pad, pad)
|
||||
// 2.5.1 DetectionResult (Block 0: r, azi, ele, v)
|
||||
|
||||
void KalmanFilter::predictAndAssociate(TrackData& track, const DetectionResult& meas) {
|
||||
// 1. 时间对齐 (使用 2.6.2 定义的硬件/内核时间戳)
|
||||
int64_t dt_us = (int64_t)meas.timestamp_us - (int64_t)track.timestamp_us;
|
||||
|
||||
// 2. OOSM 保护 (基线:丢弃乱序)
|
||||
if (dt_us <= 0) return;
|
||||
|
||||
float dt = dt_us * 1e-6f;
|
||||
|
||||
// 3. 状态外推 (CV 模型示例) - SIMD 友好
|
||||
// x' = x + vx * dt
|
||||
// vx' = vx
|
||||
float predicted_state[8];
|
||||
|
||||
// 手动展开循环或利用编译器自动向量化
|
||||
predicted_state[0] = track.state[0] + track.state[3] * dt; // x
|
||||
predicted_state[1] = track.state[1] + track.state[4] * dt; // y
|
||||
predicted_state[2] = track.state[2] + track.state[5] * dt; // z
|
||||
predicted_state[3] = track.state[3]; // vx
|
||||
predicted_state[4] = track.state[4]; // vy
|
||||
predicted_state[5] = track.state[5]; // vz
|
||||
|
||||
// 4. 转换量测到笛卡尔坐标 (z_meas)
|
||||
float z_x, z_y, z_z;
|
||||
SphericalToCartesian(meas.range, meas.azimuth, meas.elevation, z_x, z_y, z_z);
|
||||
|
||||
// 5. 计算马氏距离 (关联判据)
|
||||
float dist = CalculateMahalanobisDist(predicted_state, track.covariance_diag, z_x, z_y, z_z);
|
||||
|
||||
if (dist < GATING_THRESHOLD) {
|
||||
// 关联成功,准备 Update
|
||||
// …
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4\. 时钟跳变防护 (Sanity Check)
|
||||
|
||||
为了防止 2.6.1 中的 PTP/NTP 发生阶跃导致 $\Delta t$ 异常(例如突然跳变 100 年):
|
||||
|
||||
- **基线策略**:设置物理合理性阈值。
|
||||
- **判据**:若 `abs(dt) > 10.0f` (10 秒),视为时钟故障。
|
||||
- **动作**:
|
||||
- 不更新航迹状态。
|
||||
- 重置航迹的 `timestamp_us` 为 `meas.timestamp_us`(强制同步,但不外推状态)。
|
||||
- 触发 `SystemClockJumpEvent` 告警。
|
||||
|
||||
-----
|
||||
|
||||
## 四、总结:2.6.4 基线图谱
|
||||
|
||||
| 维度 | 核心基线 | 设计意图 |
|
||||
| :--- | :--- | :--- |
|
||||
| **对齐方向** | **Track Extrapolates to Measurement** | 尊重数据的物理生成时间,消除处理延迟对估计的影响。 |
|
||||
| **时间差计算** | **$\Delta t = t_{meas} - t_{track}$** | 基于绝对时间戳,而非处理间隔。 |
|
||||
| **乱序处理** | **Drop OOSM ($\Delta t < 0$)** | 牺牲个别数据点的利用率,换取算法的低复杂度和高吞吐。 |
|
||||
| **异常防护** | **Sanity Check (\> 10s)** | 防止时钟源故障导致航迹“飞”出地球。 |
|
||||
|
||||
-----
|
||||
@@ -0,0 +1,134 @@
|
||||
---
|
||||
tags: []
|
||||
date created: 星期三, 十一月 26日 2025, 10:55:22 晚上
|
||||
date modified: 星期三, 十一月 26日 2025, 11:02:47 晚上
|
||||
---
|
||||
|
||||
# 2.6.5 全链路延迟审计与抖动监控 (End-to-End Latency Auditing & Jitter Monitoring)
|
||||
|
||||
如果说 2.6.4 是为了让算法“算得对”,那么 2.6.5 就是为了证明系统“跑得快”且“跑得稳”。这是系统性能的**心电图**,也是触发 **2.3.5 热节流** 和 **2.3.4 故障恢复** 的核心依据。
|
||||
|
||||
-----
|
||||
|
||||
## TL;DR
|
||||
|
||||
本节确立了 **“关键节点埋点 (Checkpointing) + 驻留时间计算 (Residence Time)”** 的基线策略。
|
||||
利用数据包中携带的 **不可变时间戳 (Birth Timestamp)** 作为 T0,在流水线的每个交接点(Handoff Point)采集当前时间,计算 **阶段耗时** 与 **全系统驻留时间**。监控数据通过 **无锁直方图 (Lock-free Histogram)** 聚合,并实时计算 P99 延迟与抖动(Jitter),一旦超出 CPI 周期阈值,立即触发流控。
|
||||
|
||||
-----
|
||||
|
||||
## 一、 约束输入与对齐 (Constraints & Alignment)
|
||||
|
||||
基于前序基线,我们面临以下硬性约束:
|
||||
|
||||
1. **时间基准**:所有打点必须使用 **2.6.1** 确立的 `HighPrecisionClock` (TSC-based),以保证纳秒级精度和极低开销(\< 20ns)。
|
||||
2. **零干扰**:监控逻辑严禁阻塞业务线程。严禁在热路径上进行文件 I/O(写日志)或复杂的锁操作。
|
||||
3. **关联性**:必须能够将延迟数据关联到具体的 `StationID` 和 `TraceID`,以便定位是哪个雷达阵面或哪一帧数据导致了卡顿。
|
||||
|
||||
-----
|
||||
|
||||
## 二、 权衡分析与选项呈现 (Trade-off Matrix)
|
||||
|
||||
### 议题 1:埋点粒度 (Granularity)
|
||||
|
||||
| 选项 | A. 仅首尾打点 (Black Box) | B. 细粒度逐级打点 (White Box) **(推荐)** |
|
||||
| :--- | :--- | :--- |
|
||||
| **机制** | 仅在 `DataReceiver` 入口和 `DisplayController` 出口记录。计算 $T_{out} - T_{in}$。 | 在每个模块的 Input/Output 队列处均打点:Rx, DSP\_Start, DSP\_End, Track, Tx… |
|
||||
| **开销** | 极低。 | 低(TSC 读取极快)。 |
|
||||
| **诊断能力** | **差**。只能知道系统慢了,不知道是 GPU 算慢了,还是 PCIe 堵了,还是调度器卡了。 | **强**。能精确绘制“火焰图”,定位瓶颈环节。 |
|
||||
| **结论** | 仅适用于最终用户 SLA。 | **研发与运维的标准解**。 |
|
||||
|
||||
### 议题 2:数据聚合策略 (Aggregation Strategy)
|
||||
|
||||
| 选项 | A. 全量日志 (Raw Logs) | B. 内存直方图聚合 (In-Memory Histogram) **(推荐)** |
|
||||
| :--- | :--- | :--- |
|
||||
| **机制** | `LOG(INFO) << "Packet " << id << " took " << lat << "us";` | 维护一组原子计数器桶(Buckets),如 `<1ms`, `1-5ms`… 仅统计次数。 |
|
||||
| **IO 压力** | **极大**。每秒 10k 包产生 10k 条日志,瞬间打爆磁盘 IOPS。 | **零**。仅涉及内存原子自增 (`fetch_add`)。 |
|
||||
| **实时性** | 延迟高(需等日志落盘分析)。 | **实时**。控制面可随时读取当前 P99 值。 |
|
||||
|
||||
-----
|
||||
|
||||
## 三、 基线确立与实施规范
|
||||
|
||||
为了实现“显微镜级”的性能观测,我们确立 **B. 细粒度逐级打点** + **B. 内存直方图聚合** 为基线。
|
||||
|
||||
### 1\. 关键路径检查点定义 (Checkpoint Definition)
|
||||
|
||||
我们将数据包在系统中的生命周期划分为 5 个关键时刻(Timestamp):
|
||||
|
||||
- **T0 (Birth/Ingress)**: `DataReceiver` 收到 UDP 包的时刻(硬件/内核打点,见 2.6.2)。
|
||||
- *阶段 1:接收耗时 (Rx Latency)* = $T_1 - T_0$
|
||||
- **T1 (Dispatch)**: `AssemblyBuffer` 组装完成,推入 GPU 输入队列的时刻。
|
||||
- *阶段 2:排队与传输 (Queue & PCIe)* = $T_2 - T_1$
|
||||
- **T2 (Algo Start)**: `SignalProcessor` 从队列取出数据,开始 CUDA Kernel 的时刻。
|
||||
- *阶段 3:计算耗时 (Compute)* = $T_3 - T_2$
|
||||
- **T3 (Algo End)**: 信号处理完成,生成点迹/航迹的时刻。
|
||||
- *阶段 4:后处理与关联 (Post-Proc)* = $T_4 - T_3$
|
||||
- **T4 (Egress)**: `DisplayController` 完成序列化并调用 `sendto` 的时刻。
|
||||
|
||||
**全链路驻留时间 (Residence Time)** = $T_4 - T_0$。
|
||||
|
||||
### 2\. 数据结构:伴随式遥测对象 (`TelemetryContext`)
|
||||
|
||||
为了避免在 `DataPacket` 中增加过多字段(膨胀 Payload),我们采用 **“边车模式 (Sidecar)”** 或利用 `TraceContext`(如果支持携带额外数据)。考虑到 C++ 性能,建议直接在 `DataPacket::Header` 中扩展调试字段(仅在 Debug/Profile 模式下启用)或使用 **Thread Local 统计器**。
|
||||
|
||||
**生产环境基线(高性能方案)**:
|
||||
不随包携带所有中间时间戳,而是 **即时聚合**。
|
||||
|
||||
```cpp
|
||||
// 伪代码:各模块内的埋点逻辑
|
||||
void SignalProcessor::onData(DataPacket& pkt) {
|
||||
uint64_t now = Clock::now();
|
||||
|
||||
// 1. 计算上一阶段耗时 (排队延迟)
|
||||
// Packet Header 中记录了上一个节点的离开时间 last_leav_time
|
||||
uint64_t queue_latency = now - pkt.header.last_leave_time;
|
||||
Metrics::Histogram("dsp_queue_latency")->observe(queue_latency);
|
||||
|
||||
// 2. 执行业务
|
||||
process(pkt);
|
||||
|
||||
// 3. 计算本阶段耗时
|
||||
uint64_t done = Clock::now();
|
||||
uint64_t compute_latency = done - now;
|
||||
Metrics::Histogram("dsp_compute_latency")->observe(compute_latency);
|
||||
|
||||
// 4. 更新 Header,传给下游
|
||||
pkt.header.last_leave_time = done;
|
||||
}
|
||||
```
|
||||
|
||||
### 3\. 抖动监控与告警阈值 (Jitter Monitor)
|
||||
|
||||
单纯看平均耗时(Avg)会掩盖问题,我们必须监控 **P99 (99 分位)** 和 **抖动 (Jitter)**。
|
||||
|
||||
- **抖动定义**:$J_i = |(T4_i - T4_{i-1}) - (T0_i - T0_{i-1})|$
|
||||
- 即:*输出间隔的变化量* 与 *输入间隔的变化量* 之差。
|
||||
- 理想情况下,如果系统处理能力恒定,输入是 10ms 间隔,输出也应该是 10ms 间隔,抖动为 0。
|
||||
- **阈值设定**:
|
||||
- **Warning**: P99 Latency \> $0.8 \times \text{CPI\_Interval}$。
|
||||
- **Critical**: P99 Latency \> $1.0 \times \text{CPI\_Interval}$(系统已无法实时处理,必然积压)。
|
||||
|
||||
### 4\. 闭环反馈:触发热节流 (2.3.5 Linkage)
|
||||
|
||||
这是 2.6.5 与 2.3.5 的联动点。热节流不仅仅由**温度**触发,也应由**性能拥塞**触发。
|
||||
|
||||
- **逻辑**:
|
||||
`MonitoringModule` 每秒轮询一次延迟指标。
|
||||
- 如果 `dsp_queue_latency` (排队延迟) 持续增长 -\> 说明 GPU 处理不过来。
|
||||
- **动作**:发布 `SystemOverloadEvent(Type=COMPUTE_LAG)`。
|
||||
- **响应**:调度器下发 `SetComputeThrottleEvent(Level=1)`,通过主动降级(如减少点迹提取数量)来恢复实时性。
|
||||
|
||||
-----
|
||||
|
||||
## 四、 总结:2.6 章节整体回顾
|
||||
|
||||
至此,**2.6 时序同步与数据一致性** 已全部完成。我们构建了一套严密的时空治理体系:
|
||||
|
||||
1. **2.6.1 时钟源**:Hardware PTP + TSC,确保尺子是准的。
|
||||
2. **2.6.2 打点**:Kernel/Hardware Ingress Timestamp,确保起点是真实的。
|
||||
3. **2.6.3 对齐**:原地乱序重组,确保数据在进入算法前是齐整的。
|
||||
4. **2.6.4 融合**:异步外推,确保航迹与量测在物理时间上是对齐的。
|
||||
5. **2.6.5 审计**:全链路耗时监控,确保系统没有因过载而失速。
|
||||
|
||||
-----
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
tags: []
|
||||
date created: 星期三, 十一月 26日 2025, 11:04:00 晚上
|
||||
date modified: 星期三, 十一月 26日 2025, 11:24:19 晚上
|
||||
---
|
||||
|
||||
# 2.7 链路鲁棒性与错误校检 (Link Robustness & Error Checking)
|
||||
|
||||
- **覆盖范围**:定义系统对通信链路故障的容错能力。涵盖在 UDP 链路中部署 CRC/Checksum 校验、丢包统计与报告机制、以及内部 IPC 异常时的超时和重试策略。
|
||||
- **2.7.1 应用层数据完整性校验 (Application-Layer Data Integrity Verification)**
|
||||
- **核心指向**:弥补 UDP 标准校验和(16-bit)在大数据量传输下的碰撞风险。确立 **CRC32c (Castagnoli)**(硬件指令加速)为标准算法,强制在所有 `TrackDataBatch` 和 `RawDataPacket` 的协议头中包含校验字段。定义校验失败时的**“零容忍”丢弃策略**,防止比特翻转(Bit Flip)导致的脏数据污染卡尔曼滤波状态。
|
||||
- **2.7.2 链路健康度监测与心跳机制 (Link Health Monitoring & Heartbeat Mechanism)**
|
||||
- **核心指向**:定义双向链路的保活协议。在数据静默期(无业务数据发送时)强制发送 **高频心跳包 (1Hz - 10Hz)**,以维持中间网络设备的 NAT 映射并快速检测物理断连。定义 **“静默超时” (Silence Timeout)** 阈值(如 2000ms),一旦触发即判定链路中断,自动触发告警并重置接收状态机。
|
||||
- **2.7.3 差异化丢包恢复策略 (Differentiated Packet Loss Recovery Strategy)**
|
||||
- **核心指向**:针对不同业务流性质定义恢复逻辑。对于 **实时雷达数据(Data Plane)**,采用 **“即时丢弃 (Drop-and-Forget)”** 策略,严禁重传以避免队头阻塞(Head-of-Line Blocking);对于 **关键控制指令(Control Plane)**,采用 **“带确认重传 (ARQ / ACK-Retry)”** 机制,确保配置变更和启停指令的必达性。
|
||||
- **2.7.4 内部 IPC 拥塞控制与背压 (Internal IPC Congestion Control & Backpressure)**
|
||||
- **核心指向**:针对进程内 `SPSC`(无锁队列)的溢出保护。定义 **“有界队列 (Bounded Queue)”** 策略,当队列深度达到高水位(High Watermark,如 80%)时,对上游模块施加**背压 (Backpressure)**,强制执行 **“尾部丢弃 (Tail Drop)”** 或 **“间隔抽稀”**,优先保障系统主进程不发生 OOM(内存溢出)。
|
||||
Reference in New Issue
Block a user