12

任务很简单,将一个整数变量序列写入内存:

原始代码:

for (size_t i=0; i<1000*1000*1000; ++i)
{
   data[i]=i;
};

并行化代码:

    size_t stepsize=len/N;

#pragma omp parallel num_threads(N)
    {
        int threadIdx=omp_get_thread_num();

        size_t istart=stepsize*threadIdx;
        size_t iend=threadIdx==N-1?len:istart+stepsize;
#pragma simd
        for (size_t i=istart; i<iend; ++i)
            x[i]=i;
    };

性能很烂,写1G变量需要1.6秒uint64相当于每秒5GB),通过上面代码的简单并行化open mp parallel(i7 3970 有 6 个线程。

我的设备( i7 3970/64G DDR3-1600)的理论内存带宽是51.2 GB/sec,对于上面的例子,即使通过应用程序,实现的内存带宽也只有理论带宽的1/10左右。 -带宽有界。

有人知道如何改进代码吗?

我在 GPU 上写了很多内存绑定代码,GPU 很容易充分利用 GPU 的设备内存带宽(例如 85% 以上的理论带宽)。

编辑:

该代码由 Intel ICC 13.1 编译为 64 位二进制,并启用了最大优化 (O3) 和 AVX 代码路径以及自动矢量化。

更新:

我尝试了下面的所有代码(感谢 Paul R),没有什么特别的事情发生,我相信编译器完全有能力进行那种 simd/矢量化优化。

至于我为什么要在那里填写数字,长话短说:

它是高性能异构计算算法的一部分,在设备端,该算法效率很高,以至于多GPU集如此之快,以至于我发现性能瓶颈恰好是CPU尝试编写多个序列时记忆中的数字。

当然,知道 CPU 在填充数字方面很糟糕(相比之下,GPU 可以以非常接近的速度填充数字序列( GK110 上的 288GB/中的238GB /秒与 51.2GB/秒中的可怜的5GB/秒 CPU)到GPU全局内存的理论带宽),我可以稍微改变一下我的算法,但让我想知道的是为什么CPU在这里填充数字序列时表现如此糟糕。

至于我的设备的内存带宽,我相信带宽(51.2GB)是正确的,根据我的memcpy()测试,实现的带宽约为理论带宽的80%+>40GB/秒)。

4

2 回答 2

12

假设这是 x86,并且您的可用 DRAM 带宽尚未饱和,您可以尝试使用 SSE2 或 AVX2 一次写入 2 或 4 个元素:

上交所2:

#include "emmintrin.h"

const __m128i v2 = _mm_set1_epi64x(2);
__m128i v = _mm_set_epi64x(1, 0);

for (size_t i=0; i<1000*1000*1000; i += 2)
{
    _mm_stream_si128((__m128i *)&data[i], v);
    v = _mm_add_epi64(v, v2);
}

AVX2:

#include "immintrin.h"

const __m256i v4 = _mm256_set1_epi64x(4);
__m256i v = _mm256_set_epi64x(3, 2, 1, 0);

for (size_t i=0; i<1000*1000*1000; i += 4)
{
    _mm256_stream_si256((__m256i *)&data[i], v);
    v = _mm256_add_epi64(v, v4);
}

注意data需要适当对齐(16 字节或 32 字节边界)。

AVX2 仅在 Intel Haswell 及更高版本上可用,但 SSE2 现在几乎是通用的。


FWIW 我把一个带有标量循环的测试工具放在一起,上面的 SSE 和 AVX 循环用 clang 编译它,并在 Haswell MacBook Air(1600MHz LPDDR3 DRAM)上测试它。我得到以下结果:

# sequence_scalar: t = 0.870903 s = 8.76033 GB / s
# sequence_SSE: t = 0.429768 s = 17.7524 GB / s
# sequence_AVX: t = 0.431182 s = 17.6941 GB / s

我还在一台 3.6 GHz Haswell 的 Linux 桌面 PC 上进行了尝试,使用 gcc 4.7.2 进行编译,得到以下结果:

# sequence_scalar: t = 0.816692 s = 9.34183 GB / s
# sequence_SSE: t = 0.39286 s = 19.4201 GB / s
# sequence_AVX: t = 0.392545 s = 19.4357 GB / s

所以看起来 SIMD 实现比 64 位标量代码有 2 倍或更多的改进(尽管 256 位 SIMD 似乎没有比 128 位 SIMD 有任何改进),并且典型的吞吐量应该比 5 GB/快得多s。

我的猜测是 OP 的系统或基准测试代码有问题,导致吞吐量明显降低。

于 2013-08-23T13:13:36.947 回答
5

您是否有任何理由期望所有内容都data[]在通电的 RAM 页面中?

DDR3 预取器将正确预测大多数访问,但频繁的 x86-64 页面边界可能是一个问题。您正在写入虚拟内存,因此在每个页面边界都存在对预取器的潜在错误预测。MEM_LARGE_PAGES您可以通过使用大页面(例如在 Windows 上)大大减少这种情况。

于 2013-08-23T14:57:27.450 回答