我可以使用内在函数加速类型转换吗?

Posted

技术标签:

【中文标题】我可以使用内在函数加速类型转换吗?【英文标题】:Can I speed up type conversion using intrinsics? 【发布时间】:2015-11-26 18:05:21 【问题描述】:

我正在开发一个需要将数据转换为浮点数的应用程序。 数据为 unsigned char 或 unsigned short。

我在这段代码中同时使用了 AVX2 和其他 SIMD 内部函数。 我这样写转换:

无符号字符 -> 浮点数:

#ifdef __AVX2__

__m256i tmp_v =_mm256_lddqu_si256(reinterpret_cast<const __m256i*>(src+j));

v16_avx[0] = _mm256_cvtepu8_epi16(_mm256_extractf128_si256(tmp_v,0x0));
v16_avx[1] = _mm256_cvtepu8_epi16(_mm256_extractf128_si256(tmp_v,0x1));

v32_avx[0] = _mm256_cvtepi16_epi32(_mm256_extractf128_si256(v16_avx[0],0x0));
v32_avx[1] = _mm256_cvtepi16_epi32(_mm256_extractf128_si256(v16_avx[0],0x1));
v32_avx[2] = _mm256_cvtepi16_epi32(_mm256_extractf128_si256(v16_avx[1],0x0));
v32_avx[3] = _mm256_cvtepi16_epi32(_mm256_extractf128_si256(v16_avx[1],0x1));

for (int l=0; l<4; l++) 
    __m256 vc1_ps = _mm256_cvtepi32_ps(_mm256_and_si256(v32_avx[l],m_lt_avx[l]));
    __m256 vc2_ps = _mm256_cvtepi32_ps(_mm256_and_si256(v32_avx[l],m_ge_avx[l]));

    /*
      ....
      some processing there.
      */


#endif

#ifdef __SSE2__

#ifdef __SSE3__
__m128i tmp_v = _mm_lddqu_si128(reinterpret_cast<const __m128i*>(src+j));
#else
__m128i tmp_v = _mm_loadu_si128(reinterpret_cast<const __m128i*>(src+j));
#endif

#ifdef __SSE4_1__
v16[0] = _mm_cvtepu8_epi16(tmp_v);
tmp_v = _mm_shuffle_epi8(tmp_v,mask8);
v16[1] = _mm_cvtepu8_epi16(tmp_v);

v32[0] = _mm_cvtepi16_epi32(v16[0]);
v16[0] = _mm_shuffle_epi32(v16[0],0x4E);
v32[1] = _mm_cvtepi16_epi32(v16[0]);

v32[2] = _mm_cvtepi16_epi32(v16[1]);
v16[1] = _mm_shuffle_epi32(v16[1],0x4E);
v32[3] = _mm_cvtepi16_epi32(v16[1]);

#else

__m128i tmp_v_l = _mm_slli_si128(tmp_v,8);
__m128i tmp_v_r = _mm_srli_si128(tmp_v,8);

v16[0] = _mm_unpacklo_epi8(tmp_v,tmp_v_l);
v16[1] = _mm_unpackhi_epi8(tmp_v,tmp_v_r);

tmp_v_l = _mm_srli_epi16(v16[0],8);
tmp_v_r = _mm_srai_epi16(v16[0],8);

v32[0] = _mm_unpacklo_epi16(v16[0],tmp_v_l);
v32[1] = _mm_unpackhi_epi16(v16[0],tmp_v_r);

v16[0] = _mm_unpacklo_epi8(tmp_v,tmp_v_l);
v16[1] = _mm_unpackhi_epi8(tmp_v,tmp_v_r);

tmp_v_l = _mm_srli_epi16(v16[1],8);
tmp_v_r = _mm_srai_epi16(v16[1],8);

v32[2] = _mm_unpacklo_epi16(v16[1],tmp_v_l);
v32[3] = _mm_unpackhi_epi16(v16[1],tmp_v_r);

#endif

for (int l=0; l<4; l++) 
    __m128 vc1_ps = _mm_cvtepi32_ps(_mm_and_si128(v32[l],m_lt[l]));
    __m128 vc2_ps = _mm_cvtepi32_ps(_mm_and_si128(v32[l],m_ge[l]));
    /*
      ...
      some processing there.
    */

#endif

无符号短 -> 浮动

#ifdef __AVX2__
v32_avx[0] = _mm256_cvtepu16_epi32(_mm256_extractf128_si256(tmp_v,0x0));
v32_avx[1] = _mm256_cvtepu16_epi32(_mm256_extractf128_si256(tmp_v,0x1));

for(int l=0;l<2;l++) 
    __m256 vc1_ps = _mm256_cvtepi32_ps(_mm256_and_si256(v32_avx[l],m_lt_avx[l]));
    __m256 vc2_ps = _mm256_cvtepi32_ps(_mm256_and_si256(v32_avx[l],m_ge_avx[l]));

    /*
          ...
          some processing there.
    */

#endif

#ifdef __SSE2__

#ifdef __SSE3__
__m128i tmp_v = _mm_lddqu_si128(reinterpret_cast<const __m128i*>(src+j));
#else
__m128i tmp_v = _mm_loadu_si128(reinterpret_cast<const __m128i*>(src+j));
#endif

