In Erik Michaels-Ober's great talk Writing Fast Ruby: Video @ Baruco 2014, Slide, he presents us many idioms that leads to Faster Ruby. He inspired me and I want to document these to let more people know. I try to link to real commit for people to see this can really benefits in real world. But this does not mean you can always replace one with another, depends on the context (e.g. gsub
versus tr
).
Each idiom has a corresponding code example resides in code.
All results 🏃 with Ruby 2.2.0-preview1 on OS X 10.9.4. Your results may vary but you get the idea. : )
Let's write faster code, together! <3
👬 👭 👬 👬 👭 👫 👭 👬 👭 👫 👬 👫 👯 👫 👬 👬 👬 👬 👫 👬 👭 👭 👫 👫 👬 👭 👬 👯 👫
Use benchmark-ips.
require 'benchmark/ips' def slow end def fast end Benchmark.ips do |x| x.report('slow') { slow } x.report('fast') { fast } x.compare! end
Parallel Assignment vs Sequential Assignment code
Parallel Assignment allocates an extra array.
$ ruby -v code/general/assignment.rb ruby 2.2.0preview1 (2014-09-17 trunk 47616) [x86_64-darwin13] Calculating ------------------------------------- Parallel Assignment 113719 i/100ms Sequential Assignment 145620 i/100ms ------------------------------------------------- Parallel Assignment 2927066.6 (±4.8%) i/s - 14669751 in 5.024304s Sequential Assignment 6660499.7 (±4.5%) i/s - 33346980 in 5.018252s Comparison: Sequential Assignment: 6660499.7 i/s Parallel Assignment: 2927066.6 i/s - 2.28x slower
begin...rescue
vs respond_to?
for Control Flow code
$ ruby -v code/general/begin-rescue-vs-respond-to.rb ruby 2.2.0preview1 (2014-09-17 trunk 47616) [x86_64-darwin13] Calculating ------------------------------------- begin...rescue 35978 i/100ms respond_to? 131419 i/100ms ------------------------------------------------- begin...rescue 474725.9 (±2.7%) i/s - 2374548 in 5.005720s respond_to? 3970459.3 (±3.4%) i/s - 19844269 in 5.004508s Comparison: respond_to?: 3970459.3 i/s begin...rescue: 474725.9 i/s - 8.36x slower
String Concatenation code
$ ruby code/string/concatenation.rb ruby 2.2.0preview1 (2014-09-17 trunk 47616) [x86_64-darwin13] Calculating ------------------------------------- String#+ 112031 i/100ms String#concat 118035 i/100ms String#append 120165 i/100ms "foo" "bar" 143485 i/100ms ------------------------------------------------- String#+ 3172217.8 (±4.6%) i/s - 15908402 in 5.026528s String#concat 3326443.4 (±3.9%) i/s - 16642935 in 5.011320s String#append 3482180.3 (±4.3%) i/s - 17423925 in 5.014100s "foo" "bar" 5727567.5 (±4.7%) i/s - 28697000 in 5.022824s Comparison: "foo" "bar": 5727567.5 i/s String#append: 3482180.3 i/s - 1.64x slower String#concat: 3326443.4 i/s - 1.72x slower String#+: 3172217.8 i/s - 1.81x slower
String#gsub
vs String#sub
code
$ ruby -v code/string/gsub-vs-sub.rb ruby 2.2.0preview1 (2014-09-17 trunk 47616) [x86_64-darwin13] Calculating ------------------------------------- String#gsub 43991 i/100ms String#sub 52787 i/100ms ------------------------------------------------- String#gsub 584475.4 (±5.3%) i/s - 2947397 in 5.058064s String#sub 751518.3 (±4.2%) i/s - 3800664 in 5.066852s Comparison: String#sub: 751518.3 i/s String#gsub: 584475.4 i/s - 1.29x slower
String#gsub
vs String#tr
code
$ ruby -v code/string/gsub-vs-tr.rb ruby 2.2.0preview1 (2014-09-17 trunk 47616) [x86_64-darwin13] Calculating ------------------------------------- String#gsub 45306 i/100ms String#tr 94363 i/100ms ------------------------------------------------- String#gsub 603615.0 (±3.9%) i/s - 3035502 in 5.036649s String#tr 2134064.7 (±3.2%) i/s - 10663019 in 5.002019s Comparison: String#tr: 2134064.7 i/s String#gsub: 603615.0 i/s - 3.54x slower
String#match
vs String#start_with?
/String#end_with?
code (start) code (end)
⚠️
Sometimes you cant replace regexp withstart_with?
,
for example:"a\nb" =~ /^b/ #=> 2
but"a\nb" =~ /\Ab/ #=> nil
.
⚠️
You can combinestart_with?
andend_with?
to replaceerror.path =~ /^#{path}(\.rb)?$/
to this
error.path.start_with?(path) && error.path.end_with?('.rb', '')
—— @igas rails/rails#17316
$ ruby -v code/string/start-string-checking-match-vs-start_with.rb ruby 2.2.0preview1 (2014-09-17 trunk 47616) [x86_64-darwin13] Calculating ------------------------------------- String#=~ 50281 i/100ms String#start_with? 106594 i/100ms ------------------------------------------------- String#=~ 1007225.5 (±30.3%) i/s - 4374447 in 5.006442s String#start_with? 4088874.6 (±10.5%) i/s - 20252860 in 5.014760s Comparison: String#start_with?: 4088874.6 i/s String#=~: 1007225.5 i/s - 4.06x slower
$ ruby -v code/string/end-string-checking-match-vs-start_with.rb ruby 2.2.0preview1 (2014-09-17 trunk 47616) [x86_64-darwin13] Calculating ------------------------------------- String#=~ 43826 i/100ms String#end_with? 84810 i/100ms ------------------------------------------------- String#=~ 926098.8 (±28.3%) i/s - 4075818 in 5.025425s String#end_with? 2792475.3 (±12.5%) i/s - 13739220 in 5.004958s Comparison: String#end_with?: 2792475.3 i/s String#=~: 926098.8 i/s - 3.02x slower
String#casecmp
vs String#downcase + ==
code
$ ruby -v code/string/casecmp-vs-downcase-\=\=.rb ruby 2.2.0preview1 (2014-09-17 trunk 47616) [x86_64-darwin13] Calculating ------------------------------------- String#downcase + == 112715 i/100ms String#casecmp 121906 i/100ms ------------------------------------------------- String#downcase + == 3348751.8 (±5.2%) i/s - 16794535 in 5.029945s String#casecmp 4214478.7 (±4.7%) i/s - 21089738 in 5.016033s Comparison: String#casecmp: 4214478.7 i/s String#downcase + ==: 3348751.8 i/s - 1.26x slower
Array#shuffle.first
vs Array#sample
code
Array#shuffle
allocates an extra array.
Array#sample
indexes into the array without allocating an extra array.
This is the reason why Array#sample exists.
—— @sferik rails/rails#17245
$ ruby -v code/array/shuffle-first-vs-sample.rb ruby 2.2.0preview1 (2014-09-17 trunk 47616) [x86_64-darwin13] Calculating ------------------------------------- Array#shuffle.first 29000 i/100ms Array#sample 121248 i/100ms ------------------------------------------------- Array#shuffle.first 347632.6 (±8.0%) i/s - 1740000 in 5.041768s Array#sample 6155292.3 (±6.6%) i/s - 30675744 in 5.008394s Comparison: Array#sample: 6155292.3 i/s Array#shuffle.first: 347632.6 i/s - 17.71x slower
Enumerable#map
...Array#flatten
vs Enumerable#flat_map
code
-- @sferik rails/rails@3413b88, Replace map.flatten with flat_map, Replace map.flatten(1) with flat_map
ruby -v code/enumerable/map-flatten-vs-flat_map.rb ruby 2.2.0preview1 (2014-09-17 trunk 47616) [x86_64-darwin13] Calculating ------------------------------------- Array#map.flatten(1) 3796 i/100ms Array#map.flatten 3829 i/100ms Array#flat_map 5987 i/100ms ------------------------------------------------- Array#map.flatten(1) 38832.0 (±3.3%) i/s - 197392 in 5.088969s Array#map.flatten 38759.1 (±3.6%) i/s - 195279 in 5.045226s Array#flat_map 63079.3 (±3.9%) i/s - 317311 in 5.038472s Comparison: Array#flat_map: 63079.3 i/s Array#map.flatten(1): 38832.0 i/s - 1.62x slower Array#map.flatten: 38759.1 i/s - 1.63x slower
Enumerable#reverse.each
vs Enumerable#reverse_each
code
Enumerable#reverse
allocates an extra array.
Enumerable#reverse_each
yields each value without allocating an extra array.
This is the reason whyEnumerable#reverse_each
exists.
-- @sferik rails/rails#17244
$ ruby -v code/enumerable/reverse-each-vs-reverse_each.rb ruby 2.2.0preview1 (2014-09-17 trunk 47616) [x86_64-darwin13] Calculating ------------------------------------- Array#reverse.each 18475 i/100ms Array#reverse_each 20324 i/100ms ------------------------------------------------- Array#reverse.each 205046.1 (±4.0%) i/s - 1034600 in 5.054304s Array#reverse_each 231763.2 (±2.3%) i/s - 1178792 in 5.088863s Comparison: Array#reverse_each: 231763.2 i/s Array#reverse.each: 205046.1 i/s - 1.13x slower
Enumerable#each + push
vs Enumerable#map
code
$ ruby -v code/enumerable/each-push-vs-map.rb ruby 2.2.0preview1 (2014-09-17 trunk 47616) [x86_64-darwin13] Calculating ------------------------------------- Array#each + push 10308 i/100ms Array#map 15125 i/100ms ------------------------------------------------- Array#each + push 106052.2 (±4.2%) i/s - 536016 in 5.063496s Array#map 166484.6 (±4.9%) i/s - 847000 in 5.100707s Comparison: Array#map: 166484.6 i/s Array#each + push: 106052.2 i/s - 1.57x slower
Enumerable#each_with_index
vs while
loop code
$ ruby -v code/array/each_with_index-vs-while-loop.rb ruby 2.2.0preview1 (2014-09-17 trunk 47616) [x86_64-darwin13] Calculating ------------------------------------- each_with_index 13880 i/100ms While Loop 23616 i/100ms ------------------------------------------------- each_with_index 146266.8 (±2.7%) i/s - 735640 in 5.033031s While Loop 274998.8 (±3.0%) i/s - 1393344 in 5.071824s Comparison: While Loop: 274998.8 i/s each_with_index: 146266.8 i/s - 1.88x slower
Enumerable#sort
vs Enumerable#sort_by
code
$ ruby -v code/enumerable/sort-vs-sort_by.rb ruby 2.2.0preview1 (2014-09-17 trunk 47616) [x86_64-darwin13] Calculating ------------------------------------- Enumerable#sort 1266 i/100ms Enumerable#sort_by 2688 i/100ms ------------------------------------------------- Enumerable#sort 12892.0 (±2.8%) i/s - 64566 in 5.012285s Enumerable#sort_by 27421.4 (±3.3%) i/s - 137088 in 5.004812s Comparison: Enumerable#sort_by: 27421.4 i/s Enumerable#sort: 12892.0 i/s - 2.13x slower
Enumerable#detect
vs Enumerable#select.first
code
$ ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin14] Calculating ------------------------------------- Enumerable#select.first 9.265k i/100ms Enumerable#detect 37.176k i/100ms ------------------------------------------------- Enumerable#select.first 94.906k (± 6.4%) i/s - 1.890M Enumerable#detect 461.291k (± 4.9%) i/s - 9.220M Comparison: Enumerable#detect: 461291.4 i/s Enumerable#select.first: 94906.2 i/s - 4.86x slower
Hash#merge
vs Hash#merge!
code
$ ruby -v code/hash/merge-vs-merge-bang.rb ruby 2.2.0preview1 (2014-09-17 trunk 47616) [x86_64-darwin13] Calculating ------------------------------------- Hash#merge 51 i/100ms Hash#merge! 1141 i/100ms ------------------------------------------------- Hash#merge 496.7 (±7.2%) i/s - 2499 in 5.054120s Hash#merge! 11656.2 (±3.5%) i/s - 59332 in 5.096368s Comparison: Hash#merge!: 11656.2 i/s Hash#merge: 496.7 i/s - 23.47x slower
Hash#merge!
vs Hash#[]=
code
$ ruby -v code/hash/merge-bang-vs-\[\]=.rb ruby 2.2.0preview1 (2014-09-17 trunk 47616) [x86_64-darwin13] Calculating ------------------------------------- Hash#merge! 1276 i/100ms Hash#[]= 3303 i/100ms ------------------------------------------------- Hash#merge! 12913.5 (±3.6%) i/s - 65076 in 5.046038s Hash#[]= 34812.0 (±11.2%) i/s - 175059 in 5.079305s Comparison: Hash#[]=: 34812.0 i/s Hash#merge!: 12913.5 i/s - 2.70x slower
Hash#fetch
with argument vs Hash#fetch
+ block code
$ ruby -v code/hash/fetch-vs-fetch-with-block.rb ruby 2.2.0preview1 (2014-09-17 trunk 47616) [x86_64-darwin13] Calculating ------------------------------------- Hash#fetch + arg 19954 i/100ms Hash#fetch + block 146532 i/100ms ------------------------------------------------- Hash#fetch + arg 230612.5 (±3.8%) i/s - 1157332 in 5.026216s Hash#fetch + block 6496408.6 (±3.5%) i/s - 32530104 in 5.014893s Comparison: Hash#fetch + block: 6496408.6 i/s Hash#fetch + arg: 230612.5 i/s - 28.17x slower
Hash#each_key
instead of Hash#keys.each
code
Hash#keys.each
allocates an array of keys;
Hash#each_key
iterates through the keys without allocating a new array.
This is the reason whyHash#each_key
exists.
—— @sferik rails/rails#17099
$ ruby -v code/hash/keys-each-vs-each_key.rb ruby 2.2.0preview1 (2014-09-17 trunk 47616) [x86_64-darwin13] Calculating ------------------------------------- Hash#keys.each 66419 i/100ms Hash#each_key 73281 i/100ms ------------------------------------------------- Hash#keys.each 1031745.2 (±3.9%) i/s - 5180682 in 5.029275s Hash#each_key 1215805.3 (±3.5%) i/s - 6082323 in 5.009353s Comparison: Hash#each_key: 1215805.3 i/s Hash#keys.each: 1031745.2 i/s - 1.18x slower
Proc#call
vs yield
code
$ ruby -v code/proc-and-block/proc-call-vs-yield.rb ruby 2.2.0preview1 (2014-09-17 trunk 47616) [x86_64-darwin13] Calculating ------------------------------------- block.call 84982 i/100ms yield 149488 i/100ms ------------------------------------------------- block.call 1584410.0 (±4.3%) i/s - 7988308 in 5.051477s yield 6544207.5 (±5.5%) i/s - 32737872 in 5.019144s Comparison: yield: 6544207.5 i/s block.call: 1584410.0 i/s - 4.13x slower
Block vs Symbol#to_proc
code
Symbol#to_proc
is considerably more concise than using block syntax.
...In some cases, it reduces the number of lines of code.
—— @sferik rails/rails#16833
$ ruby -v code/proc-and-block/block-vs-to_proc.rb ruby 2.2.0preview1 (2014-09-17 trunk 47616) [x86_64-darwin13] Calculating ------------------------------------- Block 5609 i/100ms Symbol#to_proc 6599 i/100ms ------------------------------------------------- Block 55716.2 (±6.8%) i/s - 280450 in 5.059515s Symbol#to_proc 67124.3 (±6.6%) i/s - 336549 in 5.038400s Comparison: Symbol#to_proc: 67124.3 i/s Block: 55716.2 i/s - 1.20x slower
Please! Edit this README.md then Submit a Awesome Pull Request!
Code example is wrong? 😢 Got better example? 😍 Excellent!
Please open an issue or Open a Pull Request to fix it.
Thank you in advance! 😉 🍺
Share this with your #Rubyfriends! <3
Brought to you by @JuanitoFatas
Feel free to talk with me on Twitter! <3
💝 💞 💝 💖 💙 💕 ❤️ 💗 💚 💞 💓 💛 💗 💓 💟 💙
This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.