转换4x4字节矩阵的最快方法

时间:2014-07-18 03:20:47

标签: c++ c optimization matrix bit-manipulation

我有一个4x4字节块,我想使用通用硬件进行转置。换句话说,对于字节A-P,我正在寻找最有效的(就指令数而言)的方式来自

A B C D
E F G H
I J K L
M N O P

A E I M
B F J N
C G K O
D H L P

我们可以假设我在内存中指向AEIM的有效指针(这样从A读取32位将会得到)我是包含字节ABCD)的整数。

由于对大小和数据类型的限制,这不是this question的重复。我的矩阵的每一行都可以放入一个32位整数,我正在寻找可以使用通用硬件快速执行转置的答案,类似于SSE宏_MM_TRANSPOSE4_PS的实现。

5 个答案:

答案 0 :(得分:11)

您想要适​​销性和效率。那么你不可能两种方式。你说你想用最少的指令来做这件事。好吧,只需使用SSS3的一条指令,使用x86指令集中的pshufb指令(见下文)就可以做到这一点。

也许ARM Neon有同等的东西。如果你想要效率(并确定你需要它),那么学习硬件。

字节的_MM_TRANSPOSE4_PS的SSE等价物是使用掩码使用_mm_shuffle_epi8(pshufb的内在函数)。在主循环外定义掩码。

//use -msse3 with GCC or /arch:SSE2 with MSVC
#include <stdio.h>
#include <tmmintrin.h> //SSSE3
int main() {
    char x[] = {0,1,2,3, 4,5,6,7, 8,9,10,11, 12,13,15,16};
    __m128i mask = _mm_setr_epi8(0x0,0x04,0x08,0x0c, 0x01,0x05,0x09,0x0d, 0x02,0x06,0x0a,0x0e, 0x03,0x07,0x0b,0x0f);

    __m128i v = _mm_loadu_si128((__m128i*)x);
    v = _mm_shuffle_epi8(v,mask);
    _mm_storeu_si128((__m128i*)x,v);
    for(int i=0; i<16; i++) printf("%d ", x[i]); printf("\n");
    //output: 0 4 8 12 1 5 9 13 2 6 10 15 3 7 11 16   
}

答案 1 :(得分:8)

让我重新提一下你的问题:你要求的是只有C或C ++的便携式解决方案。然后:

void transpose(uint32_t const in[4], uint32_t out[4]) {
  // A B C D    A E I M
  // E F G H    B F J N
  // I J K L    C G K O
  // M N O P    D H L P

  out[0] = in[0] & 0xFF000000U; // A . . .
  out[1] = in[1] & 0x00FF0000U; // . F . .
  out[2] = in[2] & 0x0000FF00U; // . . K .
  out[3] = in[3] & 0x000000FFU; // . . . P

  out[1] |= (in[0] <<  8) & 0xFF000000U; // B F . .
  out[2] |= (in[0] << 16) & 0xFF000000U; // C . K .
  out[3] |= (in[0] << 24);               // D . . P

  out[0] |= (in[1] >>  8) & 0x00FF0000U; // A E . .
  out[2] |= (in[1] <<  8) & 0x00FF0000U; // C G K .
  out[3] |= (in[1] << 16) & 0x00FF0000U; // D H . P

  out[0] |= (in[2] >> 16) & 0x0000FF00U; // A E I .
  out[1] |= (in[2] >>  8) & 0x0000FF00U; // B F J .
  out[3] |= (in[2] <<  8) & 0x0000FF00U; // D H L P

  out[0] |= (in[3] >> 24);               // A E I M
  out[1] |= (in[3] >>  8) & 0x000000FFU; // B F J N
  out[2] |= (in[3] <<  8) & 0x000000FFU; // C G K O
}

我不知道如何以任何其他方式回答它,因为那时你将依赖于特定编译器以特定方式编译它等等。

当然,如果这些操作本身可以以某种方式简化,那么它会有所帮助。所以这是进一步追求的唯一途径。到目前为止,没有什么是突出的,但对我来说这是漫长的一天。

到目前为止,成本为12班,12个OR,16个AND。如果编译器和平台有任何好处,可以在9个32位寄存器中完成。

如果编译器非常难过,或者平台没有桶形移位器,那么某些转换可以帮助推断移位和掩码只是字节提取这一事实:

void transpose(uint8_t const in[16], uint8_t out[16]) {
  // A B C D    A E I M
  // E F G H    B F J N
  // I J K L    C G K O
  // M N O P    D H L P

  out[0]  = in[0];  // A . . .
  out[1]  = in[4];  // A E . .
  out[2]  = in[8];  // A E I .
  out[3]  = in[12]; // A E I M
  out[4]  = in[1];  // B . . .
  out[5]  = in[5];  // B F . .
  out[6]  = in[9];  // B F J .
  out[7]  = in[13]; // B F J N
  out[8]  = in[2];  // C . . .
  out[9]  = in[6];  // C G . .
  out[10] = in[10]; // C G K .
  out[11] = in[14]; // C G K O
  out[12] = in[3];  // D . . .
  out[13] = in[7];  // D H . .
  out[14] = in[11]; // D H L .
  out[15] = in[15]; // D H L P
}

如果你真的想把它随意洗牌,那么下面就可以了。

void transpose(uint8_t m[16]) {
  std::swap(m[1], m[4]);
  std::swap(m[2], m[8]);
  std::swap(m[3], m[12]);
  std::swap(m[6], m[9]);
  std::swap(m[7], m[13]);
  std::swap(m[11], m[14]);
}

面向字节的版本可能会在现代平台上产生更糟糕的代码。只有基准才能证明。

答案 2 :(得分:1)

不确定速度,但这些都没关系。

template<typename T, std::size_t Size>
void Transpose(T (&Data)[Size][Size])
{
    for (int I = 0; I < Size; ++I)
    {
        for (int J = 0; J < I; ++J)
        {
            std::swap(Data[I][J], Data[J][I]);
        }
    }
}

template<typename T, std::size_t Size>
void Transpose(T (&Data)[Size * Size])
{
    for (int I = 0; I < Size; ++I)
    {
        for (int J = 0; J < I; ++J)
        {
            std::swap(Data[I * Size + J], Data[J * Size + I]);
        }
    }
}

答案 3 :(得分:1)

如果您接受,可以在64位机器上实现有效的解决方案。 首先将32位整数常量分别移位(0,)1,2和3字节[3 shitfs]。然后屏蔽掉不需要的位并执行逻辑OR [12个AND与常量,12个OR]。最后,转回32位[3个移位]并读出32位。

ABCD
EFGH
IJKL
MNOP

ABCD
 EFGH
  IJKL
   MNOP

A---
 E---
  I---
   MNOP
=======
AEIMNOP
AEIM

AB--
 -F--
  -J--
   -NOP
=======
ABFJNOP
BFJN

ABC-
 --G-
  --K-
   --OP
=======
ABCGKOP
CGKO

ABCD
 ---H
  ---L
   ---P
=======
ABCDHLP
DHLP

答案 4 :(得分:1)

前一段时间我为SSE ​​here发布了同样问题的答案。

唯一需要添加的是矢量化加载/存储操作。

此答案与Z boson's answer to this question类似。可以在那里看到加载/存储的示例。这个答案不同,因为除了SSE3实现之外,还有一个SSE2实现,可以保证在任何x64处理器上运行。

值得注意的是,这两个解决方案都假设整个矩阵在内存中都是行主要的,但是OP的问题表明每行可以拥有它自己的指针,这意味着数组可能是支离破碎的。