2015-05-27 47 views
2

我有一个由32个字节组成的数组。我需要从这个数组中构建8个4字节的整数。例如 0x00,0x11,0x22,0x33 8位整数必须是一个0x00112233 32位整数。 我决定使用AVX指令,因为我可以用一个命令将整个数组加载到寄存器。如何使用Intel内在函数从8位整数数组构建32位整数?

代码我写道:

#include <stdio.h> 
#include "immintrin.h" 

typedef unsigned int  uint32_t; 
typedef unsigned char  uint8_t; 

main() { 
    const uint8_t block[32] __attribute((aligned(32))) = { 
    0x00,0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88,0x99,0xaa,0xbb,0xcc,0xdd,0xee,0xff 
    ,0x00,0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88,0x99,0xaa,0xbb,0xcc,0xdd,0xee,0xff 
    }; 
    uint32_t m[8] __attribute((aligned(32))); 

    __m256i ymm9 = _mm256_set_epi8(
     block[ 0],block[ 1],block[ 2],block[ 3],block[ 4],block[ 5],block[ 6],block[ 7], 
     block[ 8],block[ 9],block[10],block[11],block[12],block[13],block[14],block[15], 
     block[16],block[17],block[18],block[19],block[20],block[21],block[22],block[23], 
     block[24],block[25],block[26],block[27],block[28],block[29],block[30],block[31]); 
    _mm256_store_si256(&(m[0]),ymm9); 
    int i; 
    for(i=0;i<32;++i) printf("i=%d, 0x%02x\n",i,block[i]); 
    for(i=0;i<8;++i) printf("i=%d, 0x%08x\n",i,m[i]); 
} 

你是否认为这是最佳的在性能方面?它可以做得更好,跑得更快吗?我使用Linux @ x86_64和gcc 4.8.2。

我是英特尔内部世界的初学者。谢谢你的帮助。

+0

pre meture优化,只是memcpy – user3528438

+0

为什么不使用简单的位移? –

+0

你确定你想要big-endian语义吗? – chqrlie

回答

3

像往常一样,检查拆卸。然后,事实证明,无论如何,我使用的编译器依赖于该数据是一个编译时间常量,并重新排列它以便可以轻松加载。如果这是实际上在你的真实代码的情况下,这是没有问题的(但为什么不使用数组开头?)。但是,如果我怀疑这是,这仅仅是一个例子,实际的阵列是可变的,这是一场灾难,只要看看它:

