如何计算代码的算术强度?两种存储场景分析及相关疑问
算术强度(Arithmetic Intensity)计算分析
首先给出待分析的代码片段:
const int N = 8192; float a[N], b[N], c[N], d[N]; ... #pragma omp parallel for simd for(int i = 0; i < N; i++) { const float tmp_a = a[i]; const float tmp_b = b[i]; c[i] += tmp_a*tmp_b; d[i] = tmp_a+tmp_b; }
情况1:tmp_a和tmp_b存储在寄存器中时的算术强度
浮点运算次数统计
每个循环迭代包含3次浮点运算:
- 1次乘法:
tmp_a*tmp_b - 1次加法:
c[i] += ...(先计算乘积再与c[i]相加) - 1次加法:
tmp_a+tmp_b
数据传输字节数统计
寄存器属于CPU内部存储,访问时无需与内存/缓存进行数据传输,因此tmp_a、tmp_b的存储不会产生额外传输开销。每个迭代的内存传输操作如下:
- 读取:
a[i](4字节)、b[i](4字节)、c[i](4字节),共12字节 - 写入:
c[i](4字节)、d[i](4字节),共8字节
总传输字节数为20字节。
算术强度计算
算术强度(AI)= 浮点运算次数 / 数据传输字节数 = 3 FLOPs / 20 B = 0.15 FLOPs/B
情况2:tmp_a和tmp_b存储在内存或缓存中时的算术强度
浮点运算次数统计
浮点运算次数与情况1一致,仍为3次/迭代。
数据传输字节数统计
tmp_a、tmp_b存储在内存/缓存时,需要额外的读写操作:
- 读取:
a[i](4B)、b[i](4B)、tmp_a(4B)、tmp_b(4B)、c[i](4B),共20字节 - 写入:
tmp_a(4B)、tmp_b(4B)、c[i](4B)、d[i](4B),共16字节
总传输字节数为36字节。
算术强度计算
AI = 3 FLOPs / 36 B ≈ 0.083 FLOPs/B
数据传输字节数随存储位置的变化规律
- 寄存器:寄存器是CPU核心内部的高速存储,数据存入寄存器后,访问时无需与内存/缓存进行数据交互,因此不会产生额外的内存传输字节数,能最大程度减少传输开销。
- 缓存:缓存是CPU与内存之间的中间层级存储。若数据已驻留缓存,仅需在缓存与CPU之间传输(部分场景下计算算术强度时可忽略缓存内传输,仅统计内存到缓存的传输);若缓存未命中,则需要从内存加载数据,此时传输字节数与内存访问一致。缓存容量越大、命中率越高,整体传输字节数越低。
- 内存:数据存储在内存时,每次访问都需要从内存向CPU传输数据,会产生完整的内存传输开销,是三种存储位置中传输字节数最多的场景。
计算最大浮点吞吐量所需的额外信息
要计算该代码能达到的最大浮点吞吐量,还需要以下硬件和运行环境信息:
- CPU的SIMD指令集支持与宽度:比如AVX2支持256位向量,可同时对8个float进行运算,直接决定单周期浮点运算能力。
- CPU的时钟频率:用于计算单位时间内的总周期数。
- 内存带宽:当算术强度较低时,数据传输速率会成为吞吐量的瓶颈,内存带宽决定了数据从内存到CPU的最大传输速率。
- 缓存参数:包括各级缓存的容量、带宽和延迟,影响数据的访问效率和缓存命中率。
- 并行执行效率:OpenMP的线程数设置、SIMD向量的实际利用率,以及多线程调度的开销等,都会影响最终的并行执行效率。
内容的提问来源于stack exchange,提问作者Jeet
相关产品推荐
相关产品推荐

