处理双数组中未对齐的部分,对其余部分进行矢量化
Process unaligned part of a double array, vectorize the rest
我正在生成sse/avx指令,目前我必须使用未对齐的加载和存储。我在浮点/双数组上操作,我永远不知道它是否对齐。因此,在对其进行矢量化之前,我希望有一个pre循环,也可能有一个post循环,负责处理未对齐的部分。主矢量化循环然后对对齐的部分进行操作。
但是,如何确定数组何时对齐?我可以检查指针值吗?循环前和循环后应何时停止?
下面是我的简单代码示例:
void func(double * in, double * out, unsigned int size){
for( as long as in unaligned part ){
out[i] = do_something_with_array(in[i])
}
for( as long as aligned ){
awesome avx code that loads operates and stores 4 doubles
}
for( remaining part of array ){
out[i] = do_something_with_array(in[i])
}
}
编辑:我一直在考虑这个问题。理论上,指向第I个元素的指针应该可以被2,4,16,32(取决于它是双的,是sse还是avx)除(类似于&a[I]%16==0)。因此,第一个循环应该覆盖不可分割的元素。
实际上,我会尝试编译器的杂注和标志,看看编译器会产生什么。如果没有人给出一个好的答案,我会在周末发布我的解决方案(如果有的话)。
下面是一些示例C代码,它可以实现
#include <stdio.h>
#include <x86intrin.h>
#include <inttypes.h>
#define ALIGN 32
#define SIMD_WIDTH (ALIGN/sizeof(double))
int main(void) {
int n = 17;
int c = 1;
double* p = _mm_malloc((n+c) * sizeof *p, ALIGN);
double* p1 = p+c;
for(int i=0; i<n; i++) p1[i] = 1.0*i;
double* p2 = (double*)((uintptr_t)(p1+SIMD_WIDTH-1)&-ALIGN);
double* p3 = (double*)((uintptr_t)(p1+n)&-ALIGN);
if(p2>p3) p2 = p3;
printf("%p %p %p %pn", p1, p2, p3, p1+n);
double *t;
for(t=p1; t<p2; t+=1) {
printf("a %p %fn", t, *t);
}
puts("");
for(;t<p3; t+=SIMD_WIDTH) {
printf("b %p ", t);
for(int i=0; i<SIMD_WIDTH; i++) printf("%f ", *(t+i));
puts("");
}
puts("");
for(;t<p1+n; t+=1) {
printf("c %p %fn", t, *t);
}
}
这将生成一个32字节对齐的缓冲区,但随后将其偏移一倍大小,使其不再是32字节对齐。它在标量值上循环直到达到32字节对齐,在32字节对齐的值上循环,然后最后对任何不是SIMD宽度倍数的剩余值进行另一个标量循环。
我认为这种优化只对Nehalem之前的Intel x86处理器有很大意义。由于Nehalem,未对齐加载和存储的延迟和吞吐量与对齐加载和存储器相同。此外,由于Nehalem,缓存线拆分的成本很小。
自Nehalem以来,SSE有一个微妙的点,即未对齐的加载和存储无法与其他操作折叠。因此,自Nehalem以来,对齐的加载和存储并没有随着SSE而过时。因此,原则上,即使使用Nehalem,这种优化也会产生影响,但在实践中,我认为很少有情况会产生影响。
但是,对于AVX,未对齐的加载和存储可以折叠,因此对齐的加载与存储指令已过时。
我与GCC、MSVC和Clang一起对此进行了研究。GCC如果它不能假设指针与SSE对齐到例如16字节,则它将生成与上面的代码类似的代码,以达到16字节对齐,从而避免在向量化时高速缓存行拆分。
Clang和MSVC不这样做,所以他们会受到缓存线拆分的影响。然而,额外代码的成本弥补了缓存线分割的成本,这可能解释了Clang和MSVC不担心的原因
唯一的例外是在纳哈莱姆之前。在这种情况下,当指针未对齐时,GCC比Clang和MSVC快得多。如果指针是对齐的,Clang知道它,那么它将使用对齐的加载和存储,并像GCC一样快速。MSVC矢量化仍然使用未对齐的存储和加载,因此即使指针是16字节对齐的,在Nahalem之前也是缓慢的。
这是一个我认为使用指针差异更清晰的版本
#include <stdio.h>
#include <x86intrin.h>
#include <inttypes.h>
#define ALIGN 32
#define SIMD_WIDTH (ALIGN/sizeof(double))
int main(void) {
int n = 17, c =1;
double* p = _mm_malloc((n+c) * sizeof *p, ALIGN);
double* p1 = p+c;
for(int i=0; i<n; i++) p1[i] = 1.0*i;
double* p2 = (double*)((uintptr_t)(p1+SIMD_WIDTH-1)&-ALIGN);
double* p3 = (double*)((uintptr_t)(p1+n)&-ALIGN);
int n1 = p2-p1, n2 = p3-p2;
if(n1>n2) n1=n2;
printf("%d %d %dn", n1, n2, n);
int i;
for(i=0; i<n1; i++) {
printf("a %p %fn", &p1[i], p1[i]);
}
puts("");
for(;i<n2; i+=SIMD_WIDTH) {
printf("b %p ", &p1[i]);
for(int j=0; j<SIMD_WIDTH; j++) printf("%f ", p1[i+j]);
puts("");
}
puts("");
for(;i<n; i++) {
printf("c %p %fn", &p1[i], p1[i]);
}
}
- 为什么 openmp 的并行不适用于矢量化色彩空间转换?
- GCC 4.8.2 自动矢量化由于 cout 而失败
- 为什么浮点数的矢量化比双精度更有效?
- GCC、CLANG 和 MSVC 的可视化C++自动矢量化要求
- 如何使 msvc 矢量化浮点添加?
- 我可以期望某些 STL 函数实现是可自动矢量化的吗?
- 当在循环中使用时,std::shared_ptr 对该循环的矢量化有任何影响吗?
- 矢量化图像处理
- MSVC 2017 是否支持具有自动矢量化的 AVX 512
- 矢量化对称矩阵
- 如何在块复制期间矢量化范围检查
- 是否可以使用G 或Clang -OpenMP获得矢量化报告
- 错误的矢量化代码会影响可伸缩性吗?
- C 矢量化双回路
- 如何将现有的矢量化函数与Intel编译器自动化的现有标量函数相关
- 用于自动矢量化的展开指针增量循环
- 这是矢量化的良好实践吗
- 对于使用 C 样式指针矢量化的循环,但不使用迭代器
- SSE2 矢量化和虚拟机
- 为什么 GCC 不能矢量化这个函数和循环