我想定义一个有效的整数下限函数,即从 float 或 double 执行截断到负无穷大的转换。
我们可以假设这些值不会发生整数溢出。到目前为止,我有几个选择
转换为 int;这需要对负值进行特殊处理,因为强制转换趋向于零;
I= int(F); if (I < 0 && I != F) I--;
将 floor 的结果转换为 int;
int(floor(F));
转换为 int 并进行较大的转换以获得正数(这可能会为较大的值返回错误的结果);
int(F + double(0x7fffffff)) - 0x7fffffff;
众所周知,转换为 int 的速度很慢。 if 测试也是如此。地板功能我没有计时,但看到帖子声称它也很慢。
您能在速度、准确性或允许范围方面想出更好的选择吗?它不需要是可移植的。目标是最近的 x86/x64 架构。
最佳答案
Casting to int is notoriously slow.
也许您自 x86-64 以来就一直生活在一 block 岩石下,或者以其他方式错过了在 x86 上这已经有一段时间了。 :)
SSE/SSE2 有一个指令来转换用截断(而不是默认的舍入模式)。 ISA 有效地支持此操作正是因为在实际代码库中使用 C 语义进行转换并不罕见。 x86-64 代码使用 SSE/SSE2 XMM 寄存器进行标量 FP 数学,而不是 x87,因为这个和其他使它更高效的东西。即使是现代 32 位代码也使用 XMM 寄存器进行标量数学运算。
当为 x87 编译时(没有 SSE3 fisttp
),编译器过去必须将 x87 舍入模式更改为截断,FP 存储到内存,然后再次更改舍入模式。 (然后从内存中重新加载整数,通常从堆栈上的本地重新加载,如果对它进行进一步的处理。)x87 对此糟糕。
是的, 非常慢,例如在 2006 年编写 @Kirjain 答案中的链接时,如果您仍然拥有 32 位 CPU 或正在使用 x86-64 CPU 运行 32 位代码。
不直接支持使用截断或默认(最近)以外的舍入模式进行转换,直到 SSE4.1 roundps
/roundpd
你最好的选择是魔术 - the 2006 link 中的数字技巧来自@Kirjain 的回答。
那里有一些不错的技巧,但仅适用于 double
-> 32 位整数。如果您有 float
,则不太可能值得扩展到 double
。
或者更常见的是,只需添加一个大数值来触发舍入,然后再次减去它以返回原始范围。这可以用于 float
而无需扩展到 double
,但我不确定让 floor
工作有多容易。
无论如何,这里明显的解决方案是 _mm256_floor_ps()
和 _mm256_cvtps_epi32
(vroundps
和 vcvtps2dq
)。它的非 AVX 版本可以与 SSE4.1 一起使用。
我不确定我们是否可以做得更好;如果您有一个庞大的数组要处理(并且无法设法将这项工作与其他工作交错),您可以将 MXCSR 舍入模式设置为“towards -Inf”(地板)并简单地使用 vcvtps2dq
(使用当前的舍入模式)。然后放回去。但最好缓存阻止您的转换,或者在生成数据时即时执行,可能来自需要将 FP 舍入模式设置为默认最近的其他 FP 计算。
roundps
/pd/ss/sd 在 Intel CPU 上是 2 uop,但在 AMD Ryzen 上只有 1 uop(每 128 位 channel )。 cvtps2dq
也是 1 uop。打包的 double->int 转换还包括随机播放。标量 FP->int 转换(复制到整数寄存器)通常还需要额外的 uop。
因此,在某些情况下,魔术数字技巧有可能获胜;如果 _mm256_floor_ps()
+ cvt 是关键瓶颈的一部分(或者如果您有 double 并且想要 int32,则更有可能)。
@Cássio Renan 的 int foo = floorf(f)
如果使用 gcc -O3 -fno-trapping-math
(或 - ffast-math
),带有 SSE4.1 或 AVX 的 -march=
东西。 https://godbolt.org/z/ae_KPv
如果您将它与其他未手动矢量化的标量代码一起使用,这可能会很有用。特别是如果您希望编译器自动矢量化整个事情。
关于c++ - C++ 中的高效整数下限函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56417115/