performance - Are there differences between returning early from an if statement or returning from both cases? -
when writing function must return value, there 2 similar ways doing so:
#1 (extracted rustbyexample)
// integer division doesn't `panic!` fn checked_division(dividend: i32, divisor: i32) -> option<i32> { if divisor == 0 { // failure represented `none` variant none } else { // result wrapped in `some` variant some(dividend / divisor) } }
#2 (an variation of above)
// integer division doesn't `panic!` fn checked_division(dividend: i32, divisor: i32) -> option<i32> { if divisor == 0 { // failure represented `none` variant return none } // result wrapped in `some` variant some(dividend / divisor) }
i used write second snippet, i've seen in every example in the rust programming language or rust example use first case. considering match-all-possibilities code above, styling or there performance differences? first 1 practice or me?
on rust playground, can use asm , llvm ir buttons view how code compiled assembler (machine code) or llvm's intermediate representation. llvm ir easier read, it's more high-level assembler.
use std::io::bufread; // integer division doesn't `panic!` #[inline(never)] fn checked_division(dividend: i32, divisor: i32) -> option<i32> { if divisor == 0 { // failure represented `none` variant none } else { // result wrapped in `some` variant some(dividend / divisor) } } // integer division doesn't `panic!` #[inline(never)] fn checked_division2(dividend: i32, divisor: i32) -> option<i32> { if divisor == 0 { // failure represented `none` variant return none } // result wrapped in `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)); }
(note: i'm performing i/o values compiler can't optimize; constants optimized aggressively , checked_division
functions disappear, #[inline(never)]
.)
first, let's compile code in release mode. what's llvm ir like? here's 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 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 }
and here's 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 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 }
if compare both functions in favorite diff tool (a side-by-side diff tool better here, since there's bit of noise there), you'll notice major difference checked_division
has block called join
@ end, while checked_division2
has block called return
between next1
, cond2
– contents of these blocks same. in other words, functions equivalent.
another thing can notice function still panics if try perform -2147483648 / -1 (the -1 test part of switch
@ beginning, -2147483648 test right under cond2:). that's because particular division overflows, , [llvm's
sdiv` instruction]3 documents case leading undefined behavior, rust compiler gives function well-defined behavior panicking instead.
Comments
Post a Comment