我对我的核心i7 920的FP峰值性能有疑问。 我有一个应用程序可以执行大量的MAC操作(基本上是卷积操作),并且当使用多线程和SSE指令时,我无法达到cpu的峰值FP性能约8倍。 当试图找出原因是什么时,我最终得到了一个简化的代码片段,在单个线程上运行而不使用同样糟糕的SSE指令:
for(i=0; i<49335264; i++)
{
data[i] += other_data[i] * other_data2[i];
}
如果我是正确的(数据和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处理器)对吗?
这个巨大的差距有没有解释?因为我无法解释它。
提前多多感谢,我真的可以帮助你了!
答案 0 :(得分: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,理论上限为DDR3每个10,667 GBytes / s的2个通道)。
答案 1 :(得分:3)
解释很简单:虽然您的处理器可以运行在(例如)6.4GHz,但您的内存子系统只能以大约1/10的速率输入/输出数据(对于大多数当前商品而言,广泛的经验法则)的CPU)。因此,实现处理器理论最大值的1/8的持续触发器速率实际上是非常好的性能。
由于您似乎正在处理大约370MB的数据,这可能比处理器上的缓存大,因此您的计算受I / O限制。
答案 2 :(得分:1)
正如高性能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的理论最大值。当然,它可能无法解决您的原始问题,但希望它可以帮助理解正在发生的事情。
答案 3 :(得分: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个循环。
问题似乎是编译器无法管道循环的执行,即使没有循环间依赖,我是否正确?
有人对此有任何其他想法/解决方案吗?
感谢您的帮助!