mza*_*uev 5 integer-overflow rust integer-arithmetic
在执行带有溢出检查的整数算术时,计算通常需要组合多个算术运算。在 Rust 中链接检查算术的一种直接方法是使用checked_*方法和Option链接:
fn calculate_size(elem_size: usize,
length: usize,
offset: usize)
-> Option<usize> {
elem_size.checked_mul(length)
.and_then(|acc| acc.checked_add(offset))
}
Run Code Online (Sandbox Code Playgroud)
但是,这告诉编译器为每个基本操作生成一个分支。我遇到了一种使用overflowing_*方法更展开的方法:
fn calculate_size(elem_size: usize,
length: usize,
offset: usize)
-> Option<usize> {
let (acc, oflo1) = elem_size.overflowing_mul(length);
let (acc, oflo2) = acc.overflowing_add(offset);
if oflo1 | oflo2 {
None
} else {
Some(acc)
}
}
Run Code Online (Sandbox Code Playgroud)
不考虑溢出而继续计算并使用按位 OR 聚合溢出标志可确保在整个评估中最多执行一个分支(前提是overflowing_*生成无分支代码的实现)。这种优化友好的方法更麻烦,并且在处理中间值时需要谨慎。
有没有人有过 Rust 编译器如何在各种 CPU 架构上优化上述任一模式的经验,以判断显式展开是否值得,尤其是对于更复杂的表达式?
有没有人有过 Rust 编译器如何在各种 CPU 架构上优化上述任一模式的经验,以判断显式展开是否值得,尤其是对于更复杂的表达式?
您可以使用操场来检查 LLVM 如何优化事物:只需单击“LLVM IR”或“ASM”而不是“运行”。#[inline(never)]在要检查的函数上贴上 a ,并注意传递运行时参数,以避免常量折叠。在这里:
use std::env;
#[inline(never)]
fn calculate_size(elem_size: usize,
length: usize,
offset: usize)
-> Option<usize> {
let (acc, oflo1) = elem_size.overflowing_mul(length);
let (acc, oflo2) = acc.overflowing_add(offset);
if oflo1 | oflo2 {
None
} else {
Some(acc)
}
}
fn main() {
let vec: Vec<usize> = env::args().map(|s| s.parse().unwrap()).collect();
let result = calculate_size(vec[0], vec[1], vec[2]);
println!("{:?}",result);
}
Run Code Online (Sandbox Code Playgroud)
然而,你会得到的答案是,不幸的是,Rust 和 LLVM 中的溢出内在函数是为了方便而不是性能而编码的。这意味着虽然显式展开优化得很好,但目前指望 LLVM 来优化已检查的代码是不现实的。
通常这不是问题;但对于性能热点,您可能需要手动展开。
注意:这种性能不足也是在 Release 模式下默认禁用溢出检查的原因。