以下程序在运行不同次数的迭代时显示出非常可变的性能。可能是什么原因,如何获得一致的测量结果?
该程序分析了 CPU 内核达到的最大浮点运算次数。是:
#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);
}
fmadd_256
函数计算 FMA,main
函数对此进行阶段性处理。还有一个 epilogue
对累加器的值求和,还有一个 reporting
函数将 GFLOPS/秒打印到标准输出。运行时间取决于 iters
的精确值:对于较小的值,每秒 GLOPS 不稳定(这是有道理的),然后每秒 GLOPS 增加并稳定在约 120-130 左右。超过某个点后,它们仍然保持一致,但会下降。这是一个小汇总表:
迭代 | GFLOPS/秒 |
---|---|
1*10^6 | 120 |
1*10^7 | 120 |
2*10^7 | 100 |
3*10^7 | 66 |
5*10^7 | 40 |
我的问题是:
我使用
taskset
将程序绑定到特定 CPU,并查看 /proc/cpuinfo
下以 0.1sec
频率可见的 CPU 缩放。频率略有波动,但报告间隔太长,无法记录重要的频率范围。我还尝试使用 cpupower frequency-set --governor performance
禁用频率缩放。该命令退出时没有错误,但 /proc/cpuinfo
仍然显示出变化,并且程序运行也不一致。
我正在 Alderlake 笔记本电脑上运行基准测试。
您的测试程序有多个错误。在我的电脑上,你的代码甚至无法编译。
error C2676: binary '[': '__m256' does not define this operator or a conversion to a type acceptable to the predefined operator
修复了以下函数以添加向量中的所有车道:
inline float hadd( __m256 v8 )
{
__m128 v = _mm256_extractf128_ps( v8, 1 );
v = _mm_add_ps( v, _mm256_castps256_ps128( v8 ) );
v = _mm_add_ps( v, _mm_movehl_ps( v, v ) );
v = _mm_add_ss( v, _mm_movehdup_ps( v ) );
return _mm_cvtss_f32( v );
}
无论如何,主要问题是那一行:
total_gflops += 8 * 8 * 2;
您的
total_gflops
数字只有 FP32 精度。你不能将它增加 10^7 倍 +128,FP32 没有足够的精度。
经过 1E6 次迭代,您的代码在我的计算机上打印了 146 GFlops。 通过 2E7 迭代,它打印 123 GFlops,通过 5E7,它打印 48.5 GFlops。
但是,一旦我将
float total_gflops{0};
替换为 double total_gflops = 0;
,它就恢复正常,报告 5E7 次迭代的 147 GFlops。这是因为 FP64 数字具有更好的精度,足以将值递增 5E7 倍。