避免频率缩放以提高 SIMD FMA 性能

问题描述 投票:0回答:1

以下程序在运行不同次数的迭代时显示出非常可变的性能。可能是什么原因,如何获得一致的测量结果?

该程序分析了 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 笔记本电脑上运行基准测试。

c++ performance x86 cpu simd
1个回答
0
投票

您的测试程序有多个错误。在我的电脑上,你的代码甚至无法编译。

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 倍。

© www.soinside.com 2019 - 2024. All rights reserved.