movzx eax, BYTE PTR [rsp+95] 
xor ebx, ebx 
mov BYTE PTR [rsp], al 
movzx eax, BYTE PTR [rsp+93] 
vmovd xmm7, DWORD PTR [rsp] 
vpinsrb xmm7, xmm7, BYTE PTR [rsp+94], 1 
mov BYTE PTR [rsp], al 
movzx eax, BYTE PTR [rsp+91] 
vmovd xmm3, DWORD PTR [rsp] 
vpinsrb xmm3, xmm3, BYTE PTR [rsp+92], 1 
mov BYTE PTR [rsp], al 
movzx eax, BYTE PTR [rsp+89] 
vmovd xmm1, DWORD PTR [rsp] 
vpinsrb xmm1, xmm1, BYTE PTR [rsp+90], 1 
mov BYTE PTR [rsp], al 
movzx eax, BYTE PTR [rsp+87] 
vmovd xmm6, DWORD PTR [rsp] 
vpunpcklwd xmm3, xmm7, xmm3 
vpinsrb xmm6, xmm6, BYTE PTR [rsp+88], 1 
mov BYTE PTR [rsp], al 
movzx eax, BYTE PTR [rsp+85] 
vmovd xmm5, DWORD PTR [rsp] 
vpinsrb xmm5, xmm5, BYTE PTR [rsp+86], 1 
mov BYTE PTR [rsp], al 
movzx eax, BYTE PTR [rsp+83] 
vmovd xmm2, DWORD PTR [rsp] 
vpunpcklwd xmm1, xmm1, xmm6 
vpinsrb xmm2, xmm2, BYTE PTR [rsp+84], 1 
mov BYTE PTR [rsp], al 
movzx eax, BYTE PTR [rsp+81] 
vmovd xmm0, DWORD PTR [rsp] 
vpunpckldq xmm1, xmm3, xmm1 
vpinsrb xmm0, xmm0, BYTE PTR [rsp+82], 1 
mov BYTE PTR [rsp], al 
movzx eax, BYTE PTR [rsp+79] 
vmovd xmm4, DWORD PTR [rsp] 
vpunpcklwd xmm2, xmm5, xmm2 
vpinsrb xmm4, xmm4, BYTE PTR [rsp+80], 1 
mov BYTE PTR [rsp], al 
movzx eax, BYTE PTR [rsp+77] 
vmovd xmm8, DWORD PTR [rsp] 
vpinsrb xmm8, xmm8, BYTE PTR [rsp+78], 1 
mov BYTE PTR [rsp], al 
movzx eax, BYTE PTR [rsp+75] 
vpunpcklwd xmm0, xmm0, xmm4 
vmovd xmm4, DWORD PTR [rsp] 
vpinsrb xmm4, xmm4, BYTE PTR [rsp+76], 1 
mov BYTE PTR [rsp], al 
movzx eax, BYTE PTR [rsp+73] 
vpunpckldq xmm0, xmm2, xmm0 
vmovd xmm2, DWORD PTR [rsp] 
vpinsrb xmm2, xmm2, BYTE PTR [rsp+74], 1 
mov BYTE PTR [rsp], al 
movzx eax, BYTE PTR [rsp+71] 
vmovd xmm7, DWORD PTR [rsp] 
vpunpcklqdq xmm1, xmm1, xmm0 
vpunpcklwd xmm4, xmm8, xmm4 
vpinsrb xmm7, xmm7, BYTE PTR [rsp+72], 1 
mov BYTE PTR [rsp], al 
movzx eax, BYTE PTR [rsp+69] 
vmovd xmm6, DWORD PTR [rsp] 
vpinsrb xmm6, xmm6, BYTE PTR [rsp+70], 1 
mov BYTE PTR [rsp], al 
movzx eax, BYTE PTR [rsp+67] 
vmovd xmm0, DWORD PTR [rsp] 
vpunpcklwd xmm2, xmm2, xmm7 
vpinsrb xmm0, xmm0, BYTE PTR [rsp+68], 1 
mov BYTE PTR [rsp], al 
movzx eax, BYTE PTR [rsp+65] 
vmovd xmm5, DWORD PTR [rsp] 
vpunpckldq xmm2, xmm4, xmm2 
vpinsrb xmm5, xmm5, BYTE PTR [rsp+66], 1 
mov BYTE PTR [rsp], al 
vmovd xmm3, DWORD PTR [rsp] 
vpunpcklwd xmm0, xmm6, xmm0 
vpinsrb xmm3, xmm3, BYTE PTR [rsp+64], 1 
vpunpcklwd xmm3, xmm5, xmm3 
vpunpckldq xmm0, xmm0, xmm3 
vpunpcklqdq xmm0, xmm2, xmm0 
vinserti128 ymm0, ymm1, xmm0, 0x1 
vmovdqa YMMWORD PTR [rsp+32], ymm0 

哇。好吧,不太好。事实上,如果同样的事情没有内在因素,但并非全部都失败了,那就更糟了。这将是更好地加载数据作为小端的uint,然后交换他们周围有_mm256_shuffle_epi8,有点像这样(但检查洗牌面膜,我没有测试)

__m256i ymm9 = _mm256_shuffle_epi8(_mm256_load_si256((__m256i*)block), _mm256_set_epi8(
    0, 1, 2, 3, 
    4, 5, 6, 7, 
    8, 9, 10, 11, 
    12, 13, 14, 15, 
    0, 1, 2, 3, 
    4, 5, 6, 7, 
    8, 9, 10, 11, 
    12, 13, 14, 15)); 
ymm9 = _mm256_permute2x128_si256(ymm9, ymm9, 1); 
_mm256_store_si256((__m256i*)m, ymm9); 

一般情况下,要非常小心“内置”系列内部函数,它们可以编译成非常糟糕的指令序列。

+0

