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
Performance 交换内存中未对齐的64位值的字节的最快方法是什么?_Performance_Assembly_X86 64_Endianness_Micro Optimization - Fatal编程技术网

Performance 交换内存中未对齐的64位值的字节的最快方法是什么?

Performance 交换内存中未对齐的64位值的字节的最快方法是什么?,performance,assembly,x86-64,endianness,micro-optimization,Performance,Assembly,X86 64,Endianness,Micro Optimization,我在内存中有大量的64位值。不幸的是,它们可能没有与64位地址对齐。我的目标是更改所有这些值的尾数,即交换/反转它们的字节 我知道关于交换32位或64位寄存器字节的bswap指令。但由于它需要一个寄存器参数,所以我不能将内存地址传递给它。当然,我可以先将内存加载到寄存器中,然后交换,再写回: mov rax, qword [rsi] bswap rax mov qword [rsi], rax 但考虑到地址可能未对齐,这是否正确 另一种可能是手动进行互换: mov al, byte [rsi

我在内存中有大量的64位值。不幸的是,它们可能没有与64位地址对齐。我的目标是更改所有这些值的尾数,即交换/反转它们的字节

我知道关于交换32位或64位寄存器字节的
bswap
指令。但由于它需要一个寄存器参数,所以我不能将内存地址传递给它。当然,我可以先将内存加载到寄存器中,然后交换,再写回:

mov rax, qword [rsi]
bswap rax
mov qword [rsi], rax
但考虑到地址可能未对齐,这是否正确

另一种可能是手动进行互换:

mov al, byte [rsi + 0]
mov bl, byte [rsi + 7]
mov byte [rsi + 0], bl
mov byte [rsi + 7], al

mov al, byte [rsi + 1]
mov bl, byte [rsi + 6]
mov byte [rsi + 1], bl
mov byte [rsi + 6], al

mov al, byte [rsi + 2]
mov bl, byte [rsi + 5]
mov byte [rsi + 2], bl
mov byte [rsi + 5], al

mov al, byte [rsi + 3]
mov bl, byte [rsi + 4]
mov byte [rsi + 3], bl
mov byte [rsi + 4], al
这显然是更多的指示。但是它也慢了吗

但总的来说,我对x86-64还相当缺乏经验,所以我想知道:在内存中字节交换64位值的最快方法是什么?我描述的两个选项之一是最优的吗?还是有一种完全不同的更快的方法?

PS:我的真实情况有点复杂。我有一个大字节数组,但它包含不同大小的整数,都是密集的。另一个数组告诉我下一个整数的大小。所以这个“描述”可以说“一个32位整数,两个64位整数,一个16位整数,然后再一个64位整数”。我在这里提到这一点是为了告诉您(据我所知),使用SIMD指令是不可能的,因为我实际上必须在读取之前检查每个整数的大小

在内存中以字节交换64位值的最快方法是什么?

大多数英特尔处理器上的
mov/bswap/mov
版本和
movbe/mov
大致相同。基于µop计数,似乎
movbe
解码为
mov+bswap
,Atom上除外。对于Ryzen,
movbe
可能更好。手动交换字节的速度要慢得多,除非在某些边缘情况下,大型加载/存储非常慢,例如在Skylake之前跨越4K边界时

pshufb
是一个合理的选择,甚至可以替换单个
bswap
,尽管这浪费了shuffle可以完成的一半工作


PS:我的真实情况有点复杂。我有一个大字节数组,但它包含不同大小的整数,都是密集的

在这种情况下,动态地从其他数据流获取大小,一个新的大问题是大小分支。即使在可以避免的标量代码中,也可以通过字节反转64位块并将其右移
8-size
,然后将其与未反转的字节合并,并按
size
前进。这是可以解决的,但这样做是浪费时间的,SIMD版本会更好

SIMD版本可以使用
pshufb
和一个由“大小模式”索引的洗牌掩码表,例如一个8位整数,其中每2位表示一个元素的大小
pshufb
然后反转它正在查看的16字节窗口中完全包含的元素,并将其余元素放在一边(尾部未更改的字节也将被写回,但这没关系)。然后我们按实际处理的字节数前进

为了最大限度地方便起见,这些大小模式(以及相应的字节计数)应该以这样的方式提供,即实际的Endianness Flipper本身可以在每次迭代中使用其中一个模式,无需任何繁重的操作,例如提取一个未对齐的8位字节序列,并动态确定要消耗多少位。这也是可能的,但代价要高得多。在我的测试中,速度大约是原来的4倍,受循环相关性的限制,通过“在当前位索引提取8位”通过“通过查表查找查找位索引增量”,然后进入下一个迭代:每个迭代大约16个周期,尽管仍然是等效标量代码所用时间的60%

使用未打包(每个大小1字节)表示将使提取更容易(只是一个未对齐的dword加载),但需要打包结果以索引无序排列掩码表,例如使用
pext
。这对于英特尔CPU来说是合理的,但在AMD Ryzen上,
pext
速度非常慢。对于AMD和Intel来说,另一种合适的方法是进行未对齐的dword读取,然后使用乘法/移位技巧提取8个感兴趣的位:

mov eax, [rdi]
imul eax, eax, 0x01041040
shr eax, 24
至少在方便的输入情况下,应该使用一个额外的技巧(否则我们的性能会差5倍,而且这个技巧也不相关),即在存储当前迭代的结果之前读取下一次迭代的数据。如果没有这个技巧,存储常常会“踩到”下一次迭代的加载(因为我们前进了不到16个字节,所以加载读取一些存储保持不变但无论如何都必须写入的字节),迫使它们之间存在内存依赖关系,从而阻碍下一次迭代。性能差异很大,约为3倍

然后Endianness鳍状肢可以看起来像这样:

void flipEndiannessSSSE3(char* buffer, size_t totalLength, uint8_t* sizePatterns, uint32_t* lengths, __m128i* masks)
{
    size_t i = 0;
    size_t j = 0;
    __m128i data = _mm_loadu_si128((__m128i*)buffer);
    while (i < totalLength) {
        int sizepattern = sizePatterns[j];
        __m128i permuted = _mm_shuffle_epi8(data, masks[sizepattern]);
        size_t next_i = i + lengths[j++];
        data = _mm_loadu_si128((__m128i*)&buffer[next_i]);
        _mm_storeu_si128((__m128i*)&buffer[i], permuted);
        i = next_i;
    }
}

LLVM-MCA认为每次迭代大约需要3.3个周期,在我的PC上(4770K,使用1、2、4和8字节大小的元素进行统一混合测试),它稍微慢了一点,接近于每次迭代3.7个周期,但这仍然很好:每个元素的周期略低于1.2个。

您的第一种方法的正确性没有任何问题。x86允许对所有标量指令进行未对齐的内存访问,包括
mov
;它们可能只是比对齐的访问慢。如果您需要像您所说的那样使用该值执行其他操作,那么您可能无法避免加载和存储,因此
bswap
似乎是一种方法。另请参见两条说明:a
mov
然后a
movbe
,或者反之亦然?(如果有)使用SIMD是可能的。你需要的尺寸,但PSHUFB与洗牌面具从
    test    rsi, rsi
    je      .LBB0_3
    vmovdqu xmm0, xmmword ptr [rdi]
    xor     r9d, r9d
    xor     r10d, r10d
.LBB0_2:                            # =>This Inner Loop Header: Depth=1
    movzx   eax, byte ptr [rdx + r10]
    shl     rax, 4
    vpshufb xmm1, xmm0, xmmword ptr [r8 + rax]
    mov     eax, dword ptr [rcx + 4*r10]
    inc     r10
    add     rax, r9
    vmovdqu xmm0, xmmword ptr [rdi + rax]
    vmovdqu xmmword ptr [rdi + r9], xmm1
    mov     r9, rax
    cmp     rax, rsi
    jb      .LBB0_2
.LBB0_3:
    ret