避免频率缩放以稳定SIMD FMA性能的技术咨询
CPU FLOPS基准测试性能随迭代次数变化的原因与优化方案
测试程序与现象
该程序用于测试CPU核心的最大FLOPS,核心代码如下:
#include <immintrin.h> #include <smmintrin.h> #include <chrono> #include <cstddef> #include <iostream> std::pair<float, float> fmadd_256(__m256 a[8], __m256 b[8], size_t sz) { __m256 c[8]; float total_gflops{0}; for (std::size_t i = 0; i < sz; i++) { c[0] = _mm256_fmadd_ps(a[0], b[0], c[0]); c[1] = _mm256_fmadd_ps(a[1], b[1], c[1]); c[2] = _mm256_fmadd_ps(a[2], b[2], c[2]); c[3] = _mm256_fmadd_ps(a[3], b[3], c[3]); c[4] = _mm256_fmadd_ps(a[4], b[4], c[4]); c[5] = _mm256_fmadd_ps(a[5], b[5], c[5]); c[6] = _mm256_fmadd_ps(a[6], b[6], c[6]); c[7] = _mm256_fmadd_ps(a[7], b[7], c[7]); total_gflops += 8 * 8 * 2; } float res = epilogue(c); return {res, total_gflops}; } float epilogue(__m256 c[8]) { c[0] = _mm256_add_ps(c[0], c[1]); c[2] = _mm256_add_ps(c[2], c[3]); c[4] = _mm256_add_ps(c[4], c[5]); c[6] = _mm256_add_ps(c[6], c[7]); c[0] = _mm256_add_ps(c[0], c[3]); c[4] = _mm256_add_ps(c[4], c[6]); c[0] = _mm256_add_ps(c[0], c[4]); float res{0.0}; for (size_t i = 0; i < 8; ++i) { res += c[0][i]; } return res; } template <typename T> void reporting(T duration, float flops, float res) { float gflops_sec = (flops * 1000.0 * 1000) / duration; std::cout << "THe inner product is: " << res << std::endl; std::cout << "GFLOPS/sec: " << gflops_sec << std::endl; std::cout << "total gflops: " << flops << std::endl; std::cout << "Duration: " << duration << std::endl; } int main(int argc, char** argv) { __m256 a[8]; __m256 b[8]; double total_res{0}; constexpr size_t iters{5 * 10000000 / 1}; constexpr size_t RUNS{100}; fmadd_256(a, b, iters); // test call double total_gflops{0}; auto begin = std::chrono::high_resolution_clock::now(); for (size_t i = 0; i < RUNS; ++i) { auto [res, gflops] = fmadd_256(a, b, iters); total_gflops += gflops; total_res += res; std::swap(a, b); } total_gflops *= 1e-9; auto end = std::chrono::high_resolution_clock::now(); double duration = std::chrono::duration_cast<std::chrono::microseconds>(end - begin) .count(); reporting(duration, total_gflops, total_res); }
测试时发现,程序性能随迭代次数变化明显:小迭代次数时GFLOPS不稳定,随后进入120-130的平台期,迭代次数超过阈值后性能持续下降但保持稳定,具体数据如下:
| 迭代次数 | GFLOPS/sec |
|---|---|
| 1×10⁶ | 120 |
| 1×10⁷ | 120 |
| 2×10⁷ | 100 |
| 3×10⁷ | 66 |
| 5×10⁷ | 40 |
具体疑问
- 为何迭代次数足够大后,程序性能仍出现显著下降?
- 能否通过高频率采样验证时钟频率,确认是否是频率缩放导致的问题?
- 有没有方法消除性能波动(即便无法达到峰值性能)?
已尝试的操作:
- 用
taskset将程序绑定到特定CPU,以0.1秒间隔查看/proc/cpuinfo,仅发现频率小幅波动 - 执行
cpupower frequency-set --governor performance禁用频率缩放,命令执行成功,但/proc/cpuinfo仍显示频率变化,性能也未稳定 - 测试环境为Alderlake架构笔记本
问题解答
1. 大迭代次数下性能下降的原因
核心原因是CPU热节流(Thermal Throttling):
- 迭代次数较小时,CPU短时间高负载产生的热量不足以触发散热保护,能维持睿频或峰值频率运行,因此GFLOPS稳定在120-130区间
- 迭代次数增大后,持续高负载让CPU温度快速上升,达到散热阈值后,CPU会主动降频控温,计算效率随之下降,GFLOPS数值降低
- Alderlake笔记本的散热能力有限,长时间高负载下无法维持峰值频率,这是笔记本平台的普遍限制
另外,代码中c数组未初始化,可能引发不可预测的编译器优化或计算异常,虽不直接导致性能下降,但会影响测试结果的可靠性。
2. 高频率采样验证时钟频率
可以通过两种方式实现高频率采样,精准确认频率变化:
- 使用
perf工具实时采样:执行perf stat -e cpu-clock -I 100 ./your_program,其中-I 100表示每100毫秒采样一次,能直接记录程序运行期间的CPU频率波动 - 脚本读取内核文件实现毫秒级采样:
#!/bin/bash CPU=0 # 替换为绑定的核心编号 while true; do echo "$(date +%s.%N) $(cat /sys/devices/system/cpu/cpu$CPU/cpufreq/scaling_cur_freq)" >> freq_log.txt sleep 0.01 done
运行脚本的同时启动测试程序,事后分析freq_log.txt即可看到频率随时间的变化,判断是否因热节流导致降频。
3. 消除性能波动的方法
要获得稳定的测试结果,可从以下方面入手:
- 控制测试时长:将单次测试总运行时间控制在1-2秒内,避免CPU因长时间负载触发热节流。调整
iters和RUNS的组合,让总FLOPS对应的运行时间落在这个区间,既能避开短时间的不稳定,又不会触发降温 - 彻底锁定CPU频率:
部分Alderlake笔记本仅靠cpupower无法完全禁用硬件层面的频率调节,需进入BIOS关闭「Intel Speed Shift」或「Dynamic Frequency Scaling」选项,才能彻底锁定频率 - 标准化测试环境:
- 测试时关闭所有后台程序,避免资源抢占
- 确保笔记本处于插电状态,电池模式下多数机型会限制CPU性能
- 用
taskset绑定到Alderlake的性能核(P核,通常是编号靠前的核心如cpu0、cpu1),避免调度到能效核(E核)导致性能波动
- 修正代码问题:在
fmadd_256函数开头添加memset(c, 0, sizeof(c));初始化累加器,避免未定义行为干扰测试结果
内容的提问来源于stack exchange,提问作者fabian
相关产品推荐
相关产品推荐

