为什么我的应用程序无法达到 core i7 920 峰值 FP 性能

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

我对我的酷睿 i7 920 的 FP 峰值性能有疑问。 我有一个执行大量 MAC 运算(基本上是卷积运算)的应用程序,当使用多线程和 SSE 指令时,我无法将 cpu 的峰值 FP 性能提高约 8 倍。 当试图找出原因时,我最终得到了一个简化的代码片段,在单个线程上运行并且不使用性能同样糟糕的 SSE 指令:

for(i=0; i<49335264; i++)
{
    data[i] += other_data[i] * other_data2[i];
}

如果我是正确的(data 和 other_data 数组都是 FP),这段代码需要:

49335264 * 2 = 98670528 FLOPs

它在大约 150 毫秒内执行(我非常确定这个计时是正确的,因为 C 定时器和英特尔 VTune Profiler 给了我相同的结果)

这意味着这段代码片段的性能是:

98670528 / 150.10^-3 / 10^9 = 0.66 GFLOPs/sec

该 cpu 的峰值性能应该为 2*3.2 GFlops/sec(2 个 FP 单元,3.2 GHz 处理器),对吗?

对于如此巨大的差距有什么解释吗?因为我无法解释。

提前非常感谢,我真的需要你的帮助!

c intel sse
4个回答
5
投票

我会使用SSE。

编辑:我自己又进行了一些测试,发现你的程序既不受内存带宽的限制(理论限制大约比你的结果高3-4倍),也不受浮点性能(有更高的限制),它是受操作系统延迟分配内存页面的限制。

#include <chrono>
#include <iostream>
#include <x86intrin.h>

using namespace std::chrono;

static const unsigned size = 49335264;

float data[size], other_data[size], other_data2[size];

int main() {
#if 0
        for(unsigned i=0; i<size; i++) {
                data[i] = i;
                other_data[i] = i;
                other_data2[i] = i;
        }
#endif
    system_clock::time_point start = system_clock::now();
        for(unsigned i=0; i<size; i++) 
                data[i] += other_data[i]*other_data2[i];

    microseconds timeUsed = system_clock::now() - start;

    std::cout << "Used " << timeUsed.count() << " us, " 
              << 2*size/(timeUsed.count()/1e6*1e9) << " GFLOPS\n";
}

g++ -O3 -march=native -std=c++0x
翻译。该程序给出

Used 212027 us, 0.465368 GFLOPS

作为输出,尽管热循环转换为

400848:       vmovaps 0xc234100(%rdx),%ymm0
400850:       vmulps 0x601180(%rdx),%ymm0,%ymm0
400858:       vaddps 0x17e67080(%rdx),%ymm0,%ymm0
400860:       vmovaps %ymm0,0x17e67080(%rdx)
400868:       add    $0x20,%rdx
40086c:       cmp    $0xbc32f80,%rdx
400873:       jne    400848 <main+0x18>

这意味着它是完全矢量化的,每次迭代使用 8 个浮点,甚至利用 AVX。 在尝试了像

movntdq
这样的流指令(它没有购买任何东西)之后,我决定用一些东西来实际初始化数组 - 否则它们将是零页,只有在写入时才会映射到真实内存。将
#if 0
更改为
#if 1
立即产生

Used 48843 us, 2.02016 GFLOPS

这非常接近系统的内存带宽(4 个浮点,每两次 FLOPS 4 个字节 = 16 GBytes/s,理论限制是 2 个 DDR3 通道,每个通道 10,667 GBytes/s)。


3
投票

解释很简单:虽然您的处理器可以在(例如)6.4GHz 下运行,但您的内存子系统只能以大约 1/10 的速率输入/输出数据(大多数当前商品 CPU 的广泛经验法则) 。 因此,实现处理器理论最大值的 1/8 的持续失败率实际上是非常好的性能。

由于您似乎正在处理大约 370MB 的数据,这可能大于处理器上的缓存,因此您的计算受 I/O 限制。


1
投票

正如 High Performance Mark 所解释的,您的测试很可能是内存限制而不是计算限制。

我想补充的一件事是,为了量化这种影响,您可以修改测试,以便它对适合 L1 缓存的数据进行操作:

for(i=0, j=0; i<6166908; i++) 
{ 
    data[j] += other_data[j] * other_data2[j]; j++;
    data[j] += other_data[j] * other_data2[j]; j++; 
    data[j] += other_data[j] * other_data2[j]; j++;
    data[j] += other_data[j] * other_data2[j]; j++;
    data[j] += other_data[j] * other_data2[j]; j++;
    data[j] += other_data[j] * other_data2[j]; j++; 
    data[j] += other_data[j] * other_data2[j]; j++; 
    data[j] += other_data[j] * other_data2[j]; j++; 

    if ((j & 1023) == 0) j = 0;
} 

此版本代码的性能应该更接近 FLOPS 的理论最大值。当然,它可能无法解决您原来的问题,但希望它可以帮助您理解发生了什么。


0
投票

我在第一篇文章中查看了代码片段的乘法累加的汇编代码,它看起来像:

movq  0x80(%rbx), %rcx
movq  0x138(%rbx), %rdi
movq  0x120(%rbx), %rdx
movq  (%rcx), %rsi
movq  0x8(%rdi), %r8
movq  0x8(%rdx), %r9
movssl  0x1400(%rsi), %xmm0
mulssl  0x90(%r8), %xmm0
addssl  0x9f8(%r9), %xmm0
movssl  %xmm0, 0x9f8(%r9)

我根据总周期数估计执行乘法累加需要大约 10 个周期。

问题似乎是编译器无法管道化循环的执行,即使没有循环间依赖关系,我是对的吗?

有人对此有任何其他想法/解决方案吗?

感谢迄今为止的帮助!

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