比 LLVM 中的 floor/ceil/int 慢得多

Posted

技术标签:

【中文标题】比 LLVM 中的 floor/ceil/int 慢得多【英文标题】:round much slower than floor/ceil/int in LLVM 【发布时间】:2018-12-28 18:18:09 【问题描述】:

我通过执行循环来对一些基本例程进行基准测试,例如:

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 版本。即使通过引入一些依赖性来阻止矢量化循环的能力,轮次也慢了 10 倍。对于 floor 等,生成的代码使用 vroundss,对于 round 有意大利面条代码......有什么想法吗?

编辑:我正在使用 -ffast-math、-mfpmath=sse、-fno-math-errno、-O3、-std=c++17、-march=core-avx2 -mavx2 -mfma

【问题讨论】:

defined behavior 怎么可能涉及“更多工作”? 我不明白这个问题,但通常有很多方法可以得到结果。然后是 sqrt 等的 errno 问题......所以可能的问题很简单,是否需要像其他例程一样快速循环。 对于它的价值,使用 -ffast-math 生成的程序集对于 roundfloor 来说实际上是相同的。 好吧,round 将不得不分支,因为它可以向上或向下,而其他所有方向都只是一个方向。您使用了哪些优化标志?你是如何计时的? @NathanOliver 这与分支无关。有一系列 SSE 舍入指令可以在硬件中有效地进行舍入,但由于某种原因,它不用于 round,而是用于 floor(除非打开 -ffast-math)。我也很好奇为什么会这样。可能只是实施质量问题。 【参考方案1】:

问题是 SSE 舍入模式都没有为 round 指定正确的舍入:

这些函数将 x 舍入到最接近的整数,但从零开始舍入一半的情况 (无论当前舍入方向如何,请参见 fenv(3)),而不是最接近的 偶数整数,如 rint(3)。

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

【讨论】:

有趣,不知道!我刚刚检查了 AVX2 版本在手动使用 _mm256_round_ps(a, 0+8) 进行舍入时生成了此代码,它创建了 vroundps ymm1,ymmword ptr [rsi+rdx*4+0C0h],8,这似乎工作正常。 8 操作数指定四舍五入到最接近的偶数 -- 对 rint 正确,但对舍入不正确。【参考方案2】:

需要注意的一点是,像 floor(x + 0.5) 这样的表达式虽然没有与 round(x) 完全相同的语义,但在几乎所有用例中都是有效的替代品,我怀疑它比 @ 慢近 10 倍987654323@.

【讨论】:

这实际上听起来相当简单,那么问题出在哪里?我的意思是对于极高的值,它可能最终会变成无穷大或其他东西,但这似乎不是一个重要的问题:) floor(x + 0.5)、ceil(x - 0.5) 和 round(x) 在处理小数部分正好为 0.5 的输入的方式上有所不同。我认为对于大多数目的,它被认为是一种任意选择,无论哪种方式都无关紧要。对于太大而无法放入尾数中的小数位数的数字,所有这些表达式的行为都相同(它们只是返回 x)

以上是关于比 LLVM 中的 floor/ceil/int 慢得多的主要内容,如果未能解决你的问题,请参考以下文章

讲一个编译器的故事,Gcc和LLVM的明争暗斗

LLVM编译器

LLVM 中的抽象解释

Xcode的控制台调试命令

缓存型 C++ 编译器 Zapcc 开源,号称比 Clang 更快

LLVM 中的向量加法程序