Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/assembly/6.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Assembly 如何在AVX寄存器上打包16位寄存器/变量_Assembly_X86_Sse_Avx_Inline Assembly - Fatal编程技术网

Assembly 如何在AVX寄存器上打包16位寄存器/变量

Assembly 如何在AVX寄存器上打包16位寄存器/变量,assembly,x86,sse,avx,inline-assembly,Assembly,X86,Sse,Avx,Inline Assembly,我使用内联汇编,我的代码如下: __m128i inl = _mm256_castsi256_si128(in); __m128i inh = _mm256_extractf128_si256(in, 1); __m128i outl, outh; __asm__( "vmovq %2, %%rax \n\t" "movzwl %%ax, %%ecx \n\t" "shr $16, %

我使用内联汇编,我的代码如下:

__m128i inl = _mm256_castsi256_si128(in);
__m128i inh = _mm256_extractf128_si256(in, 1); 
__m128i outl, outh;
__asm__(
    "vmovq %2, %%rax                        \n\t"
    "movzwl %%ax, %%ecx                     \n\t"
    "shr $16, %%rax                         \n\t"
    "movzwl %%ax, %%edx                     \n\t"
    "movzwl s16(%%ecx, %%ecx), %%ecx        \n\t"
    "movzwl s16(%%edx, %%edx), %%edx        \n\t"
    "xorw %4, %%cx                          \n\t"
    "xorw %4, %%dx                          \n\t"
    "rolw $7, %%cx                          \n\t"
    "rolw $7, %%dx                          \n\t"
    "movzwl s16(%%ecx, %%ecx), %%ecx        \n\t"
    "movzwl s16(%%edx, %%edx), %%edx        \n\t"
    "pxor %0, %0                            \n\t"
    "vpinsrw $0, %%ecx, %0, %0              \n\t"
    "vpinsrw $1, %%edx, %0, %0              \n\t"

: "=x" (outl), "=x" (outh)
: "x" (inl), "x" (inh), "r" (subkey)
: "%rax", "%rcx", "%rdx"
);
我在代码中省略了一些vpinsrw,这或多或少是为了说明原理。实际代码使用16个vpinsrw操作。但是输出与预期不匹配

b0f0 849f 446b 4e4e e553 b53b 44f7 552b 67d  1476 a3c7 ede8 3a1f f26c 6327 bbde
e553 b53b 44f7 552b    0    0    0    0 b4b3 d03e 6d4b c5ba 6680 1440 c688 ea36
第一行是正确答案,第二行是我的结果。 C代码如下:

for(i = 0; i < 16; i++)
{  
    arr[i] = (u16)(s16[arr[i]] ^ subkey);
    arr[i] = (arr[i] << 7) | (arr[i] >> 9);
    arr[i] = s16[arr[i]];

}

内联汇编程序与C代码稍有相似之处,因此我倾向于假设这两个代码是相同的

这主要是一种观点,但我建议使用扩展汇编程序,而不是使用扩展汇编程序。内部函数允许编译器进行寄存器分配和变量优化,以及可移植性——在没有目标指令集的情况下,每个向量操作都可以由函数模拟

下一个问题是内联源代码似乎只处理两个索引的替换块
arr[i]=s16[arr[i]
。使用AVX2,这应该通过两个聚集操作来完成,因为Y寄存器只能容纳8个uint32或到查找表的偏移量,或者当它可用时,替换阶段应该由可以并行运行的分析函数来执行

使用intrinsic,该操作可能看起来像这样

__m256i function(uint16_t *input_array, uint16_t subkey) {
  __m256i array = _mm256_loadu_si256((__m256i*)input_array);
          array = _mm256_xor_si256(array, _mm256_set_epi16(subkey));
  __m256i even_sequence = _mm256_and_si256(array, _mm256_set_epi32(0xffff));
  __m256i odd_sequence = _mm256_srli_epi32(array, 16);
  even_sequence = _mm256_gather_epi32(LUT, even_sequence, 4);
  odd_sequence = _mm256_gather_epi32(LUT, odd_sequence, 4);
  // rotate
  __m256i hi = _mm256_slli_epi16(even_sequence, 7);
  __m256i lo = _mm256_srli_epi16(even_sequence, 9);
  even_sequence = _mm256_or_si256(hi, lo);
  // same for odd
  hi = _mm256_slli_epi16(odd_sequence, 7);
  lo = _mm256_srli_epi16(odd_sequence, 9);
  odd_sequence = _mm256_or_si256(hi, lo);
  // Another substitution
  even_sequence = _mm256_gather_epi32(LUT, even_sequence, 4);
  odd_sequence = _mm256_gather_epi32(LUT, odd_sequence, 4);
  // recombine -- shift odd by 16 and OR
  odd_sequence = _mm256_slli_epi32(odd_sequence, 16);
  return _mm256_or_si256(even_sequence, odd_sequence);
}


通过优化,一个好的编译器将为每个语句生成大约一条汇编指令;如果不进行优化,所有中间变量都会溢出到堆栈中,以便轻松调试。

内联汇编程序与C代码稍有相似,因此我倾向于假设这两个变量是相同的

这主要是一种观点,但我建议使用扩展汇编程序,而不是使用扩展汇编程序。内部函数允许编译器进行寄存器分配和变量优化,以及可移植性——在没有目标指令集的情况下,每个向量操作都可以由函数模拟

下一个问题是内联源代码似乎只处理两个索引的替换块
arr[i]=s16[arr[i]
。使用AVX2,这应该通过两个聚集操作来完成,因为Y寄存器只能容纳8个uint32或到查找表的偏移量,或者当它可用时,替换阶段应该由可以并行运行的分析函数来执行

使用intrinsic,该操作可能看起来像这样

__m256i function(uint16_t *input_array, uint16_t subkey) {
  __m256i array = _mm256_loadu_si256((__m256i*)input_array);
          array = _mm256_xor_si256(array, _mm256_set_epi16(subkey));
  __m256i even_sequence = _mm256_and_si256(array, _mm256_set_epi32(0xffff));
  __m256i odd_sequence = _mm256_srli_epi32(array, 16);
  even_sequence = _mm256_gather_epi32(LUT, even_sequence, 4);
  odd_sequence = _mm256_gather_epi32(LUT, odd_sequence, 4);
  // rotate
  __m256i hi = _mm256_slli_epi16(even_sequence, 7);
  __m256i lo = _mm256_srli_epi16(even_sequence, 9);
  even_sequence = _mm256_or_si256(hi, lo);
  // same for odd
  hi = _mm256_slli_epi16(odd_sequence, 7);
  lo = _mm256_srli_epi16(odd_sequence, 9);
  odd_sequence = _mm256_or_si256(hi, lo);
  // Another substitution
  even_sequence = _mm256_gather_epi32(LUT, even_sequence, 4);
  odd_sequence = _mm256_gather_epi32(LUT, odd_sequence, 4);
  // recombine -- shift odd by 16 and OR
  odd_sequence = _mm256_slli_epi32(odd_sequence, 16);
  return _mm256_or_si256(even_sequence, odd_sequence);
}

通过优化,一个好的编译器将为每个语句生成大约一条汇编指令;如果没有优化,所有中间变量都会溢出到堆栈中,以便轻松调试。

一个Skylake(聚集速度很快),使用Aki的答案将两个聚集链接在一起可能是一个胜利。这可以让你使用向量整数的东西非常有效地进行旋转

在Haswell上,继续使用标量代码可能会更快,这取决于周围代码的外观。(或者用矢量代码执行矢量旋转+xor操作仍然是一个成功。试试看。)

您有一个非常糟糕的性能错误,还有几个其他问题:

"pxor %0, %0                            \n\t"
"vpinsrw $0, %%ecx, %0, %0              \n\t"
使用传统SSE
pxor
%0
的低位128b归零,同时保持高位128b不变,将导致Haswell受到SSE-AVX过渡处罚;我想在
pxor
和第一个
vpinsrw
上各有大约70个循环,并且有一个虚假的依赖关系

相反,使用
vmovd%%ecx,%0
,将向量reg的上部字节归零(从而打破对旧值的依赖)

实际上,使用

"vmovd        s16(%%rcx, %%rcx), %0       \n\t"   // leaves garbage in element 1, which you over-write right away
"vpinsrw  $1, s16(%%rdx, %%rdx), %0, %0   \n\t"
...
当您可以直接插入向量时,将指令(和UOP)加载到整数寄存器,然后再从整数寄存器进入向量,这是一种巨大的浪费

您的索引已经是零扩展的,所以我使用64位寻址模式来避免在每条指令上浪费地址大小前缀。(由于您的表是静态的,它位于低2G的虚拟地址空间中(在默认的代码模型中),因此32位寻址确实有效,但它没有给您带来任何好处。)

不久前,我尝试将标量LUT结果(对于GF16乘法)转换为向量,并针对Intel Sandybridge进行调优。不过,我没有像你那样把LUT查找链接起来。看见当我发现GF16作为4位LUT使用时效率更高,但不管怎样,我发现如果没有收集指令,从内存到向量的
pinsrw
是好的

您可能希望通过同时在两个向量上交错操作来提供更多ILP。或者甚至可能进入4个向量的低64b,并与
vpunpcklqdq
结合。(
vmovd
vpinsrw
更快,因此在uop吞吐量上几乎是收支平衡。)


这些可以而且应该是
xor%[subkey],%%ecx
。32位操作数大小在这里更有效,只要输入没有在上16位中设置任何位,就可以正常工作。使用
[subkey]“ri”(subkey)
约束允许在编译时已知立即值。(这可能更好,并且稍微降低了寄存器压力,但由于您多次使用它,因此以牺牲代码大小为代价。)

但是
rolw
指令必须保持16位

您可以考虑将两个或四个值打包成整数登记器(用<代码> MOVZWL S16(…))、%%ECX < />代码> SHL $ 16、%%ECX < /C> > />代码> MOVS16(…)、%CX/<代码> SL$ 16、%%RCX < /代码>……,但是您必须用移位/ /或掩蔽来模拟旋转。然后再次解包以将其作为索引重用

在两个LUT查找之间出现整数太糟糕了,否则在解包之前可以在向量中进行


您可以选择提取16b向量块的策略,这看起来非常好
movdq
从xmm到GP寄存器在Haswell/Skylake上的端口0上运行,并且
shr
/
ror
"xorw %4, %%cx                          \n\t"
"xorw %4, %%dx                          \n\t"
// This probably compiles to code like your inline asm
#include <x86intrin.h>
#include <stdint.h>

extern const uint16_t s16[];

__m256i LUT_elements(__m256i in)
{
    __m128i inl = _mm256_castsi256_si128(in);
    __m128i inh = _mm256_extractf128_si256(in, 1);

    unsigned subkey = 8;
    uint64_t low4 = _mm_cvtsi128_si64(inl);  // movq extract the first elements
    unsigned idx = (uint16_t)low4;
    low4 >>= 16;

    idx = s16[idx] ^ subkey;
    idx = __rolw(idx, 7);
    // cast to a 32-bit pointer to convince gcc to movd directly from memory
    // the strict-aliasing violation won't hurt since the table is const.

    __m128i outl = _mm_cvtsi32_si128(*(const uint32_t*)&s16[idx]);

    unsigned idx2 = (uint16_t)low4;
    idx2 = s16[idx2] ^ subkey;
    idx2 = __rolw(idx2, 7);
    outl = _mm_insert_epi16(outl, s16[idx2], 1);

    // ... do the rest of the elements

    __m128i outh = _mm_setzero_si128();  // dummy upper half
    return _mm256_inserti128_si256(_mm256_castsi128_si256(outl), outh, 1);
}
// pointer-width integers don't need to be re-extended
// but since gcc doesn't understand the asm, it thinks the whole 64-bit result may be non-zero
static inline
uintptr_t my_rolw(uintptr_t a, int count) {
    asm("rolw %b[count], %w[val]" : [val]"+r"(a) : [count]"ic"(count));
    return a;
}
if (x > 65535)
    __builtin_unreachable();