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

时间:2016-02-06 01:05:05

标签: 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)
    }
}

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

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

2 个答案:

答案 0 :(得分:5)

Rust playground上,您可以使用 ASM LLVM IR 按钮查看某些代码如何编译为汇编程序(机器代码)或LLVM&# 39;中间表示。 LLVM IR通常更容易阅读,因为它比汇编程序更高级。

Let's analyze this code

<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
<div id="chart_div"></div>

(注意:我正在执行I / O以获取编译器无法优化的值;常量过于激进且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)); } 函数完全消失,即使使用{{1} }。)

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

#[inline(never)]

这里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
}

如果你比较你最喜欢的差异工具中的两个功能(并排差异工具在这里更好,因为那里有一点噪音),你会注意到唯一的主要区别是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 } 的块,而checked_divisionjoinchecked_division2之间有一个名为return的块 - 但内容这些块都是一样的。换句话说,这些功能是完全等效的。

我们可以注意到的另一件事是,如果你尝试执行-2147483648 / -1,函数仍然会发生恐慌(-1测试是开头的next1的一部分, -2147483648测试正好位于cond2 sdiv`指令下3将此情况记录为导致未定义的行为,因此Rust编译器通过恐慌来为您的函数提供明确定义的行为。

答案 1 :(得分:4)

这两个变体肯定会产生相同的代码。所以这只是风格问题。标准风格是使用第一种风格。