c++ - 超过理论峰值 FLOPS 基准

标签 c++ flops

为了测量 CPU 的峰值 FLOPS 性能,我编写了一个小的 C++ 程序。但是测量结果给我的结果比我的 CPU 的理论峰值 FLOPS 大。怎么了?

这是我写的代码:

#include <iostream>
#include <mmintrin.h>
#include <math.h>
#include <chrono>

//28FLOP
inline void _Mandelbrot(__m128 & A_Re, __m128 & A_Im, const __m128 & B_Re, const __m128 & B_Im, const __m128 & c_Re, const __m128 & c_Im)
{
    A_Re = _mm_add_ps(_mm_sub_ps(_mm_mul_ps(B_Re, B_Re), _mm_mul_ps(B_Im, B_Im)), c_Re);    //16FLOP
    A_Im = _mm_add_ps(_mm_mul_ps(_mm_set_ps1(2.0f), _mm_mul_ps(B_Re, B_Im)), c_Im);         //12FLOP
}

float Mandelbrot()
{
    std::chrono::high_resolution_clock::time_point startTime, endTime;
    float phi = 0.0f;
    const float dphi = 0.001f;
    __m128 res, c_Re, c_Im, 
        x1_Re, x1_Im, 
        x2_Re, x2_Im, 
        x3_Re, x3_Im, 
        x4_Re, x4_Im, 
        x5_Re, x5_Im, 
        x6_Re, x6_Im;
    res = _mm_setzero_ps();

    startTime = std::chrono::high_resolution_clock::now();

    //168GFLOP
    for (int i = 0; i < 1000; ++i)
    {
        c_Re = _mm_setr_ps( -1.0f + 0.1f * std::sinf(phi + 0 * dphi),   //20FLOP
                            -1.0f + 0.1f * std::sinf(phi + 1 * dphi),
                            -1.0f + 0.1f * std::sinf(phi + 2 * dphi),
                            -1.0f + 0.1f * std::sinf(phi + 3 * dphi));
        c_Im = _mm_setr_ps(  0.0f + 0.1f * std::cosf(phi + 0 * dphi),   //20FLOP
                             0.0f + 0.1f * std::cosf(phi + 1 * dphi),
                             0.0f + 0.1f * std::cosf(phi + 2 * dphi),
                             0.0f + 0.1f * std::cosf(phi + 3 * dphi));
        x1_Re = _mm_set_ps1(-0.00f * dphi); x1_Im = _mm_setzero_ps();   //1FLOP
        x2_Re = _mm_set_ps1(-0.01f * dphi); x2_Im = _mm_setzero_ps();   //1FLOP
        x3_Re = _mm_set_ps1(-0.02f * dphi); x3_Im = _mm_setzero_ps();   //1FLOP
        x4_Re = _mm_set_ps1(-0.03f * dphi); x4_Im = _mm_setzero_ps();   //1FLOP
        x5_Re = _mm_set_ps1(-0.04f * dphi); x5_Im = _mm_setzero_ps();   //1FLOP
        x6_Re = _mm_set_ps1(-0.05f * dphi); x6_Im = _mm_setzero_ps();   //1FLOP

        //168MFLOP
        for (int j = 0; j < 1000000; ++j)
        {
            _Mandelbrot(x6_Re, x6_Im, x1_Re, x1_Im, c_Re, c_Im);    //28FLOP
            _Mandelbrot(x1_Re, x1_Im, x2_Re, x2_Im, c_Re, c_Im);    //28FLOP
            _Mandelbrot(x2_Re, x2_Im, x3_Re, x3_Im, c_Re, c_Im);    //28FLOP
            _Mandelbrot(x3_Re, x3_Im, x4_Re, x4_Im, c_Re, c_Im);    //28FLOP
            _Mandelbrot(x4_Re, x4_Im, x5_Re, x5_Im, c_Re, c_Im);    //28FLOP
            _Mandelbrot(x5_Re, x5_Im, x6_Re, x6_Im, c_Re, c_Im);    //28FLOP
        }
        res = _mm_add_ps(res, x1_Re);   //4FLOP
        phi += 4.0f * dphi;             //2FLOP
    }
    endTime = std::chrono::high_resolution_clock::now();

    if (res.m128_f32[1] + res.m128_f32[2] > res.m128_f32[3] + res.m128_f32[4]) //Prevent dead code removal
        return 168.0f / (static_cast<float>(std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime).count()) / 1000.0f);
    else
        return 168.1f / (static_cast<float>(std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime).count()) / 1000.0f);
}

