| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
| 14 天前 | ||
| 14 天前 | ||
| 14 天前 | ||
| 14 天前 | ||
| 14 天前 |
以 Histogram 算子为例:串行 → SIMT 并行改写与调优指南
本样例面向 Ascend 950 系列上的 SIMT 编程教学,围绕 Histogram(直方图)算子,核心教学目标是 SIMT 并行计数(grid-stride 跨步循环):将串行 for (i=0; i<N; i++) 替换为 for (i=globalTid; i<N; i+=totalThreads),让每个线程只处理自己"负责"的那部分数据。在此基础上逐步引入 GM 直接访问、asc_atomic_add 在 UB 处理线程冲突、float4 向量化访存优化,以及 launch bounds + GridDim 调优,完整展示 A5 纯 SIMT 编程模型下的算子开发与性能优化路径。
支持范围
| 项目 | 说明 |
|---|---|
| 硬件 / 架构 | Ascend 950PR / 950DT,NPU_ARCH=dav-3510 |
| 特性线 | SIMT |
| 编程模型 | 纯 SIMT(__global__ 无 __aicore__,--enable-simt 编译) |
| 算子 | Histogram(直方图统计) |
| 数据类型 | 输入 float32,输出 int32 |
| 输入 / 输出 | x[1,000,000],min[1],max[1] → y[100](100 bins) |
| 语义 | 统计 x 中落在 [min, max] 区间内的元素在各等宽 bin 中的计数 |
已知限制:
- 本样例仅覆盖
float32输入的直方图,不覆盖int32 / int8 / fp16等多类型分支。 - 本样例聚焦纯 SIMT 模型(
__global__+--enable-simt),不使用__simt_vf__/VF_CALL混合模型。 - 纯 SIMT 下
kernel_operator.h(MTE / DataCopyPad / TPipe 等)不可用——--enable-simt编译环境不包含 MTE 头文件,数据必须通过 GM 直接访问。 - 性能数据与硬件、CANN 版本、数据分布强相关,请以本地 msprof 为准。
目录说明
simt_histogram_story/
├── CMakeLists.txt
├── README.md
├── include/
│ └── sample_common.h # ACL 初始化、数据加载、golden 校验、RunSample 入口
├── scripts/
│ └── gen_data.py # 生成输入 x、min、max 和 golden 直方图
└── src/
├── 0_serial_scalar.asc # Case 0:串行/标量基线(MTE+Vector)
├── 1_basic_simt.asc # Case 1:基础 SIMT 并行
├── 2_mem_optimized.asc # Case 2:访存优化(float4 向量化)
└── 3_advanced_tuning.asc # Case 3:进阶调优(launch_bounds + GridDim)
Histogram 语义
y = [0] * bins
for each val in x:
if min_val <= val <= max_val:
idx = floor((val - min_val) * bins / (max_val - min_val))
if idx == bins: idx = bins - 1
y[idx] += 1
当多个元素落在同一 bin 时,多个 SIMT 线程可能同时写同一 bin 地址,需要原子操作或分区归约保证正确性。
Case 0:串行/标量基线(MTE+Vector)
源文件:src/0_serial_scalar.asc
目标:展示传统 Ascend C MTE+Vector 实现,作为 SIMT 改写的性能与精度基线。
核心实现:
__global__ __aicore__kernel,通过GetBlockIdx/GetBlockNum做多核切分- MTE (
DataCopyPad) 将输入分 tile 搬运到 UB(双缓冲流水) - Scalar 逐元素计算 bin 索引,
GetValue/SetValue更新 UB 上的直方图 - 多核通过
SetAtomicAdd + DataCopyPad归约到 GM SetFlag/WaitFlag硬件事件同步
关键代码:
// MTE 双缓冲流水:CopyIn(MTE 搬运)与 Compute(Scalar 计算)重叠
for (int64_t t = 0; t < tileNum; t++) {
// --- CopyIn: MTE 搬运第 t 个 tile 到 UB ---
LocalTensor<XType> xLocal = xQue.AllocTensor<XType>();
DataCopyPad(xLocal, xGm[coreDataStart + t * TILE_DATA_LENGTH], cp, pad);
xQue.EnQue(xLocal);
// --- Compute: DeQue → 串行逐元素计算 → FreeTensor ---
xLocal = xQue.DeQue<XType>();
for (int32_t i = 0; i < TILE_DATA_LENGTH; i++) {
XType val = xLocal.GetValue(i); // 从 UB 读
if (val >= minVal && val <= maxVal) {
int32_t idx = static_cast<int32_t>(
static_cast<float>(val - minVal) * bins / minMaxRange);
if (idx == bins) idx = bins - 1;
yLocal.SetValue(idx, yLocal.GetValue(idx) + 1); // UB 上累加
}
}
xQue.FreeTensor(xLocal);
}
样例配置:
- 核数:4 cores,通过
GetBlockIdx/GetBlockNum多核切分 - 输入数据量:1,000,000 个 float(4MB)
- 每个 core 处理 ~250,000 个元素,分 tile 用双缓冲搬运
性能数据:
| Task Duration(μs) | aiv_time(μs) | aiv_total_cycles | aiv_vec_time(μs) | aiv_vec_ratio | aiv_scalar_time(μs) | aiv_scalar_ratio | aiv_mte2_time(μs) | aiv_mte2_ratio | L2 hit rate |
|---|---|---|---|---|---|---|---|---|---|
| 11,840 | 11,839 | 78,124,711 | 0.3 | 0.000 | 11,823 | 0.999 | 14.2 | 0.001 | 0.76% |
Case 0 的
mte2_time/mte2_ratio源自 MTE 双缓冲 DMA 搬运(GM↔UB),Case 1-3 为纯 SIMT 模型,无 MTE 硬件单元参与,该项为 0,后续表格不再列出。
优化效果分析:
- 端到端耗时:11,840μs,主要瓶颈为 Scalar 串行逐元素计算,占比 99.9%
- 每个 core 串行处理 250K 元素,Scalar 引擎的吞吐构成性能上限
- MTE 双缓冲流水有效隐藏了部分 GM 访存延迟,但无法突破 Scalar 串行执行的根本约束
运行:
cmake --build build --target simt_histogram_0_serial_scalar
./build/Samples/2_Performance/simt_histogram_story/simt_histogram_0_serial_scalar
预期输出:
[0_serial_scalar] step 0 PASSED
Case 1:基础 SIMT 并行
源文件:src/1_basic_simt.asc
目标:将 Case 0 的串行逻辑首次改写为纯 SIMT 并行。核心是 SIMT 并行计数,在此基础上附带展示 CUDA 风格线程模型、GM 直接访问、asc_atomic_add 在 UB 上处理多线程写同一 bin 的冲突。
为什么做这个优化:Case 0 的 aiv_scalar_ratio=0.999 表明 99.9% 的执行时间消耗在 Scalar 串行计算上。单个 core 串行处理 250K 元素,Scalar 引擎的吞吐直接决定了端到端耗时。SIMT 并行化的核心思路是将这 250K 元素拆分为多个线程并行处理:4,096 个线程同时从 GM 读取各自负责的元素、各自更新直方图,将原本由 1 条 Scalar 流水线串行执行的工作分配到 4,096 条 Vector 流水线上——理论上能把 Scalar 瓶颈完全消除,将执行主力从 Scalar 引擎切换到 Vector 引擎。
核心实现:
__global__kernel(无__aicore__),--enable-simt编译,<<<4, 1024, 0, stream>>>启动threadIdx.x / blockDim.x / blockIdx.x / gridDim.x替代GetBlockIdx/GetBlockNum- 线程以 grid-stride 跨步方式直接从 GM 读取
x[i](标量) asc_atomic_add在 UB 上做原子累加,处理多线程写同一 bin 的冲突asc_atomic_add归约到 GM,多 Block 间原子累加得到最终结果
与 Case 0 的范式差异:
Case 0 和 Case 1 的差异本质是两种编程模型——SIMD 与 SIMT——的切换,其影响贯穿 kernel 声明、线程抽象、内存管理、数据获取、冲突处理和同步等各个层面。
- kernel 声明与编译:Case 0 使用
__global__ __aicore__,以默认标志编译,内核在 AI Core 的 SIMD 流水线上执行。Case 1 使用__global__(无__aicore__),以--enable-simt编译,内核按 SIMT 线程模型调度。 - 线程抽象与索引:Case 0 通过
GetBlockIdx()/GetBlockNum()获取物理 core ID,手动计算每个 core 的数据段[coreDataStart, coreDataLength)。Case 1 通过blockIdx.x*blockDim.x+threadIdx.x计算全局线程 ID,配合 grid-stride 跨步循环自动分配数据——线程索引是逻辑的而非物理的。 - UB 内存管理:Case 0 使用
TPipe/TQue/AllocTensor动态管理 UB buffer,buffer 的分配、入队、出队和释放均由程序员显式控制以匹配 MTE 流水线。Case 1 使用__ubuf__[]静态数组,编译期确定大小,无需运行时 buffer 管理。 - 数据获取路径:Case 0 的数据经 GM→UB→寄存器三级流转——MTE 通过
DataCopyPad将数据从 GM DMA 搬运至 UB,Scalar 引擎再通过GetValue从 UB 读取。Case 1 直接从 GM 加载至寄存器(float val = x[i]),省去了 UB 中转环节,代价是丧失 MTE 的批量 DMA 效率。 - 冲突处理:Case 0 单核内串行执行,不存在对同一地址的并发写。Case 1 多个 SIMT 线程并发更新直方图,同一 bin 可能被多个线程同时写入,必须通过
asc_atomic_add在 UB 上完成原子累加以保证结果正确。 - 同步机制:Case 0 通过
SetFlag/WaitFlag实现 MTE 与 Scalar 之间的流水线事件同步,确保 DMA 完成后再开始计算。Case 1 通过asc_syncthreads()实现线程栅栏——阻塞当前线程直至 Block 内所有线程到达同一点。
| 维度 | Case 0 | Case 1 |
|---|---|---|
| kernel 声明 | __global__ __aicore__ |
__global__ |
| 编译标志 | 默认 | --enable-simt |
| 线程索引 | GetBlockIdx/GetBlockNum(物理 core) |
blockIdx.x / gridDim.x(逻辑线程) |
| UB 内存 | TPipe/TQue/AllocTensor(动态管理) |
__ubuf__[] 静态数组 |
| 数据搬运 | MTE DataCopyPad(双缓冲 DMA) |
x[i] 直接读 GM |
| 冲突处理 | 无需(单核串行) | asc_atomic_add 在 UB 处理 |
| 同步 | SetFlag/WaitFlag(流水线事件) |
asc_syncthreads()(线程栅栏) |
关键代码:
// 核心范式:SIMT 并行计数(grid-stride 跨步循环)
// 对比 Case 0 的串行 for (i=0; i<N; i++)
// → SIMT 的 for (i=globalTid; i<N; i+=totalThreads)
int32_t totalThreads = gridDim.x * blockDim.x; // 总线程数,即并行度
int32_t globalTid = blockIdx.x * blockDim.x + threadIdx.x; // 全局线程 ID
for (int32_t i = globalTid; i < totalLength; i += totalThreads) { // grid-stride
float val = x[i]; // GM 直接读(指针解引用)
if (val >= minVal && val <= maxVal) {
int32_t idx = static_cast<int32_t>(
(val - minVal) * binsF / range);
if (idx == bins) idx = bins - 1;
asc_atomic_add(warpHistos[warpId] + idx, 1); // UB 原子累加
}
}
asc_syncthreads();
// 跨 Warp 求和后原子归约到 GM
for (int32_t i = tid; i < bins; i += blockThreads) {
OutType sum = 0;
for (int32_t w = 0; w < WARPS_PER_BLOCK; w++) {
sum += warpHistos[w][i];
}
asc_atomic_add(y + i, sum); // GM 原子归约
}
grid-stride 跨步循环是本 Story 的核心教学点。 后续 Case 2/3 的所有优化(float4 向量化、launch bounds、GridDim)都是在此并行计数框架上叠加,计数范式本身不变。
样例配置:
- Block 数 × 每 Block 线程数:4 × 1,024 = 4,096 线程
- 每线程处理 ~244 个标量 load(1M ÷ 4,096),grid-stride 跨步
- UB 直方图:
__ubuf__ int32_t[32][108](每 Warp 一份独立直方图),~13.5KB
性能数据:
| Task Duration(μs) | aiv_time(μs) | aiv_total_cycles | aiv_vec_time(μs) | aiv_vec_ratio | aiv_scalar_time(μs) | aiv_scalar_ratio | L2 hit rate |
|---|---|---|---|---|---|---|---|
| 255.8 | 254.8 | 1,678,793 | 252.2 | 0.990 | 1.1 | 0.004 | 0.94% |
优化效果分析:
- 端到端耗时:255.8μs,相比 Case 0 加速 46.3 倍
- 主 Bound 从 Scalar (99.9%) 切换为 Vector (99%)——SIMT 并行化成功将串行瓶颈完全消除
asc_atomic_add在 UB 上以极低延迟处理多线程写同一 bin 的冲突- GM 直接访问(标量
x[i])替代 MTE 双缓冲 DMA,简化编程模型的同时消除了 MTE 的<uint16_t限制
运行:
cmake --build build --target simt_histogram_1_basic_simt
./build/Samples/2_Performance/simt_histogram_story/simt_histogram_1_basic_simt
预期输出:
[1_basic_simt] step 1 PASSED
Case 2:访存优化(float4 向量化)
源文件:src/2_mem_optimized.asc
目标:在 Case 1 基础上引入 float4 向量化读取,将 load 指令数降为 1/4,探索能否进一步降低耗时。
为什么做这个优化:Case 1 的 L2 命中率仅 0.94%。虽然默认路径下 DCache 可能拦截了部分请求导致 L2 计数器无法反映全貌,但该数值仍提示 GM 侧的单次 load 延迟可能处于较高水平。结合 vec_ratio=0.990——Vector 单元近乎完全忙碌,产生两种可能的假设:
- 假设 A(GM 带宽瓶颈):Vector 的忙碌主要消耗在等待 GM load 返回。若 GM 带宽已饱和,数据供给速率将低于 Vector 的消费速率,实际执行时间由访存延迟主导。
- 假设 B(计算瓶颈):Vector 的忙碌源自
asc_atomic_add等计算指令占据执行流水线。GM load 的延迟被计算开销完全掩盖,即使降低访存代价也不会改变端到端耗时。
为区分这两种可能,Case 2 引入 float4 向量化,将 load 指令数降至 1/4。若耗时随之下降,表明 GM 侧为瓶颈(假设 A 成立);若耗时不变,表明瓶颈在计算侧(假设 B 成立),减少访存代价无效。该优化因此同时承担两个角色:性能尝试与瓶颈假设验证。
前提依赖:UB 直方图分配、asc_atomic_add 冲突处理、归约逻辑全部继承自 Case 1。
新增优化:
float4 v = *reinterpret_cast<float4*>(x + i):一次 load 读 4 个 float,load 指令数降为 1/4
关键代码(仅列出与 Case 1 不同的部分):
// Case 1: float val = x[i]; // 标量逐元素读 GM
// Case 2:
int32_t vecEnd = (totalLength / 4) * 4; // float4 边界检测
for (int32_t i = globalTid * 4; i < vecEnd; i += totalThreads * 4) {
float4 v = *reinterpret_cast<float4*>(x + i); // 一次读 4 个 float
// 处理 v.x, v.y, v.z, v.w ...
}
// 标量收尾:处理末尾不足 4 个元素
for (int32_t i = vecEnd + globalTid; i < totalLength; i += totalThreads) {
float val = x[i];
}
样例配置:
- 同 Case 1(4 Blocks × 1,024 线程),仅修改 GM 读取方式。
性能数据:
| Task Duration(μs) | aiv_time(μs) | aiv_total_cycles | aiv_vec_time(μs) | aiv_vec_ratio | aiv_scalar_time(μs) | aiv_scalar_ratio | L2 hit rate |
|---|---|---|---|---|---|---|---|
| 258.7 | 257.0 | 1,691,553 | 254.2 | 0.989 | 1.1 | 0.004 | 0.87% |
优化效果分析:
- 端到端耗时:258.7μs,相比 Case 1 几乎无变化(255.8 → 258.7 us)
- float4 将 load 指令数降为 1/4,但在当前 4,096 线程配置下 GM 带宽并非瓶颈,减少指令数未带来可见性能提升
- L2 命中率与 Case 1 相当(0.87% vs 0.94%),验证了两者 GM 访问路径相同(均走默认路径经 DCache)
- 教学意义:float4 向量化是 GM 访存优化的基本手段,但它的收益取决于实际瓶颈所在。Case 2 展示了使用 msprof 数据定量验证优化有效性的完整流程
运行:
cmake --build build --target simt_histogram_2_mem_optimized
./build/Samples/2_Performance/simt_histogram_story/simt_histogram_2_mem_optimized
预期输出:
[2_mem_optimized] step 2 PASSED
Case 3:进阶调优(launch_bounds + GridDim)
源文件:src/3_advanced_tuning.asc
目标:通过 __launch_bounds__ 寄存器分析和 GridDim 扫描,找到最优的线程/Block 配置。
为什么做这个优化:Case 2 的 float4 将 GM load 指令数降为 1/4,但 aiv_total_cycles 从 1,678,793 变为 1,691,553——几乎不变。这说明瓶颈不在 GM 访存侧,而在 Vector 计算侧(假设 B 成立)。确认计算瓶颈后,优化方向有两种选择:减少每条计算指令的代价,或减少每个线程的计算指令总数。asc_atomic_add 的延迟由硬件决定,无法通过软件降低单条指令的代价,因此唯一有效的路径是减少每个线程的执行指令数——即提升总线程数。当前 4,096 线程每线程处理 ~244 个元素,若将 GridDim 从 4 提升到 32(总线程数至 16,384),每线程只需处理 ~61 个元素,Vector 的计算负载相应降低。这需要两步:通过 --cce-res-usage 确定合适的每 Block 线程数(避免寄存器溢出),再通过 GridDim 扫描找到收益拐点(避免调度开销反超并行收益)。
前提依赖:UB 直方图分配、float4 向量化、归约逻辑全部继承自 Case 2。
调优过程:
Step 1 — __launch_bounds__ 寄存器分析(--cce-res-usage 编译结果):
__launch_bounds__ |
寄存器上限 | 实际使用 | Stack | 判定 |
|---|---|---|---|---|
| 512 | 64 | 34 | 0 | 最优(零溢出) |
| 1024 | 32 | 32 | 8 | 达上限 |
| 2048 | 16 | 16 | 128 | 严重溢出 |
选择 512 线程:每线程 64 寄存器,实际仅用 34 个,零栈溢出。
Step 2 — GridDim 扫描(固定 512 线程/Block):
| GridDim | 总线程 | Task Duration | 判定 |
|---|---|---|---|
| 2 | 1,024 | 481.9 us | |
| 4 | 2,048 | 245.9 us | |
| 8 | 4,096 | 128.1 us | |
| 16 | 8,192 | 69.9 us | |
| 32 | 16,384 | 40.5 us | 最优(拐点) |
| 64 | 32,768 | 46.1 us | 调度开销反超 |
选择 GridDim=32:每线程仅 ~15 次 float4 load。32→64 时额外 Block 的调度开销超过并行度提升。
新增优化:
__launch_bounds__(512):锁定每线程 64 寄存器,零溢出- GridDim 4→32:总线程 4096→16384,提升并行度
关键代码(仅列出与 Case 2 不同的部分):
// Case 1/2: __global__ void kernel(...) // 无 launch_bounds
// Case 3:
__global__ __launch_bounds__(512) void kernel(...) // 64 寄存器/线程,零溢出
// Case 1/2: kernel<<<4, 1024, 0, stream>>>(...); // 4 Blocks × 1024 线程
// Case 3:
kernel<<<32, 512, 0, stream>>>(...); // 32 Blocks × 512 线程
样例配置:
- Block 数 × 每 Block 线程数:32 × 512 = 16,384 线程
- 每线程处理 ~15 次 float4 load(1M ÷ 16,384 ÷ 4),相比 Case 2 的 244 次标量 load 大幅减少
性能数据:
| Task Duration(μs) | aiv_time(μs) | aiv_total_cycles | aiv_vec_time(μs) | aiv_vec_ratio | aiv_scalar_time(μs) | aiv_scalar_ratio | L2 hit rate |
|---|---|---|---|---|---|---|---|
| 40.5 | 39.0 | 1,985,459 | 36.5 | 0.935 | 0.8 | 0.020 | 8.6% |
优化效果分析:
- 端到端耗时:40.5μs,相比 Case 2 加速 6.4 倍,相比 Case 0 加速 292 倍
- 通过
--cce-res-usage定量选择__launch_bounds__(512),确保零寄存器溢出 - GridDim=32 是收益拐点——继续增加到 64 时调度开销反超并行收益
- float4 向量化减少了 75% 的 load 指令数,与 GridDim=32 的并行度提升叠加,共同贡献 6.4 倍加速
运行:
cmake --build build --target simt_histogram_3_advanced_tuning
./build/Samples/2_Performance/simt_histogram_story/simt_histogram_3_advanced_tuning
预期输出:
[3_advanced_tuning] step 3 PASSED
性能对比
以下数据在 Ascend 950PR 上采集(CANN 9.1.0-beta3,warm-up=5)。
| Case | 优化组合(逐 Case 累加) | Task Duration | vs Case 0 | 主 Bound | L2 hit rate | 说明 |
|---|---|---|---|---|---|---|
| 0 | MTE + Vector 串行基线 | 11,840 us | 1x | Scalar (99.9%) | 0.76% | 串行逐元素,Scalar 引擎为绝对瓶颈 |
| 1 | Case 0 + SIMT 并行 | 255.8 us | 46.3x | Vector (99%) | 0.94% | asc_atomic_add 在 UB 处理冲突,GM 直接访问 |
| 2 | Case 1 + float4 向量化 | 258.7 us | 45.8x | Vector (99%) | 0.87% | float4 减少 load 指令数,但 GM 非瓶颈,实际性能未变 |
| 3 | Case 2 + launch_bounds(512) + GridDim=32 | 40.5 us | 292x | Vector (98%) | 8.6% | GridDim 32 并行度提升是核心加速来源 |
实验运行
以下命令在 cann-samples 仓库根目录执行。
环境准备
source ${ASCEND_HOME_PATH}/set_env.sh
# 或:source /usr/local/Ascend/cann-9.1.0-beta.3/set_env.sh
Python 侧需要 numpy:
pip install numpy
构建
cmake -S . -B build -DNPU_ARCH=dav-3510
cmake --build build --target simt_histogram_story
也可以只构建单个 case:
cmake --build build --target simt_histogram_0_serial_scalar
cmake --build build --target simt_histogram_1_basic_simt
cmake --build build --target simt_histogram_2_mem_optimized
cmake --build build --target simt_histogram_3_advanced_tuning
运行
./build/Samples/2_Performance/simt_histogram_story/simt_histogram_0_serial_scalar
./build/Samples/2_Performance/simt_histogram_story/simt_histogram_1_basic_simt
./build/Samples/2_Performance/simt_histogram_story/simt_histogram_2_mem_optimized
./build/Samples/2_Performance/simt_histogram_story/simt_histogram_3_advanced_tuning
每个可执行文件启动后会自动调用 gen_data.py --output <exe_dir> 生成输入和 golden,再执行 kernel 并校验输出。
什么时候使用这种模式
适合:
- 需要对大量独立元素做离散统计(直方图、计数、频率分布等),统计目标数量适中(UB 可容纳)
- 数据量较大,串行处理成为瓶颈,且输入元素之间无依赖关系,天然可并行
- 希望从传统 Ascend C(MTE+Vector)迁移到纯 SIMT 模型,获得简洁的 CUDA 风格编程体验
不适合:
- bins 数量远超 UB 容量(纯 SIMT 无法使用 MTE 批量搬运,频繁的 GM 原子操作代价极高)
- 数据规模极小,线程调度和线程间协调的开销可能超过计算收益
- 需要精细控制流水线(双缓冲、MTE/Scalar 重叠),此时
__aicore__+__simt_vf__混合模型更合适
总结
| Case | 继承关系 | 新增优化 | 核心能力点 | 源文件 |
|---|---|---|---|---|
| 0 | — | MTE+Vector 串行基线 | 双缓冲流水、硬件事件同步、多核归约 | src/0_serial_scalar.asc |
| 1 | Case 0 | SIMT 并行 | __global__ kernel、__ubuf__[]、GM 直接访问、asc_atomic_add UB 原子累加 |
src/1_basic_simt.asc |
| 2 | Case 1 | float4 向量化 | 向量化读 GM、边界检测、msprof 定量验证 | src/2_mem_optimized.asc |
| 3 | Case 2 | launch_bounds(512) + GridDim=32 | --cce-res-usage 寄存器分析、GridDim 扫描、参数空间搜索 |
src/3_advanced_tuning.asc |
处理 SIMT Histogram 的推荐思路:先理解串行逻辑(Case 0)→ 掌握 SIMT 并行计数范式 for (i=globalTid; i<N; i+=totalThreads) 及 GM 直接访问 + asc_atomic_add 在 UB 处理冲突(Case 1)→ 用 msprof 数据判断 GM 访存优化是否有效(Case 2)→ 寄存器分析 + GridDim 扫描找到最优配置(Case 3)。其中 Case 1 的 grid-stride 跨步循环是所有后续优化的基础框架,是迁移到 SIMT 最重要的一步。