在Rust语言中,哪些整数操作有更高性能的替代方法?

9
在Rust中编写整数函数时(例如像素处理),使用性能最高的操作很有用,类似于C/C++。虽然参考手册解释了行为的更改,但并不总是清楚哪些方法比标准整数算术运算更高效。(请参见注1)我认为wrapping_add编译后等同于C语言的加法。在标准操作(加/减/乘/取模/除/移位/位操作...)中,哪些操作有更高效的替代方法,而默认情况下没有使用?
注: 1.通过“标准”,我指的是使用符号a+b、i/k或c%e等的整数算术。编写数学表达式时会使用它们,除非您有特殊需要使用其中一种包装或返回溢出的方法。 2.我意识到回答这个问题可能需要一些研究。所以我很乐意通过查看生成的汇编来检查使用未检查/原始操作的哪些操作。 3.如果检查/未检查操作之间的速度差异不显着,那么我仍然希望能够编写一个“快速”版本的函数与“安全”版本进行比较,以得出自己对于给定函数是否合理的结论。 4.提到像素处理后,SIMD被提出作为一种可能的解决方案。即使这是个好建议,仍然有一些情况无法使用SIMD进行优化,因此仍需考虑快速整数算术的一般情况。

2
我不太清楚你所说的“标准”整数算术是什么意思。例如,在 Release 模式下,默认情况下加法是 wrapping_add,因为 rustc 在 Release 模式下不会检查下溢/上溢,除非你明确要求检查。 - Matthieu M.
澄清了我所说的“标准”操作的含义。 - ideasman42
3个回答

8
在标准操作中(加/减/乘/取模/除/移位/位运算...),有哪些操作有更高性能的替代方案,而这些替代方案并不是默认使用的?
请注意,Rust 是为了性能而设计的;因此,在 Debug 模式下整数操作会被检查,但在 Release 模式下,它们被定义为“wrap”,除非您明确地告诉编译器否则。
因此,在默认选项的 Release 模式下,以下操作严格来说没有任何性能差异:
+ 和 wrapping_add - 和 wrapping_sub * 和 wrapping_mul / 和 wrapping_div % 和 wrapping_rem << 和 wrapping_shl >> 和 wrapping_shr 对于无符号整数,性能与 C 或 C++ 严格相同;然而,对于有符号整数,优化器可能会产生不同的结果,因为在 C 和 C++ 中,有符号整数的下溢/上溢是未定义行为(gcc 和 Clang 接受一个 -fwrapv 标志,以强制即使对于有符号整数也进行包装,但这不是默认值)。
我预计使用 checked_ *、overflow_ * 和 saturating_ * 方法通常会更慢。
那么,当您打开开关并明确要求检查算术时,会发生什么有趣的事情呢?
目前,Rust 实现1是一种精确的下溢/上溢检查实现。每个加法、减法、乘法等都是独立检查的,优化器不擅长融合这些分支。
具体而言,精确实现排除了临时溢出:5 + x - 5 不能被优化为 x,因为 5 + x 可能会溢出。它也排除了自动向量化。
仅当优化器能够证明没有溢出(通常无法)时,您可能希望重新获得更易于优化的无分支路径。
应该注意的是,在一般软件中,影响几乎不可见,因为算术指令只占总成本的一小部分。然而,当这个比例上升时,它可能非常明显,并且在 Clang 的 SPEC2006 基准测试的某些部分中显示出来。
这种开销足以被认为不适合默认激活检查。 1 这是由于 LLVM 方面的技术限制;Rust 实现只是委托给 LLVM。


在未来,有希望提供一种模糊实现的检查。模糊实现的想法是,不是检查每一个操作,而是执行它们并设置标志或在下溢/上溢的情况下使值失效。然后,在使用结果之前,执行检查(分支)。
据Joe Duffy称,Midori中有这样的实现,性能影响几乎不可察觉,因此似乎是可行的。我不知道LLVM是否已经有类似的任何努力。

5
Rust不保证其操作的速度。如果需要保证,就需要调用汇编语言。
话虽如此,目前Rust会转发到LLVM,因此您可以直接调用intrinsics(与LLVM intrinsics一一对应),并使用这些保证。但无论您做什么不是asm,要注意优化器可能有不同意见,从而使您对LLVM intrinsics的手动调用未经优化。
话虽如此,Rust力求尽可能快,因此您可以假定(或仅查看标准库的实现)所有具有相同LLVM intrinsic的操作将映射到该LLVM intrinsic,从而达到LLVM能够完成的速度。
没有一般规则适用于给定基本算术运算中最快的操作,因为它完全取决于您的用例。

这不是汇编语言。我感觉LLVM也可以优化汇编语言,我认为在某些情况下这是可以接受的。 - Shepmaster
哦,不知道那个,好吧...在那种情况下无论如何都没有押注。优化编译器不知道你想要什么,因此它们会为某些适应性进行优化。 - oli_obk
这是否意味着在任何情况下,发布模式不会因整数溢出而引发panic,或者添加检查以使其比相同的C表达式更慢?我不确定这一点,因为例如,wrapping_shl被记录为无需恐慌,并且没有提到发布模式。 - ideasman42
你不会遇到额外的恐慌。与相同的 C 表达式相比,它会更慢,仅取决于其他代码,因为 LLVM 无法进行极端优化,否则会引入未定义行为。操作本身将与 C 相当。 - oli_obk

3

思考像素处理

那么你不应该考虑单值操作,而是要使用SIMD指令。目前这些指令在稳定的Rust中尚不可用,但一些可以通过功能门控函数访问,所有指令都可以通过汇编语言访问。

LLVM是否能像clang一样将代码优化为SIMD?

正如aochagavia已经回答的,是的,LLVM会自动向量化某些类型的代码。然而,当您需要最高性能时,通常不希望将自己置于优化器的掌控之下。我倾向于在我的普通代码中期望自动向量化,然后为我的重型数学内核编写直线代码,接着编写SIMD代码,并测试其正确性和基准速度。


LLVM是否可以像clang一样将代码优化为SIMD?- http://llvm.org/docs/Vectorizers.html - ideasman42
2
当然!它确实是。 - aochagavia
1
尽管我提到了像素处理,但这个答案忽略了一些像素处理操作无法转换为SIMD的情况。因此,虽然有帮助,但仍存在一种情况,即您想要执行快速整数运算时 - 无法使用SIMD解决。 - ideasman42

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