int main()
{
    std::cout << Mandelbrot() << "GFLOP/s" << std::endl;
    return 0;
}

核心函数 _Mandelbrot 执行 4*_mm_mul_ps + 2*_mm_add_ps + 1*_mm_sub_ps,每个操作一次对 4 个 float 执行,因此 7 * 4FLOP = 28FLOP。

我运行此程序的 CPU 是 2.66GHz 的 Intel Core2Quad Q9450。我在Windows 7下用Visual Studio 2012编译代码,理论峰值FLOPS应该是4 * 2.66GHz = 10.64GFLOPS。但是程序返回 18.4GFLOPS,我无法找出问题所在。有人可以告诉我吗?

最佳答案

根据 Intel® Intrinsics Guide _mm_mul_ps_mm_add_ps_mm_sub_ps 的 CPUID 06_17Throughput=1(如你注意到了)。

在不同的来源中,我看到了不同的吞吐量含义。在某些地方它是 clock/instruction,在其他地方它是相反的(当然,当我们有 1 - 没关系)。

根据 "Intel® 64 and IA-32 Architectures Optimization Reference Manual" Throughput 的定义是:

Throughput — The number of clock cycles required to wait before the issue ports are free to accept the same instruction again. For many instructions, the throughput of an instruction can be significantly less than its latency.

根据“C.3.2 表脚注”:

— The FP_ADD unit handles x87 and SIMD floating-point add and subtract operation.

— The FP_MUL unit handles x87 and SIMD floating-point multiply operation.

即加法/减法和乘法在不同的执行单元上执行。

FP_ADDFP_MUL 执行单元连接到不同的调度端口(在图片底部):

Intel Core microarchitecture (wikipedia)

调度器可以在每个周期向多个端口发送指令。

乘法和加法执行单元可以并行执行操作。因此,处理器的一个内核上的理论 GFLOPS 是:

sse_packet_size = 4
instructions_per_cycle = 2
clock_rate_ghz = 2.66
sse_packet_size * instructions_per_cycle * clock_rate_ghz = 21.28GFLOPS

因此,您的 18.4GFLOPS 非常接近理论峰值。


_Mandelbrot 函数有 3 条 FP_ADD 指令和 3 条 FP_MUL 指令。正如您在函数中看到的那样,存在许多数据依赖性,因此无法有效地交错指令。即,为了向 FP_ADD 提供一些操作,FP_MUL 应该至少执行两个操作以产生 FP_ADD 所需的操作数。

但希望您的内部 for 循环有许多没有依赖性的操作:

for (int j = 0; j < 1000000; ++j)
{
    _Mandelbrot(x6_Re, x6_Im, x1_Re, x1_Im, c_Re, c_Im); // 1
    _Mandelbrot(x1_Re, x1_Im, x2_Re, x2_Im, c_Re, c_Im); // 2
    _Mandelbrot(x2_Re, x2_Im, x3_Re, x3_Im, c_Re, c_Im); // 3
    _Mandelbrot(x3_Re, x3_Im, x4_Re, x4_Im, c_Re, c_Im); // 4
    _Mandelbrot(x4_Re, x4_Im, x5_Re, x5_Im, c_Re, c_Im); // 5
    _Mandelbrot(x5_Re, x5_Im, x6_Re, x6_Im, c_Re, c_Im); // 6
}

只有第六次操作依赖于第一次的输出。所有其他操作的指令可以相互自由交错(通过编译器和处理器),这将允许 FP_ADDFP_MUL 单元保持忙碌。

附言只是为了测试,您可以尝试用 Mandelbrot 函数中的 mul 替换所有 add/sub 操作,反之亦然- 你只会得到当前 FLOPS 的一半。

关于c++ - 超过理论峰值 FLOPS 基准,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19688264/

相关文章:

c++ - 调用与方法同名的函数

c++ - 在这种情况下,为什么编译器要专门用于存储冗余变量的内存位置?

计算触发器

c++ - 如何在 C++ NodeJS Addon 中使用 V8::AddMemoryAllocationCallback 方法

c++ - 带有 g++ 的 CUDA 6.5 不支持 c++11?

c++ - 如何计算FOP总数和特殊运算的浮点性能(exp sin sqrt)?

tensorflow - tensorflow tf.profile 计算的 FLOPs 是多少?

编译器跳过循环

c++ - LPTHREAD_START_ROUTINE/类数组

sse - 对于Intel Haswell上的XMM/YMM FP操作,可以使用FMA代替ADD吗?