【问题标题】:vector shift using pointers使用指针的矢量移位
【发布时间】:2012-11-16 05:46:34
【问题描述】:

我正在使用 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;

这样,操作将是恒定的,根本不需要任何操作。

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

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

【问题讨论】:

  • 您能出示您的 SSE 代码吗?很难准确地说出你在做什么。
  • 那段代码不会用零填充向量吗?
  • v 到底是什么?一个std::vector,一个数组,一个指针? v = (v-1) 在我看来不是个好主意。

标签: c++ pointers sse shift


【解决方案1】:

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 进行优化,并且您使用指向字节的指针执行一些任务,那么您很可能做错了什么,并且如果您尝试将 16 个v 加载到SSE 寄存器中,您就会遇到麻烦 -由于未对齐而导致的段错误或由于强制编译器使用movdqu而导致的性能损失。

【讨论】:

  • 哈哈。谢谢。我真正想到的是,我将完全绕过 SSE 指令,只是减少预分配内存中的指针。
  • 但是,我不知道 _mm_srli_si128,所以这可能是一个更可行的解决方案
【解决方案2】:

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

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

如果您使用 SIMD 代码来执行此操作,标准技术是(例如)将字节 0..15 加载到 xmm0,将 16..31 加载到 xmm1,然后将两个寄存器打乱以最终得到元素 1。 .16 英寸 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 指向分配的内存块中间(不是开头)的某个位置,那么递减就可以正常工作,但是您必须有一种方法来确保始终如此(例如,内存分配在将使用此技巧的同一函数中)。

【讨论】:

  • palignr 指令一次性完成您的 psrldq psrlldq por 序列。
  • 除此之外,您可能是对的,在大局中有些可疑之处:-)
  • 真的,非常好,谢谢@hirschhornsalz。自从我不得不使用 vpermilps 以外的任何东西以来已经有一段时间了 :) 我忘记了 palignr,它是一个不错的选择。
  • AVX2 中有一个vpalignrb :-)
【解决方案3】:

递减指针首先会导致对第 0 个元素的越界访问,并且它会使你的向量错位。向量操作,除了要正确对齐以提高性能的数据。如果数据未对齐,指令调度程序必须将从内存中读取分成两次读取,这会损失一些性能。

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

【讨论】:

  • 递减指向元素 0 的指针肯定不会导致“越界访问”。只有当它被取消引用时,它才会调用 UB,但即便如此,它也很可能会静默失败,不会崩溃。
  • 虽然对于较旧的 x86 CPU 是正确的,但 Nehalem 一代或更新的处理器能够执行未对齐的 128 位内存访问,与对齐的相比,没有任何损失。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-12-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多