每次分裂发生时,我都可以禁用零分割检查吗?

Laz*_*535 6 performance exception divide-by-zero rust

为了更好地理解Rusts恐慌/异常机制,我编写了以下代码:

#![feature(libc)]

extern crate libc;

fn main() {
    let mut x: i32;
    unsafe {
      x = libc::getchar();
    }

    let y = x - 65;
    println!("{}", x);

    let z = 1 / y;
    println!("{}", z);
}
Run Code Online (Sandbox Code Playgroud)

我想检查Rust如何处理除零案例.最初我假设它要么面对未处理的SIGFPE并且正在死亡,要么实施了一个处理程序并将其重新路由到恐慌(现在可以处理?).

代码是冗长的,因为我想确保Rust在编译时知道某些东西是零,因此用户输入时不会做任何"智能".只要给它一个'A'就可以了.

我发现Rust实际上产生的代码每次在除法发生之前检查零除法.我甚至看了一次装配.:-)

长话短说:我可以禁用此行为吗?我想对于较大的数据集,这可能会产生很大的性能影响.为什么不使用我们的CPU能力为我们检测这些东西?我可以设置自己的信号处理程序并处理SIGFPE吗?

根据Github上一个问题,前一段时间的情况肯定是不同的.

我认为事先检查每个部门远离"零成本".你怎么看?我错过了一些明显的东西吗

Mat*_* M. 8

我认为事先检查每个部门远离"零成本".你怎么看?

你测量了什么?

执行的指令数量很少代表性能; 矢量化代码通常更冗长,但速度更快.

所以真正的问题是:这个分支的成本是多少?

由于故意除以0是不太可能的,并且偶然地做它只是稍微更可能,除非出现0除法,否则将始终正确地预测分支.但是,考虑到恐慌的代价,错误预测的分支是您最不担心的.

因此,成本是:

  • 一个稍胖的组装,
  • 分支预测器中的占用槽.

确切的影响难以取得资格,对于数学繁重的代码,它可能会产生影响.虽然我会提醒你一个整数除法是〜100个循环1开始,所以数学繁重的代码会尽可能地避开它(它可能是你CPU中最耗时的单指令).

1 请参阅Agner Fog的指令表:例如,在Intel Nehalem DIV上,64位积分上的IDIV的延迟分别为28到90个周期和37到100个周期.


除此之外,rustc在LLVM之上实现,它委托实际的代码生成.因此,在许多情况下,rustc受LLVM的支配,这就是其中之一.

LLVM有两个整数除法指令:udiv和sdiv.

两者都有未定义的行为,除数为0.

Rust旨在消除Undefined Behavior,因此必须防止0除以,以免优化器将发出的代码破坏而无法修复.

它使用LLVM手册中的建议进行检查.

  • _整数除法是 ~100 个周期_ <- 这对我来说真的很关键。有了这样的成本,分店实在是微不足道。我脑子里有不同的数字(2-4个周期)。很好的答案,谢谢! (2认同)
  • @Lazarus535:啊,但是在 C 和 C++ 整数除法中有未定义的行为(根据规范),因此 Clang 调用 `udiv` 和 `sdiv` 而不事先检查是“正常的”。 (2认同)
  • @Lazarus535 C 标准明确允许 Clang 在除以零时产生未定义的行为。另一方面,Rust 避免了安全代码中的未定义行为。如果 Rust 在整数类型上提供了一个“不安全的 fndivide_unchecked(divisor:T)”方法,它会保持在它的承诺范围内,但它不存在,可能是由于答案中解释的原因。 (2认同)

小智 5

长话短说:我可以禁用此行为吗?

是的你可以:std::intrinsics::unchecked_div(a, b)。你的问题也适用于余数(这就是 Rust 调用modulo 的方式):std::intrinsics::unchecked_rem(a, b)我在这里检查了汇编输出,将其与 C++ 进行比较。

在文档中它指出:

这是一个仅限夜间使用的实验性 API。(核心内部函数)

内在函数不太可能稳定,相反,它们应该通过标准库其余部分中的稳定接口来使用

因此,您必须使用夜间构建,并且由于Matthieu M.已经指出的原因,它不太可能以稳定的形式进入标准库。