Skip to content

Instantly share code, notes, and snippets.

@manuphatak
Last active November 17, 2020 04:18
Show Gist options
  • Select an option

  • Save manuphatak/9ef6ba81743300ca57e93078aecc4222 to your computer and use it in GitHub Desktop.

Select an option

Save manuphatak/9ef6ba81743300ca57e93078aecc4222 to your computer and use it in GitHub Desktop.

Revisions

  1. manuphatak revised this gist Dec 27, 2019. No changes.
  2. manuphatak revised this gist Dec 27, 2019. 1 changed file with 109 additions and 41 deletions.
    150 changes: 109 additions & 41 deletions hash_performance.rb
    Original file line number Diff line number Diff line change
    @@ -48,6 +48,7 @@ def run_gc


    PointStruct = Struct.new(:x, :y)
    PointKeywordStruct = Struct.new(:x, :y, keyword_init: true)

    class FakedPointStruct
    attr_accessor :x, :y
    @@ -57,102 +58,169 @@ def initialize(x, y)
    end
    end

    class PointClass
    class PointClassFetch
    attr_accessor :x, :y
    def initialize(args)
    @x = args.fetch(:x) # NOTE: Hash#fetch -> performance impact
    @y = args.fetch(:y)
    end
    end

    class PointClassKeyword
    attr_accessor :x, :y
    def initialize(x:, y:)
    @x = x
    @y = y
    end
    end

    puts "\n\nINITIALIZATION =========="

    Benchmark.ips do |x|
    x.json! 'INITIALIZATION.json'
    x.config(suite: suite)

    # Create Objects as a reference value
    x.report("Object.new") { Object.new }

    x.report("FakedPointStruct") { FakedPointStruct.new(100, 200) }
    x.report("PointStruct") { PointStruct.new(100, 200) }
    x.report("PointKeywordStruct") { PointKeywordStruct.new(data) }
    x.report("Hash[]") { Hash[data] }
    x.report("PointClass") { PointClass.new(data) }
    x.report("PointClassFetch") { PointClassFetch.new(data) }
    x.report("PointClassKeyword") { PointClassKeyword.new(data) }
    x.report("Hash#merge") { Hash.new.merge(data) }
    x.report("OpenStruct") { OpenStruct.new(data) }

    x.compare!
    end

    puts "\n\nREAD =========="

    point_struct = PointStruct.new(100, 200)
    point_class = PointClass.new(data)
    point_keyword_struct = PointKeywordStruct.new(data)
    point_class_fetch = PointClassFetch.new(data)
    point_class_keyword = PointClassKeyword.new(data)
    point_hash = Hash[data]
    point_open_struct = OpenStruct.new(data)

    Benchmark.ips do |x|
    x.json! 'READ.json'
    x.config(suite: suite)

    x.report("PointStruct") { point_struct.x }
    x.report("PointClass") { point_class.x }
    x.report("PointKeywordStruct") { point_keyword_struct.x }
    x.report("PointClassFetch") { point_class_fetch.x }
    x.report("PointClassKeyword") { point_class_keyword.x }
    x.report("Hash#fetch") { point_hash.fetch(:x) }
    x.report("Hash#[]") { point_hash[:x] }
    x.report("OpenStruct") { point_open_struct.x }

    x.compare!
    end


    puts "\n\nWRITE =========="

    Benchmark.ips do |x|
    x.json! 'WRITE.json'
    x.config(suite: suite)

    x.report("PointStruct") { point_struct.x = 1 }
    x.report("PointClass") { point_class.x = 1 }
    x.report("PointKeywordStruct") { point_keyword_struct.x = 1 }
    x.report("PointClassFetch") { point_class_fetch.x = 1 }
    x.report("PointClassKeyword") { point_class_keyword.x = 1 }
    x.report("Hash") { point_hash[:x] = 1 }
    x.report("OpenStruct") { point_open_struct.x = 1 }

    x.compare!
    end


    # INITIALIZATION ==========
    # Calculating -------------------------------------
    # Object.new 146.142k i/100ms
    # FakedPointStruct 138.237k i/100ms
    # PointStruct 138.366k i/100ms
    # Hash[] 134.258k i/100ms
    # PointClass 117.585k i/100ms
    # Hash#merge 75.923k i/100ms
    # OpenStruct 13.386k i/100ms
    # Object.new 232.512k i/100ms
    # FakedPointStruct 208.718k i/100ms
    # PointStruct 213.914k i/100ms
    # PointKeywordStruct 175.460k i/100ms
    # Hash[] 183.239k i/100ms
    # PointClassFetch 182.943k i/100ms
    # PointClassKeyword 131.129k i/100ms
    # Hash#merge 154.966k i/100ms
    # OpenStruct 117.413k i/100ms
    # -------------------------------------------------
    # Object.new 5.711M (± 4.9%) i/s - 28.498M
    # FakedPointStruct 4.553M (± 0.2%) i/s - 22.809M
    # PointStruct 4.379M (± 6.8%) i/s - 21.862M
    # Hash[] 3.847M (± 6.8%) i/s - 19.199M
    # PointClass 3.169M (± 2.2%) i/s - 15.874M
    # Hash#merge 1.319M (± 2.4%) i/s - 6.605M
    # OpenStruct 157.615k (± 3.4%) i/s - 789.774k
    #
    #
    # Object.new 7.677M (±16.1%) i/s - 37.667M
    # FakedPointStruct 5.919M (±12.3%) i/s - 29.221M
    # PointStruct 5.801M (±11.4%) i/s - 28.664M
    # PointKeywordStruct 4.125M (± 8.9%) i/s - 20.529M
    # Hash[] 4.421M (±17.4%) i/s - 21.622M
    # PointClassFetch 4.810M (± 7.0%) i/s - 23.966M
    # PointClassKeyword 2.308M (±10.0%) i/s - 11.539M
    # Hash#merge 2.963M (±14.7%) i/s - 14.567M
    # OpenStruct 1.975M (± 6.6%) i/s - 9.863M

    # Comparison:
    # Object.new: 7677097.4 i/s
    # FakedPointStruct: 5918849.2 i/s - 1.30x slower
    # PointStruct: 5801076.3 i/s - 1.32x slower
    # PointClassFetch: 4809859.9 i/s - 1.60x slower
    # Hash[]: 4420989.7 i/s - 1.74x slower
    # PointKeywordStruct: 4125176.9 i/s - 1.86x slower
    # Hash#merge: 2962687.0 i/s - 2.59x slower
    # PointClassKeyword: 2307777.5 i/s - 3.33x slower
    # OpenStruct: 1974635.6 i/s - 3.89x slower



    # READ ==========
    # Calculating -------------------------------------
    # PointStruct 136.199k i/100ms
    # PointClass 151.109k i/100ms
    # Hash#fetch 145.969k i/100ms
    # Hash#[] 151.614k i/100ms
    # OpenStruct 127.398k i/100ms
    # PointStruct 275.075k i/100ms
    # PointKeywordStruct 284.487k i/100ms
    # PointClassFetch 284.814k i/100ms
    # PointClassKeyword 274.721k i/100ms
    # Hash#fetch 262.639k i/100ms
    # Hash#[] 282.053k i/100ms
    # OpenStruct 244.214k i/100ms
    # -------------------------------------------------
    # PointStruct 10.961M (± 3.1%) i/s - 45.218M
    # PointClass 11.606M (± 3.5%) i/s - 58.026M
    # Hash#fetch 8.123M (± 5.4%) i/s - 40.433M
    # Hash#[] 10.142M (± 1.6%) i/s - 50.791M
    # OpenStruct 6.657M (± 6.8%) i/s - 33.123M
    #
    #
    # PointStruct 19.828M (± 5.1%) i/s - 99.027M
    # PointKeywordStruct 20.033M (± 5.2%) i/s - 100.139M
    # PointClassFetch 23.465M (± 3.4%) i/s - 117.343M
    # PointClassKeyword 22.488M (± 2.2%) i/s - 112.636M
    # Hash#fetch 16.465M (± 2.0%) i/s - 82.469M
    # Hash#[] 22.144M (± 3.0%) i/s - 110.847M
    # OpenStruct 10.675M (± 3.5%) i/s - 53.483M

    # Comparison:
    # PointClassFetch: 23465137.5 i/s
    # PointClassKeyword: 22488166.1 i/s - 1.04x slower
    # Hash#[]: 22143937.7 i/s - 1.06x slower
    # PointKeywordStruct: 20033487.9 i/s - 1.17x slower
    # PointStruct: 19827584.5 i/s - 1.18x slower
    # Hash#fetch: 16465062.4 i/s - 1.43x slower
    # OpenStruct: 10674642.1 i/s - 2.20x slower



    # WRITE ==========
    # Calculating -------------------------------------
    # PointStruct 162.156k i/100ms
    # PointClass 182.713k i/100ms
    # Hash 171.868k i/100ms
    # OpenStruct 128.931k i/100ms
    # PointStruct 263.792k i/100ms
    # PointKeywordStruct 265.678k i/100ms
    # PointClassFetch 269.824k i/100ms
    # PointClassKeyword 282.345k i/100ms
    # Hash 267.091k i/100ms
    # OpenStruct 224.234k i/100ms
    # -------------------------------------------------
    # PointStruct 9.015M (± 1.0%) i/s - 45.079M
    # PointClass 10.911M (± 3.5%) i/s - 54.448M
    # Hash 8.741M (± 5.7%) i/s - 43.483M
    # OpenStruct 4.542M (± 0.6%) i/s - 22.821M
    # PointStruct 17.523M (± 2.3%) i/s - 87.579M
    # PointKeywordStruct 17.653M (± 3.0%) i/s - 88.205M
    # PointClassFetch 21.269M (± 4.6%) i/s - 106.041M
    # PointClassKeyword 21.139M (± 3.7%) i/s - 105.597M
    # Hash 16.899M (± 4.7%) i/s - 84.401M
    # OpenStruct 7.053M (± 5.8%) i/s - 35.205M

    # Comparison:
    # PointClassFetch: 21268766.9 i/s
    # PointClassKeyword: 21138642.2 i/s - 1.01x slower
    # PointKeywordStruct: 17652879.3 i/s - 1.20x slower
    # PointStruct: 17522748.0 i/s - 1.21x slower
    # Hash: 16899040.6 i/s - 1.26x slower
    # OpenStruct: 7053426.1 i/s - 3.02x slower
  3. manuphatak created this gist Dec 27, 2019.
    158 changes: 158 additions & 0 deletions hash_performance.rb
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,158 @@
    # Response to:
    # http://blog.honeybadger.io/how-openstruct-and-hashes-can-kill-performance/
    #
    # It's not faire to use `Hash.new.merge(data)` if you can `Hash[data]`.
    # `Hash[data]` is way faster! Lets compare!
    #
    # Read more: http://ruby-doc.org/core-2.2.0/Hash.html#method-c-5B-5D
    #
    # [UPDATE]
    #
    # * Disable GC for each report

    require 'benchmark/ips'
    require 'ostruct'


    # Enable and start GC before each job run. Disable GC afterwards.
    #
    # Inspired by https://www.omniref.com/ruby/2.2.1/symbols/Benchmark/bm?#annotation=4095926&line=182
    class GCSuite
    def warming(*)
    run_gc
    end

    def running(*)
    run_gc
    end

    def warmup_stats(*)
    end

    def add_report(*)
    end

    private

    def run_gc
    GC.enable
    GC.start
    GC.disable
    end
    end

    suite = GCSuite.new


    data = { x: 100, y: 200 }


    PointStruct = Struct.new(:x, :y)

    class FakedPointStruct
    attr_accessor :x, :y
    def initialize(x, y)
    @x = x
    @y = y
    end
    end

    class PointClass
    attr_accessor :x, :y
    def initialize(args)
    @x = args.fetch(:x) # NOTE: Hash#fetch -> performance impact
    @y = args.fetch(:y)
    end
    end


    puts "\n\nINITIALIZATION =========="

    Benchmark.ips do |x|
    x.config(suite: suite)

    # Create Objects as a reference value
    x.report("Object.new") { Object.new }

    x.report("FakedPointStruct") { FakedPointStruct.new(100, 200) }
    x.report("PointStruct") { PointStruct.new(100, 200) }
    x.report("Hash[]") { Hash[data] }
    x.report("PointClass") { PointClass.new(data) }
    x.report("Hash#merge") { Hash.new.merge(data) }
    x.report("OpenStruct") { OpenStruct.new(data) }
    end

    puts "\n\nREAD =========="

    point_struct = PointStruct.new(100, 200)
    point_class = PointClass.new(data)
    point_hash = Hash[data]
    point_open_struct = OpenStruct.new(data)

    Benchmark.ips do |x|
    x.config(suite: suite)

    x.report("PointStruct") { point_struct.x }
    x.report("PointClass") { point_class.x }
    x.report("Hash#fetch") { point_hash.fetch(:x) }
    x.report("Hash#[]") { point_hash[:x] }
    x.report("OpenStruct") { point_open_struct.x }
    end


    puts "\n\nWRITE =========="

    Benchmark.ips do |x|
    x.config(suite: suite)

    x.report("PointStruct") { point_struct.x = 1 }
    x.report("PointClass") { point_class.x = 1 }
    x.report("Hash") { point_hash[:x] = 1 }
    x.report("OpenStruct") { point_open_struct.x = 1 }
    end

    # INITIALIZATION ==========
    # Calculating -------------------------------------
    # Object.new 146.142k i/100ms
    # FakedPointStruct 138.237k i/100ms
    # PointStruct 138.366k i/100ms
    # Hash[] 134.258k i/100ms
    # PointClass 117.585k i/100ms
    # Hash#merge 75.923k i/100ms
    # OpenStruct 13.386k i/100ms
    # -------------------------------------------------
    # Object.new 5.711M (± 4.9%) i/s - 28.498M
    # FakedPointStruct 4.553M (± 0.2%) i/s - 22.809M
    # PointStruct 4.379M (± 6.8%) i/s - 21.862M
    # Hash[] 3.847M (± 6.8%) i/s - 19.199M
    # PointClass 3.169M (± 2.2%) i/s - 15.874M
    # Hash#merge 1.319M (± 2.4%) i/s - 6.605M
    # OpenStruct 157.615k (± 3.4%) i/s - 789.774k
    #
    #
    # READ ==========
    # Calculating -------------------------------------
    # PointStruct 136.199k i/100ms
    # PointClass 151.109k i/100ms
    # Hash#fetch 145.969k i/100ms
    # Hash#[] 151.614k i/100ms
    # OpenStruct 127.398k i/100ms
    # -------------------------------------------------
    # PointStruct 10.961M (± 3.1%) i/s - 45.218M
    # PointClass 11.606M (± 3.5%) i/s - 58.026M
    # Hash#fetch 8.123M (± 5.4%) i/s - 40.433M
    # Hash#[] 10.142M (± 1.6%) i/s - 50.791M
    # OpenStruct 6.657M (± 6.8%) i/s - 33.123M
    #
    #
    # WRITE ==========
    # Calculating -------------------------------------
    # PointStruct 162.156k i/100ms
    # PointClass 182.713k i/100ms
    # Hash 171.868k i/100ms
    # OpenStruct 128.931k i/100ms
    # -------------------------------------------------
    # PointStruct 9.015M (± 1.0%) i/s - 45.079M
    # PointClass 10.911M (± 3.5%) i/s - 54.448M
    # Hash 8.741M (± 5.7%) i/s - 43.483M
    # OpenStruct 4.542M (± 0.6%) i/s - 22.821M