Skip to content

nmeylan/fast-ruby

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

34 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Fast Ruby 💨 💨 💨 🚀 Build Status

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). ⚠️ Use with caution!

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

👬 👭 👬 👬 👭 👫 👭 👬 👭 👫 👬 👫 👯 👫 👬 👬 👬 👬 👫 👬 👭 👭 👫 👫 👬 👭 👬 👯 👫

Measurement Tool

Use benchmark-ips.

Template

require 'benchmark/ips' def slow end def fast end Benchmark.ips do |x| x.report('slow') { slow } x.report('fast') { fast } x.compare! end

Idioms

General

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

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

rails/rails#17257

$ 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 with start_with?,
for example: "a\nb" =~ /^b/ #=> 2 but "a\nb" =~ /\Ab/ #=> nil.
⚠️
You can combine start_with? and end_with? to replace error.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

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

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 why Enumerable#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

rails/rails#12065

$ 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

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 why Hash#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 & Block

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 

Submit New Entry

Please! Edit this README.md then Submit a Awesome Pull Request!

Something went wrong

Code example is wrong? 😢 Got better example? 😍 Excellent!

Please open an issue or Open a Pull Request to fix it.

Thank you in advance! 😉 🍺

One more thing

Share this with your #Rubyfriends! <3

Brought to you by @JuanitoFatas

Feel free to talk with me on Twitter! <3

💝 💞 💝 💖 💙 💕 ❤️ 💗 💚 💞 💓 💛 💗 💓 💟 💙 ♥️ 💘 ♥️ 💛 💚 💘 ❤️ ❤️ 💛 💜 💜 💟 💖

License

CC-BY-SA

This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.

About

Writing Fast Ruby —— Collect Common Ruby idioms.

Resources

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Ruby 100.0%