在LLVM中,round函数比floor/ceil/int函数慢得多。

4

我正在通过执行循环来基准测试一些重要的例程,例如:

float *src, *dst;
for (int i=0; i<cnt; i++) dst[i] = round(src[i]);

所有目标都是AVX2,使用最新的CLANG。有趣的是,floor(x)、ceil(x)、int(x)...似乎都很快。但round(x)非常慢,查看反汇编代码发现一些奇怪的代码,而不是较新的SSE或AVX版本。即使通过引入一些依赖关系阻止向量化循环的能力,round也比较慢。对于floor等,生成的代码使用vroundss,而对于round,则有Spaghetti Code... 有什么想法吗?
编辑:我使用了-ffast-math,-mfpmath=sse,-fno-math-errno,-O3,-std=c++17,-march=core-avx2 -mavx2 -mfma

定义行为如何涉及“更多工作”? - user2864740
我不理解这个问题,但通常有很多方法可以得到结果。然后有sqrt等函数的errno问题...所以我的问题很简单,是否需要什么东西来使round()函数像其他例程一样快速。 - Vojtěch Melda Meluzín
就算说了也没什么用,使用-ffast-math编译选项生成的汇编代码对于roundfloor函数来说基本相同。 - SergeyA
好的,“round”函数将不得不分支,因为它可以向上或向下取整,而其他函数只能朝一个方向取整。你使用了哪些优化标志?你如何计时代码? - NathanOliver
@NathanOliver 这不是关于分支的问题。有一系列SSE舍入指令可以在硬件中高效地进行舍入,但由于某种原因它没有用于round,而是用于floor(除非打开-ffast-math)。我也很好奇为什么会这样。可能只是实现质量的问题。 - SergeyA
那就是 - 我正在使用 -ffast-math,-mfpmath=sse,-fno-math-errno! - Vojtěch Melda Meluzín
2个回答

3
问题在于SSE舍入模式中没有一个指定round的正确舍入方式:

这些函数将x四舍五入到最近的整数,但是会将中间值从0舍入至远离零的方向(不管当前的舍入方向,详见 fenv(3)),而不是像 rint(3) 一样舍入到最近的偶数。

如果你想要更快的代码,你可以尝试测试rint而不是round,因为它指定了SSE支持的舍入模式。

有趣,我不知道!我刚刚检查了一下,当手动使用_mm256_round_ps(a, 0+8)进行舍入时,AVX2版本生成了这段代码,它创建了vroundps ymm1,ymmword ptr [rsi+rdx*4+0C0h],8,看起来工作得很好。 - Vojtěch Melda Meluzín
8 操作数指定了四舍五入到最近偶数 -- 适用于 rint,但不适用于 round。 - Chris Dodd

1
需要翻译的内容:一个需要注意的问题是,像 floor(x + 0.5) 这样的表达式虽然与 round(x) 的语义不完全相同,但在几乎所有情况下都可以作为有效的替代品,我怀疑它比 floor(x) 慢多少倍。

那听起来相当简单,问题在哪里呢?我的意思是对于极高的值,它可能会趋近于无穷大之类的,但这似乎不是一个重要的问题 :) - Vojtěch Melda Meluzín
floor(x + 0.5),ceil(x - 0.5)和round(x)在处理精确为.5的有小数部分的输入时有所不同。我认为对于大多数情况而言,这被认为是一个任意的选择,不管怎样都没有太大关系。对于不能放在小数位中的尾数的过大的数字,所有这些表达式的行为都是相同的(它们只返回x)。 - yonil

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接