具有AVX2和范围保留的按位型转换

时间:2016-02-04 02:25:41

标签: c++ bitwise-operators avx2

我想将signed char的向量转换为unsigned char的向量。 我想保留每种类型的值范围。

我的意思是当unsigned char元素的值范围介于0到255之间时,signed char的值范围是-128和+127。

没有内在函数我几乎可以这样做:

#include <iostream>

int main(int argc,char* argv[])
{

typedef signed char schar;
typedef unsigned char uchar;

schar a[]={-1,-2,-3,4,5,6,-7,-8,9,10,-11,12,13,14,15,16,17,-128,19,20,21,22,23,24,25,26,27,28,29,30,31,32};

uchar b[32] = {0};

    for(int i=0;i<32;i++)
        b[i] = 0xFF & ~(0x7F ^ a[i]);

    return 0;

}

所以使用AVX2我编写了以下程序:

#include <immintrin.h>
#include <iostream>

int main(int argc,char* argv[])
{
    schar a[]={-1,-2,-3,4,5,6,-7,-8,9,10,-11,12,13,14,15,16,17,-128,19,20,21,22,23,24,25,26,27,28,29,30,31,32};

     uchar b[32] = {0};

    __m256i _a = _mm256_stream_load_si256(reinterpret_cast<const __m256i*>(a));
    __m256i _b;
    __m256i _cst1 = _mm256_set1_epi8(0x7F);
    __m256i _cst2 = _mm256_set1_epi8(0xFF);

    _a = _mm256_xor_si256(_a,_cst1);
    _a = _mm256_andnot_si256(_cst2,_a);

// The way I do the convertion is inspired by an algorithm from OpenCV. 
// Convertion from epi8 -> epi16
    _b = _mm256_srai_epi16(_mm256_unpacklo_epi8(_mm256_setzero_si256(),_a),8);
    _a = _mm256_srai_epi16(_mm256_unpackhi_epi8(_mm256_setzero_si256(),_a),8);

    // convert from epi16 -> epu8.
    _b = _mm256_packus_epi16(_b,_a);

_mm256_stream_si256(reinterpret_cast<__m256i*>(b),_b);

return 0;
}

当我显示变量b时,它完全是空的。 我还检查了以下情况:

   #include <immintrin.h>
    #include <iostream>

    int main(int argc,char* argv[])

{
    schar a[]={-1,-2,-3,4,5,6,-7,-8,9,10,-11,12,13,14,15,16,17,-128,19,20,21,22,23,24,25,26,27,28,29,30,31,32};

     uchar b[32] = {0};

    __m256i _a = _mm256_stream_load_si256(reinterpret_cast<const __m256i*>(a));
    __m256i _b;
    __m256i _cst1 = _mm256_set1_epi8(0x7F);
    __m256i _cst2 = _mm256_set1_epi8(0xFF);


// The way I do the convertion is inspired by an algorithm from OpenCV. 
// Convertion from epi8 -> epi16
    _b = _mm256_srai_epi16(_mm256_unpacklo_epi8(_mm256_setzero_si256(),_a),8);
    _a = _mm256_srai_epi16(_mm256_unpackhi_epi8(_mm256_setzero_si256(),_a),8);

    // convert from epi16 -> epu8.
    _b = _mm256_packus_epi16(_b,_a);

_b = _mm256_xor_si256(_b,_cst1);
_b = _mm256_andnot_si256(_cst2,_b);


_mm256_stream_si256(reinterpret_cast<__m256i*>(b),_b);

return 0;
}

和:

 #include <immintrin.h>
    #include <iostream>

    int main(int argc,char* argv[])

{
    schar a[]={-1,-2,-3,4,5,6,-7,-8,9,10,-11,12,13,14,15,16,17,-128,19,20,21,22,23,24,25,26,27,28,29,30,31,32};

     uchar b[32] = {0};

    __m256i _a = _mm256_stream_load_si256(reinterpret_cast<const __m256i*>(a));
    __m256i _b;
    __m256i _cst1 = _mm256_set1_epi8(0x7F);
    __m256i _cst2 = _mm256_set1_epi8(0xFF);


// The way I do the convertion is inspired by an algorithm from OpenCV. 
// Convertion from epi8 -> epi16
_b = _mm256_srai_epi16(_mm256_unpacklo_epi8(_mm256_setzero_si256(),_a),8);
_a = _mm256_srai_epi16(_mm256_unpackhi_epi8(_mm256_setzero_si256(),_a),8);

_a = _mm256_xor_si256(_a,_cst1);
_a = _mm256_andnot_si256(_cst2,_a);

_b = _mm256_xor_si256(_b,_cst1);
_b = _mm256_andnot_si256(_cst2,_b);

_b = _mm256_packus_epi16(_b,_a);

_mm256_stream_si256(reinterpret_cast<__m256i*>(b[0]),_b);

return 0;
}

