将一系列整数写入全局内存的快速(est)方法
Fast(est) way to write a seqence of integer to global memory?
任务非常简单,将一系列整数变量写入内存:
原始代码:
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 uint64
变量(相当于每秒5GB)需要1.6秒,通过对上述代码进行简单的并行化(open mp parallel
),速度略有提高,但性能仍然很差,i7 3970上4个线程需要1.4秒6个线程需要1.35秒。
我的装备(i7 3970/64G DDR3-1600)的理论内存带宽为51.2GB/sec,对于上述示例,所实现的内存带宽仅为理论带宽的10左右,即使应用程序的内存带宽几乎是有界的。
有人知道如何改进代码吗?
我在GPU上写了很多内存绑定代码,GPU很容易充分利用GPU的设备内存带宽(例如理论带宽的85%以上)。
编辑:
该代码由Intel ICC 13.1编译为64位二进制,并启用了最大优化(O3)和AVX代码路径,以及自动矢量化。
更新:
我尝试了下面的所有代码(多亏了Paul R),没有发生什么特别的事情,我相信编译器完全有能力进行simd/矢量化优化。
至于我为什么要填写那里的数字,长话短说:
它是高性能异构计算算法的一部分,在设备端,该算法非常高效,以至于多GPU集非常快,以至于我发现性能瓶颈恰好是CPU试图向内存写入几个序列的数字。
当然,知道CPU在填充数字方面很糟糕(相比之下,GPU可以以非常接近GPU全局内存理论带宽的速度填充一系列数字(GK110上的288GB/sec中的238GB/sec而CPU上的51.2GB/secGB/sec 至于我的设备的内存带宽,我认为带宽(51.2GB)大致正确,根据我的memcpy()
测试,实现的带宽约为理论带宽的80%+(>40GB/sec)。
假设这是x86,并且您还没有饱和可用的DRAM带宽,您可以尝试使用SSE2或AVX2一次写入2或4个元素:
SSE2:
#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台式电脑上试用了它,用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没有任何改进),并且典型的吞吐量应该比5GB/s快得多。
我的猜测是OP的系统或基准测试代码出了问题,导致吞吐量明显降低。
是否有任何理由希望所有data[]
都在通电的RAM页面中?
DDR3预取器将正确预测大多数访问,但频繁的x86-64页面边界可能是一个问题。您正在向虚拟内存进行写入,因此在每个页面边界处都存在对预取器的潜在错误预测。使用大页面(例如,Windows上的MEM_LARGE_PAGES
)可以大大减少这种情况。
- 为不同配置设置MSVC_RUNTIME_LIBRARY的正确方法是什么
- 通过方法访问结构
- 最小硬币更换问题(自上而下方法)
- C++为构建时间获取QDateTime的可靠方法
- 在C#中处理C++指针而不使用unsafe的最佳方法
- 处理多个异常集合的C++方法
- 如果C++类在类方法中具有动态分配,但没有构造函数/析构函数或任何非静态成员,那么它仍然是POD类型吗
- 有什么方法可以遍历结构吗
- 当类在C++中定义时,有什么方法可以"register"类吗?
- 在C++中,将大的无符号浮点数四舍五入为整数的最佳方法是什么
- 实现无开销push_back的最佳方法是什么
- 使用std::函数映射对象方法
- 有符号的int和int-有没有一种方法可以在C++中区分它们
- C++从另一个类访问公共静态向量的正确方法是什么
- C++优先级队列,按对象的唯一指针的特定方法升序排列
- 没有为自己的结构调用列表推回方法
- 有没有什么方法可以使用一个函数中定义的常量变量,也可以由c++中同一程序中的其他函数使用
- 在类定义之后定义一个私有方法
- 从另一个向量中删除一个向量的所有元素的快速(est)方法
- 将一系列整数写入全局内存的快速(est)方法