- mongodb - 在 MongoDB mapreduce 中,如何展平值对象?
- javascript - 对象传播与 Object.assign
- html - 输入类型 ="submit"Vs 按钮标签它们可以互换吗?
- sql - 使用 MongoDB 而不是 MS SQL Server 的优缺点
SSE2 有在单精度 float 和 32 位整数之间转换 vector 的指令。
_mm_cvtps_epi32()
_mm_cvtepi32_ps()
但是没有 double 和 64 位整数的等价物。换句话说,它们不见了:
_mm_cvtpd_epi64()
_mm_cvtepi64_pd()
AVX 好像也没有。
模拟这些内在函数的最有效方法是什么?
最佳答案
在 AVX512 之前没有单一指令,它添加了与 64 位整数(有符号或无符号)的转换。 (还支持与 32 位无符号的转换)。查看像 _mm512_cvtpd_epi64
这样的内在函数和更窄的 AVX512VL 版本,如 _mm256_cvtpd_epi64
.
如果您只有 AVX2 或更少,则需要以下技巧来进行打包转换。 (对于标量,x86-64 具有来自 SSE2 的标量 int64_t <-> double 或 float,但标量 uint64_t <-> FP 需要技巧,直到 AVX512 添加无符号转换。标量 32 位无符号可以通过零扩展到 64 位来完成签名。)
如果你愿意偷工减料,double <-> int64
只需两条指令即可完成转换:
NaN
.double <-> int64_t
,您只关心 [-2^51, 2^51]
范围内的值.double <-> uint64_t
,您只关心 [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 -> 双
// 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 -> 双倍
// 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));
}
舍入行为:
double -> uint64_t
转换,舍入按照当前舍入模式正常工作。 (通常是四舍五入)double -> int64_t
转换,舍入将遵循除截断之外的所有模式的当前舍入模式。如果当前的舍入模式是截断(向零舍入),它实际上会向负无穷舍入。它是如何工作的?
尽管这个技巧只有 2 条指令,但并不完全不言自明。
关键是要认识到,对于 double 浮点,[2^52, 2^53)
范围内的值在尾数的最低位下方有“二进制位置”。换句话说,如果将指数位和符号位清零,尾数就变成了整数表示。
转换 x
来自 double -> uint64_t
,您添加魔数(Magic Number)M
这是 2^52
的浮点值.这把 x
进入 [2^52, 2^53)
的“标准化”范围并方便地舍入小数部分。
现在剩下的就是删除高 12 位。这很容易通过掩盖它来完成。最快的方法是识别那些高 12 位与 M
相同。 .因此,我们可以简单地通过 M
减去或异或,而不是引入额外的掩码常数。 . XOR 具有更高的吞吐量。
从 uint64_t -> double
转换只是这个过程的逆过程。您将 M
的指数位加回.然后通过减去 M
来取消归一化数字。 float 。
有符号整数转换稍微复杂一些,因为您需要处理 2 的补码符号扩展。我会把这些留给读者作为练习。
相关: A fast method to round a double to a 32-bit int explained
全范围 int64 -> double :
多年后,我终于需要这个了。
uint64_t -> double
的 5 条说明int64_t -> double
的 6 条说明uint64_t -> 双
__m128d uint64_to_double_full(__m128i x){
__m128i xH = _mm_srli_epi64(x, 32);
xH = _mm_or_si128(xH, _mm_castpd_si128(_mm_set1_pd(19342813113834066795298816.))); // 2^84
__m128i xL = _mm_blend_epi16(x, _mm_castpd_si128(_mm_set1_pd(0x0010000000000000)), 0xcc); // 2^52
__m128d f = _mm_sub_pd(_mm_castsi128_pd(xH), _mm_set1_pd(19342813118337666422669312.)); // 2^84 + 2^52
return _mm_add_pd(f, _mm_castsi128_pd(xL));
}
int64_t -> 双倍
__m128d int64_to_double_full(__m128i x){
__m128i xH = _mm_srai_epi32(x, 16);
xH = _mm_blend_epi16(xH, _mm_setzero_si128(), 0x33);
xH = _mm_add_epi64(xH, _mm_castpd_si128(_mm_set1_pd(442721857769029238784.))); // 3*2^67
__m128i xL = _mm_blend_epi16(x, _mm_castpd_si128(_mm_set1_pd(0x0010000000000000)), 0x88); // 2^52
__m128d f = _mm_sub_pd(_mm_castsi128_pd(xH), _mm_set1_pd(442726361368656609280.)); // 3*2^67 + 2^52
return _mm_add_pd(f, _mm_castsi128_pd(xL));
}
这些适用于整个 64 位范围,并正确舍入为当前舍入行为。
这些是下面类似的 wim 的答案 - 但有更多的滥用优化。因此,解读这些内容也将留给读者作为练习。
关于c++ - 如何使用 SSE/AVX 高效地执行 double/int64 转换?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41144668/
在浏览可用的内在函数时,我注意到没有地方可以看到水平的addsub / subadd指令可用。在过时的3DNow中可用!扩展名,但是出于明显的原因,它的使用是不实际的。是什么原因导致SSE3扩展中无法
我需要在 SSE2 汇编程序中编写一些东西。 我所看到的都是内在因素。 我一直在寻找从内在函数到汇编器的转换表,但一直没有找到。 因为我不想玩猜谜游戏,有人可以给我一个链接,将这些可怕的内在函数转换为
我正在研究 SSE 并且是这里的新手。我正在尝试使用 shuffle 指令来随机播放一个 16 位向量,如下所示: 输入: 1 2 3 4 5 6 7 8 输出: 1 5 2 6 3 7 4 8 如何
我有一个用例,其中 x86 CPU 必须将 64 字节的数据写入内存已 mmapp 到用户空间的 PCIe 从属设备。截至目前,我使用 memcpy 来执行此操作,但事实证明它非常慢。我们可以使用像
我最近偶然发现了隐式 SSE/AVX 加载/存储。我认为这些是 GCC 的一些特殊扩展,但后来意识到它们也适用于 MSVC。 __m128 a = *(__m128*)data // same
仅将较高或较低 64 位从整数 SSE 寄存器移动到另一个的最快方法是什么?使用 SSE 4.1,可以使用单个 pblendw 来完成。指令(_mm_blend_epi16)。但是旧的 SSE 版本呢
SSE/AVX 寄存器可以被视为整数或浮点 BigNum。也就是说,人们可能会忽略车道的存在。是否存在一种简单的方法来利用这个观点并将这些寄存器单独或组合用作 BigNum?我问这个问题是因为从我对
我正在尝试比较 SSE float[4] 添加与标准 float[4] 添加。作为演示,我在使用和不使用 SSE 的情况下计算求和分量的总和: #include #include struct P
我处于以下情况: 我正在为不允许 SSE 指令的内核编写代码 我需要做浮点运算 我正在为 x86_64 平台编译 这是一个说明问题的代码示例: int main(int argc, char** ar
我处于以下情况: 我正在为不允许 SSE 指令的内核编写代码 我需要做浮点运算 我正在为 x86_64 平台编译 这是一个说明问题的代码示例: int main(int argc, char** ar
我正在尝试将用 SSE3 内在函数编写的代码转换为 NEON SIMD,但由于 shuffle 函数而卡住了。我查看了 GCC Intrinsic , ARM manuals和其他论坛,但一直无法找到
我正在尝试对一些代码进行 super 优化,我想加快速度的地方如下。 我想取一个 _m128 的点积运算 (_mm_dp_ps) 的答案,并将答案直接保存到寄存器中。但是,使用 _mm_store,这
我正在寻找 SSE 和 AVX 的 SIMD 数学库(最好是开源的)。我的意思是,例如,如果我有一个带有 8 个浮点值的 AVX 寄存器 v,我希望 sin(v) 一次返回所有八个值的 sin。 AM
假设我在 128 位变量/寄存器中有 16 个 ascii 字符(因此有 16 个 8 位数字)。我想创建一个位掩码,其中那些位将是高位,其位位置(索引)由这 16 个字符表示。 例如,如果由这 16
目前我正在使用 Visual C++ 内联汇编使用 SSE 嵌入一些核心功能;但是我意识到 x64 模式不支持内联汇编。 在 x64 架构中构建软件时如何使用 SSE? 最佳答案 在 C/C++ 中使
我正在寻找计算以下函数的有效方法: 输入:__m128i数据,uint8_t in; 输出: bool 值,指示 data 中的任何字节是否在 in 中。 我实际上是在使用它们为容量为 8 的字节实现
我正在寻找计算以下函数的有效方法: 输入:__m128i数据,uint8_t in; 输出: bool 值,指示 data 中的任何字节是否在 in 中。 我实际上是在使用它们为容量为 8 的字节实现
我正在尝试将最新消息拉入顶部页面。目前,每次收到新消息时,最新消息都会显示在下方。 if(typeof(EventSource)!=="undefined") { var source=new Ev
基本上我想做的是获取一个 __m128i 寄存器并将每个负字节的值设置为 -128 (0x80) 并且不更改任何正值。 确切的是: signed char __m128_as_char_arr[16]
有 2 个指针指向要加载到 xmm 寄存器中的 2 个未对齐的 8 字节 block 。如果可能,使用内在函数。如果可能的话,不使用辅助寄存器。没有pinsrd。 (SSSE核心2) 最佳答案 来自
我是一名优秀的程序员,十分优秀!