Assembly 在双精度/simd中生成2^n

Assembly 在双精度/simd中生成2^n,assembly,simd,Assembly,Simd,我正在尝试使用双重表示构建2^n。这个把戏是众所周知的 //使用双IEEE表示的指数计算2^n的提示 联盟ieee754{ 双d; uint32_t i[2]; }; //将无符号long-long转换为double 内联双uint642dp(uint64\U t ll){ ieee754 tmp; tmp.ll=ll; 返回tmp.d; } ----------------------------------- //e^x=2^n e^y,n=round(x/log(2)+0.5) 双x=4.

我正在尝试使用双重表示构建2^n。这个把戏是众所周知的

//使用双IEEE表示的指数计算2^n的提示
联盟ieee754{
双d;
uint32_t i[2];
};
//将无符号long-long转换为double
内联双uint642dp(uint64\U t ll){
ieee754 tmp;
tmp.ll=ll;
返回tmp.d;
}
-----------------------------------
//e^x=2^n e^y,n=round(x/log(2)+0.5)
双x=4.3;//从那里开始
双y=圆形(x/对数(2)+0.5)
int n=(int)y;//换算成双精度
uint642dp(((uint64_t)n)+1023)您正在寻找的技巧实际上是
\u mm256\u castsi256\u pd
内在特性,它允许您将整数的SIMD数组转换为双精度的SIMD数组。这仅用于C/C++类型检查,不转换为任何指令

以下是执行此操作的代码片段(仅在指数的某些范围内有效):

这需要AVX2,但128位版本只需要SSE2。注意,指数是作为64位整数提供的。如果有32位整数,请使用
\u mm256\u cvtepu32\u epi64


如果开始时的向量为
double
,如OP,则使用
\u mm256\u cvtpd\u epi32
\u mm256\u cvtepu32\u epi64
(直到AVX-512才可直接使用double->int64)这可能是毫无意义的优化,因为在SSE上的INT-浮点/双转换已经非常快,当然,生成2 ^ N是微不足道的。“诡计”(<代码>联合/代码>滥用)不幸的是C++……为什么不使用<代码>(双)(1个好答案)。在C++ 11中,你可以使用<代码>对齐(32)。
而不是非标准的
\uuuu declspec
(GNU C和MSVC在这方面有所不同)。此外,我可能使用了
\uMM256\uSET\uEPI64x(7,9,4,2)
作为初始值设定项,而不是
int64\uT
数组。另外,在英语中,它是
bias
,而不是
biais
:)@PeterCordes,谢谢。我不知道alignas(32)从C++11开始,谢谢你的提醒,请随意编辑相应的帖子。对于
\u mm256\u set\u epi64x
,我懒得使用它,记住一些编译器标题没有以完全相同的命名公开它。至于英语,你找到我了!(刚刚编辑了帖子)。我必须检查它是
epi64x
还是
epi64
。编译器/链接器在合并向量常量方面做得很好,就像它们如何将相同的字符串文字合并到单个定义中一样,因此几乎总是最好使用
\u mm_set1
\u mm_set
将常量分配给局部变量,而不是将常量放入你自己把它们放在静态常量存储器中。(不幸的是,
static const\uuum128 foo=\u mm\u set()
sucks:这将在启动时运行构造函数,而不是让数据从
foo
开始)@PeterCordes,非常感谢您的编辑。虽然我的编译器版本会产生广播,但应注意标记。英特尔的intrinsics指南只列出了这些,所以希望它能安全地在所有编译器中使用。它适用于gcc和clang。使用
vpbroadcastq
加载32B常量可能是件好事,因为它是c在英特尔硬件上作为
vmovdqa
堆。任何一个好的编译器在内联后都会将广播加载从循环中提升出来,因此它不必每次都通过循环加载,无论它是否带有广播。
// tips to calculate 2^n using the exponent of the double IEEE representation
union ieee754{
    double d;
    uint32_t i[2];
};

// Converts an unsigned long long to a double
inline double uint642dp(uint64_t ll) {
  ieee754 tmp;
  tmp.ll=ll;
  return tmp.d;
}
-----------------------------------
// e^x = 2^n e^y, n = round(x/log(2)+0.5)

double x = 4.3; // start from there
double y = round(x/log(2)+0.5)
int n = (int)y; // convert in to double 
uint642dp(( ((uint64_t)n) +1023)<<52); // calculate 2^n fastly
#include <immintrin.h>

__m256d pow2n (__m256i n)
{
    const __m256i bias = _mm256_set1_epi64x( 1023 );
    __m256i t = _mm256_add_epi64 (n, bias);
    t = _mm256_slli_epi64 (t, 52);
    return _mm256_castsi256_pd (t) ;
}

#include <cstdio>

int main ()
{
        __m256i rn = _mm256_set_epi64x( 7, 9, 4, 2 );
        __m256d pn = pow2n (rn) ;
        double v [4] ;
        _mm256_storeu_pd (v, pn) ;
        printf ("v = %lf %lf %lf %lf\n", v[0], v[1], v[2], v[3]) ;
        return 0 ;
}
v = 4.000000 16.000000 512.000000 128.000000