矢量移位使用指针

时间:2012-11-16 05:46:34

标签: c++ pointers sse shift

我正在使用SSE3优化我的代码。代码中有一点迫使我将向量中的所有元素移动一个元素

v[0] = 0   //v is some char* and N = v.size()
for(int i = 1;i<N;i++){
    v[i] = v[i-1];
}

据我所知,SSE不支持向量移位,所以我必须从头开始编码。

但后来我有了想法,如果我只是递减指针会怎么样。

v = (v-1); 
v[0] = 0;

通过这种方式,操作将是恒定的,根本不需要任何操作。

我已经测试了这个,它适用于我的测试程序 但是,我不确定这个操作是否安全。

这是一个非常愚蠢的想法吗?

3 个答案:

答案 0 :(得分:4)

SSE支持移位,无论是向量内的元素按位移位还是沿字节边界移位整个寄存器。

假设您的矢量类型为uint8_t的16倍,您正在寻找的操作是

psrldq xmm, 1      ;packed shift right logical double quad word

内在

vec = _mm_srli_si128(vec, 1);   // shift by 1 byte

对于你的第一个问题:只要v是指向char的指针,递减或递增就完全安全了。取消引用可能不会,这取决于您的计划。

对于你的第二个问题:是的,它看起来像一个愚蠢的想法。如果您尝试使用SSE进行优化并执行某些带字节指针的任务,那么您最有可能做错了,如果您尝试将v中的16个加载到{{}}中,则会遇到麻烦{1}}注册 - 由于强制编译器使用SSE而导致错位或性能下降导致的段错误。

答案 1 :(得分:2)

最简单的答案:使用memmove(v + 1,v,N-1)代替您发布的循环。这可能与任何体面系统上的手动编码汇编一样快,因为手动编码汇编,使用movdqu / movdqa / movntdqa和循环展开的适当组合。

更复杂的答案:我认为,从更大的角度来看,你实际上不太可能需要转移数据。更有可能的是,您可能需要访问相邻元素和当前元素,例如对v [i]和v [i-1]进行某种计算。

如果您使用SIMD代码来执行此操作,标准技术是(例如)将字节0..15加载到xmm0,将16..31加载到xmm1中,然后将两个寄存器混洗以最终得到元素1。 .16 in xmm2。然后你可以用xmm0(这里对应矢量化v [i-1])和xmm2(矢量化v [i])进行计算。这不是逻辑/算术移位意义上的“移位”,而是SIMD车道移位。

示例:在程序集中使用字节

movdqa mem, xmm0 // load bytes 0..15
loop:
// increment mem by 16
movdqa mem, xmm1 // load bytes 16..31
movdqa xmm0, xmm2 // make a copy
movdqa xmm1, xmm3 // make a copy
psrldq xmm2, 1 // ends up with bytes 1..15 and a zero
pslldq xmm3, 15 // ends up with zeros and byte 16
por xmm2, xmm3 // ends up with bytes 1..16
// do something with xmm3 and xmm0 here, they contain bytes 1..16 and 0..15 respectively
// in other words xmm3 is a lane-shifted
movdqa xmm1, xmm0 // use our copy of bytes 16..31 to continue the loop
// goto loop

为什么不这样做:“如果我只是递减指针怎么办... v =(v-1);”

这会崩溃:

char* v = (char*)malloc(...);
v=(v-1);
v[0] = 0; // or any read or write of v[0]

如果v指向分配内存块的中间(不是开头)的某个地方,那么减量将正常工作,但你必须有办法确保始终如此(例如,内存分配在将使用此技巧的相同功能中。

答案 2 :(得分:0)

递减指针将首先导致第0个元素的越界访问,它将使您的向量不对齐。矢量操作除了要正确对齐的数据以便执行。如果数据没有对齐,指令调度程序必须将读取内存分成两次读取,从而使您失去一些性能。

SSE对整个向量提供位移操作,请参阅@hirschhornsalz'回答。