XMM或YMM寄存器中的字节顺序相反?

时间:2019-06-01 14:50:42

标签: assembly x86 x86-64 sse avx

比方说,我想反转一个非常大的字节数组的字节顺序。我可以使用主寄存器来完成此操作,但是我想使用XMM或YMM寄存器来加快速度。

有没有一种方法可以反转XMM或YMM寄存器中的字节顺序?

2 个答案:

答案 0 :(得分:1)

是的,请使用SSSE3 _mm_shuffle_epi8或AVX2 _mm256_shuffle_epi8对16字节AVX2“通道”内的字节进行混洗。根据混洗控制向量的不同,您可以交换字节对,反向4字节单位或反向8字节单位。

vpshufb是在Intel上的单个uop指令,在AMD上是2,并且一次处理32字节的数据。

对于非常大的输入,可能值得在向量化循环之前达到32或64字节的对齐边界,因此,加载/存储都不会跨越高速缓存行边界。 (对于少量输入,次要好处不值得额外的序言/结尾代码和分支。)


但可能更好的方法是只在使用前交换一个16kiB的块,因此当下一步读取它时,它在L1d缓存中仍然很热。这称为缓存阻止。或者也许使用128kiB块来阻止L2缓存大小。

从文件中读取数据时,您可能会交换大块的 。例如在内核将数据从页面缓存复制到用户空间缓冲区后,以{64}或128k的块大小read()进行系统调用,并在高速缓存中交换结果时交换结果。或使用mmap对文件进行内存映射,然后从该文件运行复制和交换循环。 (或者对于私有映射,就地交换;但这无论如何都会触发写时复制,因此没有太大的好处。Linux上的文件支持的mmap不能使用匿名大页面。)

另一种选择是,如果您只读取几次数据,则可以即时进行交换。如果以后的使用仍然受到内存的限制,或者有足够的空间进行无瓶颈的洗牌,那么它可能不会使它们变慢以进行洗牌。

通过一次访问所有数据并且仅进行字节交换的一次,其计算强度非常低;您希望在数据处于寄存器中或至少在高速缓存中时对数据进行更多处理。但是,如果您只进行一次字节交换,然后很多次读取数据,或者以随机访问模式读取数据,或者从无法高效交换的另一种语言(例如Python或JavaScript)中读取数据,那么请确保进行交换通行证。

或者,如果您要对它进行多次遍历而不会受到内存限制的遍历,则交换遍历会有所帮助,并且额外的洗牌会降低以后的遍历速度。在那种情况下,您确实想对交换进行高速缓存阻止,以便以后通过的输入在高速缓存中很热。


标量选项bswap限于每个时钟周期最多8个字节,并且每8个字节需要单独的加载和存储指令。 (movbe通过字节交换从内存中加载会保存一条指令,但是在主流CPU上,不会将微熔丝装入单个加载+交换uop中。但是,在Silvermont上,它是单uup。)

这可能会饱和现代CPU上的单线程内存带宽,但是例如,具有较少总uops来处理相同数据的SIMD可使乱序执行“更早”“看到”并更快地处理即将出现的页面的TLB丢失。硬件数据预取和TLB预取确实有很大帮助,但是通常为memcpy使用更大的加载/存储空间至少要好一点。

({vpshufb足够便宜,以至于基本上仍能像memcpy一样执行。如果重写就更好了。)

当然,如果您有任何缓存命中,甚至只有L3缓存,SIMD也会非常有用。

答案 1 :(得分:1)

我无法与传奇人物Peter Cordes竞争...我想展示C实现。

这里是使用C内部函数反转字节顺序的示例(可用于整个数组的字节反转)。

有3个代码示例。

  1. 使用 SSE2 指令集。
  2. 使用 SSSE3 指令集。
  3. 使用 AVX2 指令集。

//Initialize XMM register with uint8 values 0 to 15 (for testing):
__m128i a_F_E_D_C_B_A_9_8_7_6_5_4_3_2_1_0 = _mm_set_epi8(15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0);


//SSE2:
//Advantage: No need to build a shuffle mask (efficient for very short loops).
//////////////////////////////////////////////////////////////////////////
//Reverse order of uint32:
__m128i a_3_2_1_0_7_6_5_4_B_A_9_8_F_E_D_C = _mm_shuffle_epi32(a_F_E_D_C_B_A_9_8_7_6_5_4_3_2_1_0, _MM_SHUFFLE(0, 1, 2, 3));

//Swap pairs of uint16:
__m128i a_1_0_3_2_5_4_7_6_9_8_B_A_D_C_F_E = _mm_shufflehi_epi16(_mm_shufflelo_epi16(a_3_2_1_0_7_6_5_4_B_A_9_8_F_E_D_C, _MM_SHUFFLE(2, 3, 0, 1)), _MM_SHUFFLE(2, 3, 0, 1));

//Swap pairs of uint8:
__m128i a_0_1_2_3_4_5_6_7_8_9_A_B_C_D_E_F = _mm_or_si128(_mm_slli_epi16(a_1_0_3_2_5_4_7_6_9_8_B_A_D_C_F_E, 8), _mm_srli_epi16(a_1_0_3_2_5_4_7_6_9_8_B_A_D_C_F_E, 8));
//////////////////////////////////////////////////////////////////////////


//SSSE3: 
//Advantage: Not requires AVX2 support
//////////////////////////////////////////////////////////////////////////
//Build shuffle mask
const __m128i shuffle_mask = _mm_set_epi8(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);

a_0_1_2_3_4_5_6_7_8_9_A_B_C_D_E_F = _mm_shuffle_epi8(a_F_E_D_C_B_A_9_8_7_6_5_4_3_2_1_0, shuffle_mask);
//////////////////////////////////////////////////////////////////////////


//AVX2: 
//Advantage: Potentially faster than SSSE3
//////////////////////////////////////////////////////////////////////////
//Initialize YMM register with uint8 values 0 to 31 (for testing):
__m256i a__31_to_0 = _mm256_set_epi8(31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0);

//Build shuffle mask
const __m256i shuffle_mask2 = _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);

//Reverse bytes oreder of upper lane and lower lane of YMM register.
__m256i a__16_to_31__0_to_15 = _mm256_shuffle_epi8(a__31_to_0, shuffle_mask2);

//Swap upper and lower lane of YMM register
__m256i a__0_to_31 = _mm256_permute4x64_epi64(a__16_to_31__0_to_15, _MM_SHUFFLE(1, 0, 3, 2));
//////////////////////////////////////////////////////////////////////////
相关问题