我可以在每次除法发生时禁用检查零除法吗?

问题描述 投票:0回答:3

为了更好地理解Rust的panic/异常机制,我写了下面一段代码:

#![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);
}

我想检查 Rust 如何处理被零除的情况。最初我认为它要么是在面对一个未处理的 SIGFPE 并死亡,要么是它实现了一个处理程序并将其重新路由到一个恐慌(现在可以处理吗?)。

代码很冗长,因为我想确保 Rust 在编译时知道某些内容为零(因此用户输入为零)时不会做任何“智能”的事情。只需给它一个“A”,它就可以解决问题。

我发现 Rust 实际上生成的代码每次在除法发生之前都会检查零除法。我什至看了一次大会。 :-)

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

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

我认为事先检查每个部门离“零成本”还很远。你怎么认为?我错过了一些明显的东西吗?

performance exception rust divide-by-zero
3个回答
13
投票

我认为事前检查每个部门离“零成本”还很远。你觉得怎么样?

你测量了什么?

执行的指令数量是性能的一个非常差的指标;矢量化代码通常更详细,但速度更快。

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

由于故意除以 0 的可能性相当小,并且偶然除以 0 的可能性稍大一些,因此分支总是会被正确预测 除了 当发生除以 0 时。但是,考虑到恐慌的代价,错误预测的分支是你最不用担心的。

因此,成本为:

  • 稍微胖一点的组件,
  • 分支预测器中的一个被占用的槽。

确切的影响很难确定,对于数学密集的代码来说它可能会产生影响。不过我要提醒您,整数除法一开始大约需要 100 个周期1,因此数学密集型代码会尽可能避免使用它(它可能是 CPU 中最耗时的指令)。

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


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

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

两者都具有除数为 0 的未定义行为。

Rust 旨在消除未定义行为,因此 has 防止除数为 0 的情况发生,以免优化器破坏发出的代码而无法修复。

它使用 LLVM 手册中建议的检查。


5
投票

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

是的,你可以:

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

在文档中指出:

这是一个仅限夜间的实验性 API。 (核心_内在)

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

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


1
投票

来自 Rust 社区 Discord 服务器上的 Cherry,另一种方法是使用分子类型的

NonZero
版本作为分母:

#[no_mangle]
pub fn panic_div(val: u32) {
    _ = std::hint::black_box(5) / std::hint::black_box(val);
}

use std::num::NonZeroU32;
#[no_mangle]
pub fn no_panic(val: NonZeroU32) {
    _ = std::hint::black_box(5) / std::hint::black_box(val);
}

产生:

panic_div:
        push    rax
        mov     dword ptr [rsp + 4], 5
        lea     rax, [rsp + 4]
        mov     dword ptr [rsp], edi
        mov     rax, rsp
        cmp     dword ptr [rsp], 0
        je      .LBB0_2
        pop     rax
        ret
.LBB0_2:
        lea     rdi, [rip + .L__unnamed_1]
        call    qword ptr [rip + core::panicking::panic_const::panic_const_div_by_zero::h0158edae44a9fd47@GOTPCREL]

no_panic:
        mov     dword ptr [rsp - 8], 5
        lea     rax, [rsp - 8]
        mov     dword ptr [rsp - 4], edi
        lea     rax, [rsp - 4]
        ret
© www.soinside.com 2019 - 2024. All rights reserved.