我的调查显示,问题的一部分与and_not操作有关。 但我不知道为什么。

变量b应包含以下序列: [127,126,125,120,133,134,121,120,137,138,117,140,​​141,142,143,144,145,0,147,148,149,150,151,152,153 ,154,155,156,157,158,159,160]。

提前感谢您的帮助。

2 个答案:

答案 0 :(得分:0)

是的,&#34;而不是&#34;绝对看起来粗略。由于0xFF值设置为_b,因此此操作会将您的__m256i _a, _b; _a = _mm256_stream_load_si256( reinterpret_cast<__m256i*>(a) ); _b = _mm256_xor_si256( _a, _mm256_set1_epi8( 0x7f ) ); _b = _mm256_andnot_si256( _b, _mm256_set1_epi8( 0xff ) ); _mm256_stream_si256( reinterpret_cast<__m256i*>(b), _b ); 向量与零进行对比。我认为你混淆了论点的顺序。它是被转换的第一个参数。 See the reference

我也无法通过转换了解剩余的guff。你只需要这个:

__m256i _a, _b;
_a = _mm256_stream_load_si256( reinterpret_cast<__m256i*>(a) );
_b = _mm256_add_epi8( _a, _mm256_set1_epi8( 0x80 ) );
_mm256_stream_si256( reinterpret_cast<__m256i*>(b), _b );

另一种解决方案是添加128,但在这种情况下,我不确定溢出的含义:

a

最后一件重要的事情是,您的balignas数组必须具有32字节对齐方式。如果您使用的是C ++ 11,则可以使用alignas(32) signed char a[32] = { -1,-2,-3,4,5,6,-7,-8,9,10,-11,12,13,14,15,16,17, -128,19,20,21,22,23,24,25,26,27,28,29,30,31,32 }; alignas(32) unsigned char b[32] = {0};

_mm256_loadu_si256

否则,您需要使用非对齐加载和存储指令, _mm256_storeu_si256和{{1}}。但是那些不具有与流指令相同的非临时缓存属性。

答案 1 :(得分:0)

您只是在谈论为每个字节添加128,对吗?这会将范围从[-128..127]转移到[0..255]。当你只能使用8位操作数时添加128的技巧是减去-128。

但是,当结果被截断为8位时,添加0x80也可以。 (因为有两个补充)。添加是好的,因为操作数所在的顺序并不重要,因此编译器可以使用加载和添加指令(将内存操作数折叠到加载中)。

添加/减去-128,进位/借位被元素边界停止,相当于xor(又称无进位加法)。使用pxor可能是英特尔酷睿2通过Broadwell的一个小优势,因为英特尔必须认为在Skylake的port0上添加paddb/w/d/q硬件是值得的(给他们一个0.333c吞吐量,如{{1} }})。 (感谢@harold指出这一点)。这两条指令只需要SSE2。

XOR对SWAR未对齐清理或者没有字节大小的加/减操作的SIMD架构也有用。

您不应该使用pxor作为变量名称。保留_a个名称。我倾向于使用像_veca这样的名字,最好是对临时表现更具描述性的名字。 (如va)。

a_unpacked

是的,这很简单,你不需要两个补码比特。首先,您的方式需要两个独立的32B掩码,这会增加缓存占用空间。 (但请参阅What are the best instruction sequences to generate vector constants on the fly?您(或编译器)可以使用3条指令生成__m256i signed_bytes = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(a)); __m256i unsigned_bytes = _mm256_add_epi8(signed_bytes, _mm256_set1_epi8(-128)); 字节的向量,或者从4B常量生成广播负载。)

仅对{/ 1}}使用I / O(例如从视频RAM中读取)。不要用它来阅读&#34;普通&#34; (写回)记忆;它没有做你认为它做的事情。 (但我认为它没有任何特别的缺点。它只是像普通-128负载一样工作。我在another answer I wrote recently中提供了一些相关链接。

流媒体商店对普通(写回)内存区域很有用。但是,如果您不打算在短时间内再次阅读该内存,那么它们只是 的好主意。如果是这种情况,您应该在读取此数据的代码中即时执行从已签名到无符号的转换,因为它非常便宜。只需将数据保存为一种格式或另一种格式,即可在需要其他方式的代码中进行即时转换。与在一些循环中保存一条指令相比,在缓存中只需要一份它就是一个巨大的胜利。

google&#34;缓存阻止&#34; (也就是循环平铺)并阅读有关优化代码以在小块中工作以提高计算密度的内容。 (在缓存中使用数据尽可能多地处理数据。)