C-交换两个大小相等的内存块的最快方法?(解决方案可行性)
这个问题是问题的延伸。这里我提出两种可能的解决方案,我想知道它们的可行性。我将Haswell微体系结构与GCC/ICC编译器一起使用。我还假设内存是对齐的 选项1-我已经分配了一个内存位置,并进行了3次内存移动。(我使用C-交换两个大小相等的内存块的最快方法?(解决方案可行性),c,memory,swap,avx2,C,Memory,Swap,Avx2,这个问题是问题的延伸。这里我提出两种可能的解决方案,我想知道它们的可行性。我将Haswell微体系结构与GCC/ICC编译器一起使用。我还假设内存是对齐的 选项1-我已经分配了一个内存位置,并进行了3次内存移动。(我使用memmove而不是memcpy来避免复制构造函数) 选项2使用AVX或AVX2加载和存储,利用对齐内存。对于这个解决方案,我考虑交换 int 数据类型。 void swap_memory(int *A, int* B, int NUM_ELEMS){ int i,
memmove
而不是memcpy
来避免复制构造函数)
选项2使用AVX或AVX2加载和存储,利用对齐内存。对于这个解决方案,我考虑交换<代码> int <代码>数据类型。
void swap_memory(int *A, int* B, int NUM_ELEMS){
int i, STOP_VEC = NUM_ELEMS - NUM_ELEMS%8;
__m256i data_A, data_B;
for (i=0; i<STOP_VEC; i+=8) {
data_A = _mm256_load_si256((__m256i*)&A[i]);
data_B = _mm256_load_si256((__m256i*)&B[i]);
_mm256_store_si256((__m256i*)&A[i], data_B);
_mm256_store_si256((__m256i*)&B[i], data_A);
}
for (; i<NUM_ELEMS; i++) {
std::swap(A[i], B[i]);
}
}
void swap_内存(int*A、int*B、int NUM_元素){
int i,STOP_VEC=NUM_ELEMS-NUM_ELEMS%8;
__m256i数据_A、数据_B;
对于(i=0;i如果您确信内存已对齐,则使用AVX可能是最好的。请注意,显式执行此操作可能不可移植-最好装饰指针,以便已知它们已对齐(例如,使用aligned
属性或类似属性)
最有可能的是,选项2(或在语义上这样做的东西)可能更快,因为指针没有受到限制或其他任何东西。编译器可能不知道重新排序内存或保持“aux”不变是安全的
此外,选项2可能更线程安全,具体取决于aux的设置方式
使用一个本地临时文件和memcpy在块中或一次从临时文件到临时文件,甚至一次使用所有临时文件都可以,因为gcc可能能够将其矢量化。避免使用外部临时文件,并确保所有结构都按照对齐方式进行装饰。选项2的读取次数较少,因此我希望它会更快(当然,这一切都取决于数据的大小,如果所有东西都放在缓存中,那么性能优势就会小得多)
您也可以使用AVX内置的_mm256 _stream _si256而不是存储(这样,在再次读取内存之前,您需要一个围栏)。我只需执行以下操作:
unsigned char t;
unsigned char *da = A, *db = B;
while(TO_MOVE--) {
t = *da;
*da++ = *db;
*db++ = t;
}
基于这一点,它非常清晰,而且乐观主义者很有可能做得很好。你分析过它吗?我猜(在优化打开的情况下)gcc/icc会为你矢量化循环,而不是要求你手动执行。OP:“我使用memmove而不是memcpy来避免复制构造函数”→ 什么?这两个函数都只处理原始字节,都不使用复制(或移动)构造函数或赋值运算符。第一种方法可以正确地处理重叠的范围。但是,如果您需要进行所有复制,那么这可能更像是一个设计问题-您应该能够交换两个指针,当然?…请注意--如果指针标记为\uuuuuuu restrict\uuuuuu
,我希望gcc/icc将循环矢量化为你。如果没有\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
,我不知道现在有多少编译器会添加非重叠范围的测试,以检查重新排序操作是否安全。我认为你不能使用alignas
告诉gcc指针f(例如,alignas(32)foo[32]
).Option2更好,因为编译器几乎肯定不会将memcpy版本优化为不涉及aux
的交错循环。您的最后一段可能会以少量计数多次调用memcpy。那么可能是指向对齐存储的指针?我通常只使用gcc属性或定义等rgot认为alignas不是指向内存的东西。gcc特定的方式是a=\uuuu内置\u假设\u对齐(a,32)
。您还可以typedef\uu属性((对齐(32)))int align32\u int
,然后void foo(align32\u int*a){
.IIRC,这确实有效,虽然同一个typedef中的alignas不触发,但typedef方法是我通常使用的方法。可惜alignas不能这样使用。如果缓冲区很小,您很快就会读取它,NT存储会更糟。它们会逐出目标,即使它在缓存中很热。这对自动矢量化很好(-O3
),但是,即使在-O2,您也可以使用-fopenmp
和#pragma omp simd
。因为您有int
和int的元素计数,将其转换为char是愚蠢的。这使得标量清理循环更糟糕。
unsigned char t;
unsigned char *da = A, *db = B;
while(TO_MOVE--) {
t = *da;
*da++ = *db;
*db++ = t;
}