如何有效地使用SSE / AVX执行double / int64转换? [英] How to efficiently perform double/int64 conversions with SSE/AVX?

查看:342
本文介绍了如何有效地使用SSE / AVX执行double / int64转换?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述




  • _mm_cvtps_epi32(

    SSE2具有在单精度浮点数和32位整数之间转换矢量的指令。 )


  • _mm_cvtepi32_ps()
  • b

    但是没有等价的双精度和64位整数。换句话说,他们缺少:


    • _mm_cvtpd_epi64()

    • _mm_cvtepi64_pd()



    没有它们。



    什么是模拟这些内部函数的最有效的方法是什么?

    解决方案

    如果你愿意偷工减料, double< - >如果你不关心无穷大或 NaN

  • 对于 double< - > int64_t ,您只关心 [ - 2 ^ 51,2 ^ 51] 范围内的值。

  • 对于 double< - > uint64_t ,您只关心 [0,2 ^ 52)范围内的值。



double - > uint64_t

 输入范围:[0,2 ^ 52)
__m128i double_to_uint64(__ m128d x){
x = _mm_add_pd(x,_mm_set1_pd(0x0010000000000000));
return _mm_xor_si128(
_mm_castpd_si128(x),
_mm_castpd_si128(_mm_set1_pd(0x0010000000000000))
);

$ / code $ / pre
$ b

double - > int64_t

  //只对范围内的输入有效:[-2 ^ 51,2 ^ 51] 
__m128i double_to_int64(__ m128d x){
x = _mm_add_pd(x,_mm_set1_pd(0x0018000000000000));
return _mm_sub_epi64(
_mm_castpd_si128(x),
_mm_castpd_si128(_mm_set1_pd(0x0018000000000000))
);

uint64_t - > double

  //仅适用于范围为[0,2 ^ 52)的输入
__m128d uint64_to_double(__ m128i x){
x = _mm_or_si128(x,_mm_castpd_si128(_mm_set1_pd(0x0010000000000000)));
return _mm_sub_pd(_mm_castsi128_pd(x),_mm_set1_pd(0x0010000000000000));
}

int64_t - > double

  //仅适用于范围内的输入:[-2 ^ 51,2 ^ 51] 
__m128d int64_to_double(__ m128i x){
x = _mm_add_epi64(x,_mm_castpd_si128(_mm_set1_pd(0x0018000000000000)));
return _mm_sub_pd(_mm_castsi128_pd(x),_mm_set1_pd(0x0018000000000000));

$ / code $ / pre


$ b $

舍入行为:


  • 对于 double - > uint64_t 转换,舍入在当前四舍五入模式下正常工作。 (通常是循环)

  • 对于 double - > int64_t 转换,四舍五入将遵循当前四舍五入模式,除了截断之外的所有模式。如果当前舍入模式是截断(舍入为零),则实际上会向负无穷大方向移动。





它是如何工作的?



尽管这个技巧只有2条指令,但并不完全是自我解释。 >

关键是要认识到对于双精度浮点,范围 [2 ^ 52,2 ^ 53)在尾数的最低位之下有二进制位置。换句话说,如果您将指数和符号位清零,尾数就变成了整数表示形式。



转换 x from double - > uint64_t ,您添加了 2 ^ 52 M C>。这将 x 放入 [2 ^ 52,2 ^ 53)的标准化范围内,小数部分位。

现在剩下的就是删除高12位。这很容易通过掩盖它。最快的方法是识别那些高12位与 M 相同的位。所以我们可以简单地用 M 来减去或XOR,而不是引入额外的掩码常量。 XOR具有更多的吞吐量。



uint64_t - > double 就是这个过程的反面。您添加 M 的指数位。然后通过在浮点数中减去 M 来对数字进行非规范化。

有符号整数转换稍微复杂你需要处理2的补码符号扩展。我将把这些留给读者作为练习。

相关: 一个快速的方法来解释一个双精度到一个32位整数


SSE2 has instructions for converting vectors between single-precision floats and 32-bit integers.

  • _mm_cvtps_epi32()
  • _mm_cvtepi32_ps()

But there are no equivalents for double-precision and 64-bit integers. In other words, they are missing:

  • _mm_cvtpd_epi64()
  • _mm_cvtepi64_pd()

It seems that AVX doesn't have them either.

What is the most efficient way to simulate these intrinsics?

解决方案

If you're willing to cut corners, double <-> int64 conversions can be done in only two instructions:

  • If you don't care about infinity or NaN.
  • For double <-> int64_t, you only care about values in the range [-2^51, 2^51].
  • For double <-> uint64_t, you only care about values in the range [0, 2^52).

double -> uint64_t

//  Only works for inputs in the range: [0, 2^52)
__m128i double_to_uint64(__m128d x){
    x = _mm_add_pd(x, _mm_set1_pd(0x0010000000000000));
    return _mm_xor_si128(
        _mm_castpd_si128(x),
        _mm_castpd_si128(_mm_set1_pd(0x0010000000000000))
    );
}

double -> int64_t

//  Only works for inputs in the range: [-2^51, 2^51]
__m128i double_to_int64(__m128d x){
    x = _mm_add_pd(x, _mm_set1_pd(0x0018000000000000));
    return _mm_sub_epi64(
        _mm_castpd_si128(x),
        _mm_castpd_si128(_mm_set1_pd(0x0018000000000000))
    );
}

uint64_t -> double

//  Only works for inputs in the range: [0, 2^52)
__m128d uint64_to_double(__m128i x){
    x = _mm_or_si128(x, _mm_castpd_si128(_mm_set1_pd(0x0010000000000000)));
    return _mm_sub_pd(_mm_castsi128_pd(x), _mm_set1_pd(0x0010000000000000));
}

int64_t -> double

//  Only works for inputs in the range: [-2^51, 2^51]
__m128d int64_to_double(__m128i x){
    x = _mm_add_epi64(x, _mm_castpd_si128(_mm_set1_pd(0x0018000000000000)));
    return _mm_sub_pd(_mm_castsi128_pd(x), _mm_set1_pd(0x0018000000000000));
}


Rounding Behavior:

  • For the double -> uint64_t conversion, rounding works correctly following the current rounding mode. (which is usually round-to-even)
  • For the double -> int64_t conversion, rounding will follow the current rounding mode for all modes except truncation. If the current rounding mode is truncation (round towards zero), it will actually round towards negative infinity.

How does it work?

Despite this trick being only 2 instructions, it's not entirely self-explanatory.

The key is to recognize that for double-precision floating-point, values in the range [2^52, 2^53) have the "binary place" just below the lowest bit of the mantissa. In other words, if you zero out the exponent and sign bits, the mantissa becomes precisely the integer representation.

To convert x from double -> uint64_t, you add the magic number M which is the floating-point value of 2^52. This puts x into the "normalized" range of [2^52, 2^53) and conveniently rounds away the fractional part bits.

Now all that's left is to remove the upper 12 bits. This is easily done by masking it out. The fastest way is to recognize that those upper 12 bits are identical to those of M. So rather than introducing an additional mask constant, we can simply subtract or XOR by M. XOR has more throughput.

Converting from uint64_t -> double is simply the reverse of this process. You add back the exponent bits of M. Then un-normalize the number by subtracting M in floating-point.

The signed integer conversions are slightly trickier since you need to deal with the 2's complement sign-extension. I'll leave those as an exercise for the reader.

Related: A fast method to round a double to a 32-bit int explained

这篇关于如何有效地使用SSE / AVX执行double / int64转换?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