Files
Inbox/系统基座文件/2/2.6/2.6.3 相干处理间隔对齐机制 (CPI Alignment Mechanism).md

147 lines
7.8 KiB
Markdown
Raw Normal View History

2025-12-11 07:24:36 +08:00
---
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) 地址计算)。 |
### 议题 2CPI 提交触发机制 (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% 丢弃。需显式清除脏数据。 |
-----