#ifdef __SSE4_1__
v32[0] = _mm_cvtepu16_epi32(tmp_v);
tmp_v = _mm_shuffle_epi32(tmp_v,0x4E);
v32[1] = _mm_cvtepu16_epi32(tmp_v);
#else
__m128i tmp_v_l = _mm_slli_si128(tmp_v,8);
__m128i tmp_v_r = _mm_srli_si128(tmp_v,8);

v32[0] = _mm_unpacklo_epi16(tmp_v,tmp_v_l);
v32[1] = _mm_unpackhi_epi16(tmp_v,tmp_v_r);
#endif

for(int l=0;l<2;l++) 
    __m128 vc1_ps = _mm_cvtepi32_ps(_mm_and_si128(v32[l],m_lt[l]));
    __m128 vc2_ps = _mm_cvtepi32_ps(_mm_and_si128(v32[l],m_ge[l]));
    /*
      ...
      some processing there.
    */

 

#endif

cmets中的处理与转换步骤无关。

我想加快这些转化。

我在SSE: convert short integer to float 和Converting Int to Float/Float to Int using Bitwise 中读到,可以使用按位运算来做到这一点。 这些方法真的更快吗?

我在第一个链接中尝试了实现;处理时间几乎没有变化,只要该值包含在 0 和 MAX_SHRT 之间(我的系统上为 32767),它对于有符号短和无符号短都可以正常工作:

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

void convert_sse_intrinsic(const ushort *source,const int len, int *destination)

    __m128i zero2 =  _mm_setzero_si128();

    for (int i = 0; i < len; i+=4)
    
    __m128i value = _mm_unpacklo_epi16(_mm_set_epi64x(0,*((long long*)(source+i)) /**ps*/), zero2);
    value = _mm_srai_epi32(_mm_slli_epi32(value, 16), 16);
    _mm_storeu_si128(reinterpret_cast<__m128i*>(destination+i),value);
    


void convert_sse_intrinsic2(const ushort *source,const int len, int *destination)


    for (int i = 0; i < len; i+=8)
    

        __m128i value = _mm_loadu_si128(reinterpret_cast<const __m128i*>(source+i));

        _mm_storeu_si128(reinterpret_cast<__m128i*>(destination+i),_mm_cvtepu16_epi32(value));

        value = _mm_shuffle_epi32(value,0x4E);

        _mm_storeu_si128(reinterpret_cast<__m128i*>(destination+i+4),_mm_cvtepu16_epi32(value));
    



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


    ushort CV_DECL_ALIGNED(32) toto[16] =
                        0,500,1000,5000,
                       10000,15000,20000,25000,
                       30000,35000,40000,45000,
                       50000,55000,60000,65000;

    int CV_DECL_ALIGNED(32) tutu[16] = 0;

    std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
    convert_sse_intrinsic(toto,16,tutu);
    std::chrono::steady_clock::time_point stop = std::chrono::steady_clock::now();

    std::cout<<"processing time 1st method : "<<std::chrono::duration_cast<std::chrono::nanoseconds>(stop-start).count()<<" : ns"<<std::endl;

    std::copy(tutu,tutu+16,std::ostream_iterator<int>(std::cout," "));
    std::cout<<std::endl;


    start = std::chrono::steady_clock::now();
    convert_sse_intrinsic2(toto,16,tutu);
    stop = std::chrono::steady_clock::now();

    std::cout<<"processing time 2nd method : "<<std::chrono::duration_cast<std::chrono::nanoseconds>(stop-start).count()<<" : ns"<<std::endl;

    std::copy(tutu,tutu+16,std::ostream_iterator<int>(std::cout," "));
    std::cout<<std::endl;


  return 0;

提前感谢您的帮助。

【问题讨论】:

你为什么不尝试测量一下? 哪个代码?我这样做了,为什么我会问这个问题。我使用从 unsigned short 到 float 的转换的完整代码大约在 15 微秒内工作。函数 convert_sse_intrinsics 在 61 纳秒内平均工作(超过 10 次迭代),函数 convert_sse_intrinsics2 在 58 ns 内工作。我想知道是否有办法加快转换速度,是否有办法。 你是如何编译代码的?您使用了哪些命令行开关?另外,您是如何对其进行基准测试的?您用于基准测试的数据集大小是多少? 我在 Linux 下使用 -O1 -O2 -O3 -Og 选项编译代码。我使用 header chrono (C++11) 的 stable_clock time_points 进行基准测试。然后我在纳秒内执行 duration_cast 。我用于基准第一个算法的数据集的大小是每种类型的大小为 64 x 64 的图像。我正在使用 i7 Haswell 家族,感谢英特尔 intrisics 指南网站,我知道每个步骤的周期数......等等每个处理的理论时间。我正在寻找更多的是一种“丑陋”但可移植的方式来加速从每种类型到浮动的转换。 【参考方案1】:

嗯,我认为实际上没有任何更快的方法可以将 unsigned char 或 unsigned short 转换为 float 而不是已经存在的内在函数。

我尝试了其他几种使用位运算符的方法,但没有一个明显更快。

所以我觉得让这个话题再拖下去也没意思了。

【讨论】:

以上是关于我可以使用内在函数加速类型转换吗?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 ARM NEON 内在函数将 u8 掩码转换为 u32 掩码?

使用 AVX2 和范围保留的按位类型转换

使用联合(封装在结构中)绕过霓虹灯数据类型的转换

c语言 所有类型转换函数

我可以在 C# 中从泛型类型转换为枚举吗?

我可以将 C++17 无捕获 lambda constexpr 转换运算符的结果用作函数指针模板非类型参数吗?