【问题标题】:Chaining checked arithmetic operations in Rust在 Rust 中链接检查算术运算
【发布时间】:2023-03-22 22:33:01
【问题描述】:

在进行带有溢出检查的整数运算时,计算通常需要组合多个算术运算。在 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))
}

但是,这告诉编译器为每个基本操作生成一个分支。我遇到了使用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)
    }
}

不考虑溢出继续计算并使用按位或聚合溢出标志可确保在整个评估中最多执行一个分支(前提是overflowing_* 的实现生成无分支代码)。这种优化友好的方法比较麻烦,在处理中间值时需要谨慎。

有没有人了解 Rust 编译器如何在各种 CPU 架构上优化上述任一模式,以判断显式展开是否值得,尤其是对于更复杂的表达式?

【问题讨论】:

  • 不太清楚你的问题是什么。理想的答案包含什么?
  • 我希望人们提出轶事,或者更好地观察一致的编译器行为,当展开的方法最终比Option 更符合人体工程学的代码好得多时。

标签: rust integer-overflow integer-arithmetic


【解决方案1】:

有没有人了解 Rust 编译器如何在各种 CPU 架构上优化上述任一模式,以判断显式展开是否值得,尤其是对于更复杂的表达式?

您可以使用playground 检查 LLVM 如何优化事物:只需单击“LLVM IR”或“ASM”而不是“运行”。在要检查的函数上粘贴#[inline(never)],并注意传递运行时参数,以避免不断折叠。如here

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);
}

然而,您将得到的答案是,不幸的是,Rust 和 LLVM 中的溢出内在函数是为了方便而不是为了性能而编码的。这意味着,虽然显式展开优化得很好,但目前依靠 LLVM 来优化检查代码是不现实的。

通常这不是问题;但对于性能热点,您可能需要手动展开。

注意:这种性能不足也是Release模式下默认禁用溢出检查的原因。

【讨论】:

  • 感谢游乐场提示!在 x86-64 上,优化器从两个示例中生成非常相似的代码,但这仅仅是因为它将oflo1 | oflo2 | ... 变成了一系列测试和分支。如果这毕竟不是一个真正的问题,那么惯用代码可能不是手动优化的主要主题。在典型计算中,分支往往会得到很好的预测(溢出是异常的或由误用引起的),但其中可能有很多分支会增加预测器的弹出压力,尤其是在代码是通用代码的情况下。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-12-03
  • 1970-01-01
  • 2015-03-03
  • 2015-02-06
  • 1970-01-01
  • 2016-03-10
  • 1970-01-01
相关资源
最近更新 更多