c - 如何使用英特尔内部函数从 8 位整数数组构建 32 位整数?

标签 c intrinsics avx

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

我写的代码:

#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。

我是英特尔内在函数领域的初学者。感谢您的帮助。

最佳答案

像往常一样,检查反汇编。然后事实证明,无论如何我使用的编译器,它依赖于该数据是一个编译时间常数,并且它重新排列它以便它可以轻松加载。如果在您的真实代码中确实是这种情况,那很好(但是为什么不使用一组 uint 开始呢?)。但是,如果正如我所怀疑的那样,这只是一个示例,而实际的数组是可变的,那么这就是一场灾难,看看它:

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

哇。好吧,不太好。确实比没有内在函数做同样的事情更糟糕,但并非所有都丢失了。最好将数据加载为 little endian 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);

一般来说,要非常小心“set”系列的内在函数,它们可以编译成非常糟糕的指令序列。

关于c - 如何使用英特尔内部函数从 8 位整数数组构建 32 位整数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30492092/

相关文章:

c++ - SIMD 内部函数 : aligned operation different than unaligned?

用于灰度到 ARGB 转换的 C++ SSE2 或 AVX2 内在函数

c - AVX 加载指令在 cygwin 上失败

c - OpenMP 中 private() 子句中变量的最大数量

c - 从C中的字符串中获取子字符串

c++ - AVX2 Gather 指令使用细节

c++ - 从 __m256 选择元素子集?

c++ - 我的向量化 xorshift+ 不是很随机

c - 查找数组中元素的最高频率(有时有效)

c - 1 字节有符号数的 printf 格式