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.

let's analyze code:

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'ssdiv` instruction]3 documents case leading undefined behavior, rust compiler gives function well-defined behavior panicking instead.


Comments

Popular posts from this blog

sublimetext3 - what keyboard shortcut is to comment/uncomment for this script tag in sublime -

java - No use of nillable="0" in SOAP Webservice -

ubuntu - Laravel 5.2 quickstart guide gives Not Found Error -