首页 > 解决方案 > C++ 中的高效整数下限函数

问题描述

我想定义一个有效的整数下限函数,即从浮点数或双精度数执行截断到负无穷大的转换。

我们可以假设这些值不会发生整数溢出。到目前为止,我有几个选择

转换为 int 是出了名的慢。if 测试也是如此。我没有计时地板功能,但看到帖子声称它也很慢。

您能在速度、准确性或允许范围方面想出更好的选择吗?它不需要是便携式的。目标是最近的 x86/x64 架构。

标签: c++performancex86-64processing-efficiencyfloor

解决方案


转换为 int 是出了名的慢。

也许你从 x86-64 开始就一直生活在一块石头下,或者以其他方式错过了在 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你最好的选择是魔术数字技巧,如@Kirjain 答案中的 2006 链接。

那里有一些不错的技巧,但仅适用于double-> 32 位整数。double如果你有,不太可能值得扩展float

或者更常见的是,只需添加一个大数字来触发舍入,然后再次减去它以返回原始范围。这可以在float不扩展到的情况下工作double,但我不确定制作floor工作有多容易。


无论如何,这里明显的解决方案是_mm256_floor_ps()and _mm256_cvtps_epi32( vroundpsand vcvtps2dq)。它的非 AVX 版本可以与 SSE4.1 一起使用。

我不确定我们是否可以做得更好;如果您有一个庞大的数组要处理(并且无法设法将这项工作与其他工作交错),您可以将 MXCSR 舍入模式设置为“towards -Inf”(地板)并简单地使用vcvtps2dq(它使用当前的舍入模式) . 然后放回去。但是最好缓存阻止您的转换或在生成数据时即时执行,可能来自需要将 FP 舍入模式设置为默认最近的其他 FP 计算。

roundps/pd/ss/sd 在 Intel CPU 上是 2 uop,但在 AMD Ryzen 上只有 1 uop(每个 128 位通道)。 cvtps2dq也是 1 uop。打包的 double->int 转换还包括随机播放。标量 FP->int 转换(复制到整数寄存器)通常也为此花费额外的微指令。

因此,在某些情况下,魔术数字技巧有可能获胜;如果_mm256_floor_ps()+ cvt 是关键瓶颈的一部分(或者如果您有 double 并且想要 int32,则更有可能)可能值得调查。


@Cássio Renan'sint foo = floorf(f)实际上会自动矢量化,如果使用gcc -O3 -fno-trapping-math(或-ffast-math)编译,使用-march=具有 SSE4.1 或 AVX 的东西。 https://godbolt.org/z/ae_KPv

如果您将它与其他未手动矢量化的标量代码一起使用,这可能会很有用。特别是如果您希望编译器能够自动矢量化整个事情。


推荐阅读