Files
Inbox/系统基座文件/2/2.2/2.2.3 NUMA 感知的内存亲和性控制 (NUMA-Aware Memory Affinity Control).md
2025-12-11 07:24:36 +08:00

94 lines
5.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
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。