Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Writing Fast Ruby

Writing Fast Ruby

Originally presented at Baruco 2014. Updated for RubyConf Portugal 2014.

Video here: https://www.youtube.com/watch?v=fGFM_UrSp70.

Erik Berlin

October 13, 2014
Tweet

More Decks by Erik Berlin

Other Decks in Programming

Transcript

  1. View Slide

  2. View Slide

  3. View Slide

  4. View Slide

  5. Levels of Optimization
    Design
    Source
    Build
    Compile
    Runtime

    View Slide

  6. Levels of Optimization
    Design
    Source
    Build
    Compile
    Runtime
    Architecture and algorithms (e.g. n + 1 queries)
    Writing fast Ruby
    Setting build flags (e.g. ./configure)
    mrbc, jrubyc, rbx compile
    Thanks Matz & Koichi (e.g. RUBY_GC_MALLOC_LIMIT)

    View Slide

  7. View Slide

  8. Benchmark
    require ‘benchmark'
    n = 50
    Benchmark.bm do |x|
    x.report { n.times { fast } }
    x.report { n.times { slow } }
    end

    View Slide

  9. Benchmark
    require ‘benchmark'
    n = 50_000
    Benchmark.bm do |x|
    x.report { n.times { fast } }
    x.report { n.times { slow } }
    end

    View Slide

  10. Benchmark IPS
    require 'benchmark/ips'
    Benchmark.ips do |x|
    x.report('fast') { fast }
    x.report('slow') { slow }
    end

    View Slide

  11. Goals
    Source
    Significant
    Easy
    Happy
    Optimize at the code level
    At least 12% improvement
    Code should be easier to read
    High quality Ruby

    View Slide

  12. Proc#call versus yield
    def slow(&block)
    block.call
    end
    def fast
    yield
    end

    View Slide

  13. Results
    slow 950950.6 (±14.0%) i/s
    fast 5508226.3 (±15.5%) i/s
    Over 5X faster!

    View Slide

  14. Proc#call versus yield
    def slow
    Proc.new.call
    end
    def fast
    yield
    end

    View Slide

  15. Block versus Symbol#to_proc
    (1..100).map { |i| i.to_s }
    (1..100).map(&:to_s)

    View Slide

  16. Results
    slow 47524.3 (±7.6%) i/s
    fast 56823.2 (±7.2%) i/s
    20% faster!

    View Slide

  17. View Slide

  18. View Slide

  19. Enumerable#map and Array#flatten
    versus Enumerable#flat_map
    enum.map do
    # do something
    end.flatten(1)
    enum.flat_map do
    # do something
    end

    View Slide

  20. Results
    slow 12348.2 (±9.0%) i/s
    fast 56647.8 (±7.2%) i/s
    Over 4.5X faster!

    View Slide

  21. View Slide

  22. View Slide

  23. Enumerable#reverse and Enumerable#each

    versus Enumerable#reverse_each
    enum.reverse.each do
    # do something
    end
    enum.reverse_each do
    # do something
    end

    View Slide

  24. Results
    slow 156173.2 (±9.2%) i/s
    fast 182859.3 (±7.8%) i/s
    17% faster!

    View Slide

  25. View Slide

  26. Hash#keys and Enumerable#each

    versus Hash#each_key
    hash.keys.each do |k|
    # do something
    end
    hash.each_key do |k|
    # do something
    end

    View Slide

  27. Results
    slow 34702.1 (±9.8%) i/s
    fast 46103.3 (±8.1%) i/s
    Over 33% faster!

    View Slide

  28. View Slide

  29. View Slide

  30. Array#shuffle and Array#first

    versus Array#sample
    array.shuffle.first array.sample

    View Slide

  31. Results
    slow 324806.7 (±8.1%) i/s
    fast 5069719.9 (±9.5%) i/s
    Over 15X faster!

    View Slide

  32. View Slide

  33. View Slide

  34. Hash#merge versus Hash#merge!
    enum.inject({}) do |h, e|
    h.merge(e => e)
    end
    enum.inject({}) do |h, e|
    h.merge!(e => e)
    end

    View Slide

  35. Results
    slow 33572.0 (±3.3%) i/s
    fast 106473.0 (±3.4%) i/s
    Over 3X faster!

    View Slide

  36. Hash#merge! versus Hash#[]=
    enum.each_with_object({}) do |e, h|
    h.merge!(e => e)
    end
    enum.each_with_object({}) do |e, h|
    h[e] = e
    end

    View Slide

  37. Results
    slow 99331.3 (±2.2%) i/s
    fast 217944.4 (±5.2%) i/s
    Over 2X faster!

    View Slide

  38. Hash#fetch versus Hash#fetch with block
    {:ruby => :conf}.fetch(:ruby, (0..9).to_a)
    {:ruby => :conf}.fetch(:ruby) { (0..9).to_a }

    View Slide

  39. Results
    slow 712384.2 (±3.8%) i/s
    fast 1590417.4 (±4.1%) i/s
    Over 2X faster!

    View Slide

  40. String#gsub versus String#sub
    ‘http://rubyconf.pt/'.gsub(‘http://', 'https://')
    'http://rubyconf.pt/'.sub('http://', 'https://')

    View Slide

  41. Results
    slow 404148.2 (±4.6%) i/s
    fast 602661.1 (±3.4%) i/s
    50% faster!

    View Slide

  42. String#gsub versus String#tr
    'slug from title'.gsub(' ', '_')
    'slug from title'.tr(' ', '_')

    View Slide

  43. Results
    slow 311878.2 (±3.5%) i/s
    fast 1573891.1 (±4.6%) i/s
    Over 5X faster!

    View Slide

  44. Parallel versus sequential assignment
    a, b = 1, 2 a = 1
    b = 2

    View Slide

  45. Results
    slow 5821588.3 (±6.0%) i/s
    fast 8010420.3 (±5.5%) i/s
    40% faster!

    View Slide

  46. Using exceptions for control flow
    begin
    ruby.conf
    rescue NoMethodError
    'conf'
    end
    if ruby.respond_to?(:conf)
    ruby.conf
    else
    'conf'
    end

    View Slide

  47. Results
    slow 328886.4 (±5.0%) i/s
    fast 3348327.9 (±9.0%) i/s
    Over 10X faster!

    View Slide

  48. Using throw/catch for control flow
    begin
    ruby.conf
    rescue NoMethodError
    'conf'
    end
    catch(:ruby) do
    if ruby.respond_to?(:conf)
    ruby.conf
    else
    throw(:ruby, ‘conf’)
    end
    end

    View Slide

  49. Results
    slow 252474.2 (±8.4%) i/s
    fast 1411916.6 (±7.2%) i/s
    Over 5X faster!

    View Slide

  50. The Future

    View Slide

  51. View Slide

  52. View Slide

  53. View Slide

  54. View Slide

  55. View Slide

  56. Special Thanks
    Aaron Patterson
    Ruby Rogues Parley
    Sam Saffron
    Aman Gupta
    Don Knuth
    Yukihiro Matsumoto
    Koichi Sasada
    RubyConf Portugal

    View Slide

  57. View Slide

  58. View Slide