[[maybe_unused]] —— C++17 属性
告诉编译器「这个变量/函数/参数可能不会被使用,别报警告」。
常见场景:
- 条件编译(debug-only 变量在 release 下没人用)
- 断言专用变量(
assert(x > 0),release 下assert被去掉,x就成”未使用”) - 固定接口签名的回调参数(签名不能改,但本次实现用不到某个参)
每日学习卡片 · 按日期归档
告诉编译器「这个变量/函数/参数可能不会被使用,别报警告」。
常见场景:
assert(x > 0),release 下 assert 被去掉,x 就成”未使用”)把一块已经存在的主机内存注册为页锁定(pinned)内存,锁在物理内存里不被换页,让 GPU 可以高效访问。
关键纠正:
size 决定三大好处:
cudaMemcpyAsync 真正异步)| 差异 | 宏 | 函数 |
|---|---|---|
__FILE__ / __LINE__ | ✅ 调用点的位置 | ❌ 函数自己的位置 |
能从调用者作用域 return | ✅ | ❌ |
| 类型检查 | ❌ | ✅ |
| 参数副作用 | ⚠️ 可能多次求值 | ✅ 只求值一次 |
| 调试友好度 | ❌ | ✅ |
CUDA_CHECK 必须用宏的核心理由: 要在调用现场拿到 __FILE__ 和 __LINE__,函数永远拿不到调用者的位置。
现代 C++ 倾向于用 inline / constexpr / 模板 / std::source_location(C++20)替代宏,但「错误检查 + 位置打印」这个场景宏仍是最简洁的写法。
操作系统系统调用,把文件直接映射到虚拟地址空间。
核心机制:按需加载——mmap 调用本身几乎不读数据,真正读取发生在第一次访问那个地址时(触发缺页中断,OS 把对应页搬进 page cache)。
好处:
read() 的 user/kernel buffer 拷贝)LLM 推理里的典型用法:
mmap 模型权重 → cudaHostRegister pin 住 → cudaMemcpyAsync 喂给 GPU。
一个进程 = 一张地址空间图,所有线程共享。
| 共享 🟢 | 独立 🔴 |
|---|---|
| 代码段、静态数据 | 栈(每个线程一块) |
| 堆 | TLS(thread_local / errno) |
| mmap 区域(含动态库) | CPU 寄存器 / PC / SP |
| 文件描述符表 | 信号掩码 |
| 信号处理器 |
关键洞察:
malloc 内部必须加锁 → 高性能 allocator 用 per-thread cache高地址 ┌──────────────┐
│ 5. 内核 │
│ 4. 栈 │ ← 局部变量,自动管理
│ 3. mmap │ ← 文件映射、动态库、大块 malloc、线程栈
│ 2. 堆 │ ← malloc / new
│ 1. 静态区域 │ ← text / data / bss
└──────────────┘
低地址
几个非显然的事实:
.so 是通过 mmap 加载的malloc(≥128KB)底层用 mmap,不走传统堆CUDA API 错误默认不会抛异常,程序继续跑直到莫名其妙坏掉。这个宏包住每个 CUDA 调用:出错就立刻打印根因(函数名 + 文件 + 行号 + 错误描述)并 abort,让根因暴露在出错的那一行。
#define CUDA_CHECK(call) do { \
cudaError_t err = (call); \
if (err != cudaSuccess) { \
fprintf(stderr, "CUDA error at %s:%d in %s: %s\n", \
__FILE__, __LINE__, #call, cudaGetErrorString(err)); \
abort(); \
} \
} while(0)
do { ... } while(0) 的作用:
if (x) CUDA_CHECK(...); 这种用法)err 不污染外层)