从if语句返回早期或从两种情况返回之间是否存在差异?

Mat*_*ipe 0 performance rust

在编写必须返回值的函数时,有两种类似的方法:

#1(从rustbyexample中提取)

// An integer division that doesn't `panic!`
fn checked_division(dividend: i32, divisor: i32) -> Option<i32> {
    if divisor == 0 {
        // Failure is represented as the `None` variant
        None
    } else {
        // Result is wrapped in a `Some` variant
        Some(dividend / divisor)
    }
}
Run Code Online (Sandbox Code Playgroud)

#2(上述变体)

// An integer division that doesn't `panic!`
fn checked_division(dividend: i32, divisor: i32) -> Option<i32> {
    if divisor == 0 {
        // Failure is represented as the `None` variant
        return None
    }
    // Result is wrapped in a `Some` variant
    Some(dividend / divisor)
}
Run Code Online (Sandbox Code Playgroud)

我曾经写过像第二个片段,但我已经看到在Rust编程语言的每个例子中或者Rust By Example他们使用第一个案例.考虑到如上所述的匹配所有可能性代码,它只是造型还是存在性能差异?第一个是好的做法还是完全取决于我?

Fra*_*gné 5

Rust操场上,您可以使用ASMLLVM IR按钮查看某些代码如何编译为汇编程序(机器代码)或LLVM的中间表示.LLVM IR通常更容易阅读,因为它比汇编程序更高级.

让我们分析一下这段代码:

use std::io::BufRead;

// An integer division that doesn't `panic!`
#[inline(never)]
fn checked_division(dividend: i32, divisor: i32) -> Option<i32> {
    if divisor == 0 {
        // Failure is represented as the `None` variant
        None
    } else {
        // Result is wrapped in a `Some` variant
        Some(dividend / divisor)
    }
}

// An integer division that doesn't `panic!`
#[inline(never)]
fn checked_division2(dividend: i32, divisor: i32) -> Option<i32> {
    if divisor == 0 {
        // Failure is represented as the `None` variant
        return None
    }
    // Result is wrapped in a `Some` variant
    Some(dividend / divisor)
}

fn main() {
    let stdin = std::io::stdin();
    let i: i32 = stdin.lock().lines().next().unwrap().unwrap().parse().unwrap();
    let j: i32 = stdin.lock().lines().next().unwrap().unwrap().parse().unwrap();
    println!("{:?}", checked_division(i, j));
    println!("{:?}", checked_division2(i, j));
}
Run Code Online (Sandbox Code Playgroud)

(注意:我正在执行I/O以获取编译器无法优化的值;常量过于激进而且checked_division函数完全消失,即使使用#[inline(never)].)

首先,让我们在发布模式下编译此代码.LLVM IR是什么样的?这是checked_division:

; Function Attrs: noinline uwtable
define internal fastcc i64 @_ZN16checked_division20h2cc10ba72e80f410faaE(i32, i32) unnamed_addr #0 {
entry-block:
  switch i32 %1, label %next1 [
    i32 0, label %join
    i32 -1, label %cond2
  ]

next1:                                            ; preds = %entry-block, %cond2
  %2 = sdiv i32 %0, %1
  %phitmp = zext i32 %2 to i64
  %phitmp5 = shl nuw i64 %phitmp, 32
  br label %join

cond2:                                            ; preds = %entry-block
  %3 = icmp eq i32 %0, -2147483648
  br i1 %3, label %cond4, label %next1

cond4:                                            ; preds = %cond2
  tail call void @_ZN9panicking5panic20h77d028a733b1a80eiEKE({ %str_slice, %str_slice, i32 }* noalias nonnull readonly dereferenceable(40) @panic_loc3962)
  unreachable

join:                                             ; preds = %entry-block, %next1
  %sret_slot.sroa.0.0 = phi i64 [ 1, %next1 ], [ 0, %entry-block ]
  %sret_slot.sroa.3.0 = phi i64 [ %phitmp5, %next1 ], [ 0, %entry-block ]
  %4 = or i64 %sret_slot.sroa.3.0, %sret_slot.sroa.0.0
  ret i64 %4
}
Run Code Online (Sandbox Code Playgroud)

这是checked_division2:

; Function Attrs: noinline uwtable
define internal fastcc i64 @_ZN17checked_division220h9ae6c6af45a9a593DaaE(i32, i32) unnamed_addr #0 {
entry-block:
  switch i32 %1, label %next1 [
    i32 0, label %return
    i32 -1, label %cond2
  ]

next1:                                            ; preds = %entry-block, %cond2
  %2 = sdiv i32 %0, %1
  %phitmp = zext i32 %2 to i64
  %phitmp5 = shl nuw i64 %phitmp, 32
  br label %return

return:                                           ; preds = %entry-block, %next1
  %sret_slot.sroa.0.0 = phi i64 [ 1, %next1 ], [ 0, %entry-block ]
  %sret_slot.sroa.3.0 = phi i64 [ %phitmp5, %next1 ], [ 0, %entry-block ]
  %3 = or i64 %sret_slot.sroa.3.0, %sret_slot.sroa.0.0
  ret i64 %3

cond2:                                            ; preds = %entry-block
  %4 = icmp eq i32 %0, -2147483648
  br i1 %4, label %cond4, label %next1

cond4:                                            ; preds = %cond2
  tail call void @_ZN9panicking5panic20h77d028a733b1a80eiEKE({ %str_slice, %str_slice, i32 }* noalias nonnull readonly dereferenceable(40) @panic_loc3964)
  unreachable
}
Run Code Online (Sandbox Code Playgroud)

如果你比较你最喜欢的diff工具中的两个函数(并排diff工具在这里更好,因为那里有一点噪音),你会注意到唯一的主要区别是checked_division有一个块join在最后调用,而checked_division2有一个称为块return之间next1cond2-但这些块的内容是相同的.换句话说,功能完全相同.

我们可以注意到的另一件事是功能还是恐慌,如果你尝试执行-2147483648/-1(-1测试的一部分switch,在开始时,-2147483648测试是对下cond2:). That's because this particular division overflows, and [LLVM'ssdiv`指令] 3个文档这种情况下,导致未定义的行为,因此Rust编译器通过恐慌来为您的函数提供明确定义的行为.