感谢您的重播可惜的是,我不能使用AVX2指令:-(但我展示,目前的解决方案是不是好是非常有帮助,我会看得更远。。 – baton

+0

@baton,使用SSE和洗牌呢。 –

+0

@Zboson我希望这是我做了以下 – baton

1

感谢所有的评论。特别是哈罗德和Zboson的。

这是我的第二次尝试:

const uint8_t block[32] __attribute((aligned(32))) = { 
    0x00,0x11,0x22,0x33,0x44,0x55,0x66,0x77, 
    0x88,0x99,0xaa,0xbb,0xcc,0xdd,0xee,0xff, 
    0x00,0x11,0x22,0x33,0x44,0x55,0x66,0x77, 
    0x88,0x99,0xaa,0xbb,0xcc,0xdd,0xee,0xff}; 
uint32_t m[8] __attribute((aligned(32))); 
const uint8_t maska[16] __attribute((aligned(16))) = { 
    0x0F,0x0E,0x0D,0x0C,0x0B,0x0A,0x09,0x08, 
    0x07,0x06,0x05,0x04,0x03,0x02,0x01,0x00}; 
__m128i mask = _mm_load_si128(maska); 
__m128i xmm0 = _mm_load_si128(block); 
_mm_store_si128((__m128i*)&(m[0]),_mm_shuffle_epi8(xmm0, mask)); 
xmm0 = _mm_load_si128(block+16); 
_mm_store_si128((__m128i*)&(m[4]),_mm_shuffle_epi8(xmm0, mask)); 

,你怎么看待这个问题?我很确定有一个改进的余地。我不知道_mm_load_si128是从内存中复制数据到注册的最佳方式。第一次迭代的汇编程序:

/* Create a vector with element 0 as *P and the rest zero. */ 

extern __inline __m128i __attribute__((__gnu_inline__, __always_inline__, __artificial__)) 
_mm_load_si128 (__m128i const *__P) 
{ 
return *__P; 
mov 0x8(%rsp),%rax 
vmovdqa (%rax),%xmm0 
    0x0F,0x0E,0x0D,0x0C,0x0B,0x0A,0x09,0x08, 
    0x07,0x06,0x05,0x04,0x03,0x02,0x01,0x00}; 
__m128i mask = _mm_load_si128(maska); 
vmovdqa %xmm0,0x30(%rsp) 
lea 0xf0(%rsp),%rax 
mov %rax,0x10(%rsp) 
mov 0x10(%rsp),%rax 
vmovdqa (%rax),%xmm0 
__m128i xmm0 = _mm_load_si128(block); 
vmovdqa %xmm0,0x40(%rsp) 
vmovdqa 0x40(%rsp),%xmm0 
vmovdqa %xmm0,0x50(%rsp) 
vmovdqa 0x30(%rsp),%xmm0 
vmovdqa %xmm0,0x60(%rsp) 
} 

extern __inline __m128i __attribute__((__gnu_inline__, __always_inline__, __artificial__)) 
_mm_shuffle_epi8 (__m128i __X, __m128i __Y) 
{ 
return (__m128i) __builtin_ia32_pshufb128 ((__v16qi)__X, (__v16qi)__Y); 
vmovdqa 0x60(%rsp),%xmm1 
vmovdqa 0x50(%rsp),%xmm0 
vpshufb %xmm1,%xmm0,%xmm0 
lea 0xb0(%rsp),%rax 
mov %rax,0x18(%rsp) 
vmovdqa %xmm0,0x70(%rsp) 
} 

extern __inline void __attribute__((__gnu_inline__, __always_inline__, __artificial__)) 
_mm_store_si128 (__m128i *__P, __m128i __B) 
{ 
*__P = __B; 
mov 0x18(%rsp),%rax 
vmovdqa 0x70(%rsp),%xmm0 
vmovdqa %xmm0,(%rax) 
lea 0xf0(%rsp),%rax 
add $0x10,%rax 
mov %rax,0x20(%rsp) 

您怎么看?

+0

你不应该还掉高16个字节的低16个字节?原代码这样做。这就是虽然琐碎的变化。代码本身看起来不错,但我认为你在那里反汇编了错误的函数 – harold

+0

我以2个步骤对所有32个字节进行混洗 - 每个16字节。 – baton

+0

是的,但我的意思是,不应该从源头的前16个字节进入目标的第二个16字节?在你第一篇文章中的那个大的_mm256_set_epi8中,你从顶部 – harold