{"id":19110240,"url":"https://github.com/lucianghinda/value-object-in-ruby-benchmarks","last_synced_at":"2025-09-17T16:06:47.886Z","repository":{"id":229139789,"uuid":"775863841","full_name":"lucianghinda/value-object-in-ruby-benchmarks","owner":"lucianghinda","description":"A series of micro benchmarks about Data.define vs Struct vs OpenStruct in #Ruby","archived":false,"fork":false,"pushed_at":"2024-03-29T04:23:55.000Z","size":24,"stargazers_count":4,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-19T08:33:29.375Z","etag":null,"topics":["benchmark","benchmarking","ruby","ruby3"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lucianghinda.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-03-22T07:39:23.000Z","updated_at":"2025-01-28T20:38:52.000Z","dependencies_parsed_at":"2024-03-29T05:28:11.732Z","dependency_job_id":"61af6b2b-fb5f-4b06-8669-a65fcee1cbdf","html_url":"https://github.com/lucianghinda/value-object-in-ruby-benchmarks","commit_stats":null,"previous_names":["lucianghinda/value-object-in-ruby-benchmarks"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lucianghinda%2Fvalue-object-in-ruby-benchmarks","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lucianghinda%2Fvalue-object-in-ruby-benchmarks/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lucianghinda%2Fvalue-object-in-ruby-benchmarks/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lucianghinda%2Fvalue-object-in-ruby-benchmarks/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lucianghinda","download_url":"https://codeload.github.com/lucianghinda/value-object-in-ruby-benchmarks/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251779083,"owners_count":21642483,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["benchmark","benchmarking","ruby","ruby3"],"created_at":"2024-11-09T04:24:15.050Z","updated_at":"2025-09-17T16:06:42.850Z","avatar_url":"https://github.com/lucianghinda.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Micro Benchmarks on Value Object options in Ruby\n\nThis is executed with defaults, no extra settings added.\n\n## How to run the benchmarks\n\n```bash\nbundle install\nbundle exec ruby \u003cbenchmark\u003e\n```\n\n## Machine use to run the benchmarks\n\nI run the following benchmarks on my machine:\n\n- Apple M3 PRO\n- 36 GB\n- Running Mac OS 14.4 (23E214)\n- Ruby 3.3.0\n\n## Comparing Data.define with Struct and OpenStruct\n\nComparing Data.define with Struct and OpenStruct.\n\nThe benchmark is focused on benchmarking the keyword arguments.\n\n### Creating Values\n\nHaving defines the following keys and values:\n\n```ruby\nkeys = 1000.times.map { |i| \"key#{i}\".to_sym }\nvalues = 1000.times.map { |i| \"value#{i}\" }\nkeys_and_values = Hash[keys.zip(values)]\n```\n\nThe creation benchmarks are testing the following code:\n\n```ruby\nDataStruct = Struct.new(*keys, keyword_init: true)\nDataStruct.new(**keys_and_values)\n\n# vs\n\nDataDefine = Data.define(*keys)\nDataDefine.new(**keys_and_values)\n\n# vs\n\nOpenStruct.new(**keys_and_values)\n```\n\n#### Benchmark with `bmbm`\n\nThis benchmark is run with Ruby default benchmark using `bmbm`\n\n```bash\nCreating a new object - Benchmark with bmbm\nRehearsal --------------------------------------------------\nStruct.new       0.000023   0.000003   0.000026 (  0.000024)\nData.define      0.000020   0.000001   0.000021 (  0.000022)\nOpenStruct.new   0.001705   0.000075   0.001780 (  0.001780)\n----------------------------------------- total: 0.001827sec\n\n                     user     system      total        real\nStruct.new       0.000020   0.000000   0.000020 (  0.000020)\nData.define      0.000022   0.000000   0.000022 (  0.000022)\nOpenStruct.new   0.001069   0.000044   0.001113 (  0.001132)\n```\n\n#### Benchmark with `ibs`\n\nThis benchmark is run with `benchmark-ips` gem.\n\n```bash\nCreating a new object - Benchmark with ips\nruby 3.3.0 (2023-12-25 revision 5124f9ac75) [arm64-darwin23]\nWarming up --------------------------------------\n          Struct.new     5.169k i/100ms\n         Data.define     5.361k i/100ms\n      OpenStruct.new    62.000 i/100ms\nCalculating -------------------------------------\n          Struct.new     50.086k (± 1.7%) i/s -    253.281k in   5.058450s\n         Data.define     51.646k (± 1.1%) i/s -    262.689k in   5.086990s\n      OpenStruct.new    607.447 (± 0.8%) i/s -      3.038k in   5.001584s\n\nComparison:\n         Data.define:    51646.3 i/s\n          Struct.new:    50085.7 i/s - 1.03x  slower\n      OpenStruct.new:      607.4 i/s - 85.02x  slower\n```\n\n#### Benchmark with `memory`\n\nThis benchmark is run with `benchmark-memory` gem\n\n```bash\nCreating a new object - Benchmark with ips\nCalculating -------------------------------------\n          Struct.new    36.792k memsize (     0.000  retained)\n                         2.000  objects (     0.000  retained)\n                         0.000  strings (     0.000  retained)\n         Data.define    36.792k memsize (     0.000  retained)\n                         2.000  objects (     0.000  retained)\n                         0.000  strings (     0.000  retained)\n      OpenStruct.new   848.728k memsize (     0.000  retained)\n                         8.005k objects (     0.000  retained)\n                        50.000  strings (     0.000  retained)\n\nComparison:\n          Struct.new:      36792 allocated\n         Data.define:      36792 allocated - same\n      OpenStruct.new:     848728 allocated - 23.07x more\n```\n\n### Accessing Attributes\n\nHaving the following data defined:\n\n```ruby\nkeys = 1000.times.map { |i| \"key#{i}\".to_sym }\nvalues = 1000.times.map { |i| \"value#{i}\" }\nkeys_and_values = Hash[keys.zip(values)]\n```\n\nAnd then defining the following structures:\n\n```ruby\nBigDataS = Struct.new(*keys, keyword_init: true)\nBigDataD = Data.define(*keys)\n```\n\nThe benchmarks are comparing:\n\n```ruby\nkeys.each { struct_object.send(_1) }\n\nkeys.each { data_object.send(_1) }\n\nkeys.each { opens_struct_object.send(_1) }\n```\n\n#### Benchmark with `bmbm`\n\nThis benchmark is run with Ruby default benchmark using `bmbm`\n\n```bash\nAccessing attributes - bmbm test\nRehearsal -----------------------------------------------\nStruct        0.000069   0.000002   0.000071 (  0.000071)\nData.define   0.000069   0.000003   0.000072 (  0.000071)\nOpenStruct    0.000110   0.000003   0.000113 (  0.000116)\n-------------------------------------- total: 0.000256sec\n\n                  user     system      total        real\nStruct        0.000049   0.000001   0.000050 (  0.000046)\nData.define   0.000046   0.000001   0.000047 (  0.000046)\nOpenStruct    0.000091   0.000001   0.000092 (  0.000094)\n```\n\n#### Benchmark with `ibs`\n\nThis benchmark is run with `benchmark-ips` gem.\n\n```bash\nAccessing attributes - ips test\nruby 3.3.0 (2023-12-25 revision 5124f9ac75) [arm64-darwin23]\nWarming up --------------------------------------\n              Struct     2.857k i/100ms\n         Data.define     2.828k i/100ms\n          OpenStruct     1.384k i/100ms\nCalculating -------------------------------------\n              Struct     28.420k (± 0.9%) i/s -    142.850k in   5.026906s\n         Data.define     28.691k (± 0.5%) i/s -    144.228k in   5.027131s\n          OpenStruct     13.475k (± 0.9%) i/s -     67.816k in   5.033315s\n\nComparison:\n         Data.define:    28690.8 i/s\n              Struct:    28419.6 i/s - same-ish: difference falls within error\n          OpenStruct:    13474.6 i/s - 2.13x  slower\n```\n\n## Data.define - benchmarks\n\n### Comparing multiple ways to create a new object\n\nHaving the following data specified:\n\n```ruby\nkeys = 1000.times.map { |i| \"key#{i}\".to_sym }\nvalues = 1000.times.map { |i| \"value#{i}\" }\nkeys_and_values = Hash[keys.zip(values)]\n\nDataDefine = Data.define(*keys)\n```\n\nThe benchmarks are comparing the following code:\n\n```ruby\n# Keyword arguments\nDataDefine.new(**keys_and_values)\n\n# Positional arguments\nDataDefine.new(*values)\n\n# Constructor method\nDataDefine[*values]\n\n# Constructor keywords\nDataDefine[**keys_and_values]\n```\n\n#### Benchmark with `bmbm`\n\nThis benchmark is run with Ruby default benchmark using `bmbm`\n\n```bash\nComparing ways to instantiate a Data.define object - Benchmark with bmbm\nRehearsal --------------------------------------------------------\nKeyword arguments      0.000025   0.000000   0.000025 (  0.000025)\nPositional arguments   0.000058   0.000000   0.000058 (  0.000059)\nConstructor method     0.000053   0.000000   0.000053 (  0.000054)\nConstructor keywords   0.000029   0.000000   0.000029 (  0.000029)\n----------------------------------------------- total: 0.000165sec\n\n                           user     system      total        real\nKeyword arguments      0.000023   0.000001   0.000024 (  0.000023)\nPositional arguments   0.000039   0.000000   0.000039 (  0.000039)\nConstructor method     0.000046   0.000000   0.000046 (  0.000046)\nConstructor keywords   0.000023   0.000000   0.000023 (  0.000023)\n```\n\n#### Benchmarking with `ips`\n\nThis benchmark is run with `benchmark-ips` gem.\n\n```bash\nComparing ways to instantiate a Data.define object - Benchmark with ips\nruby 3.3.0 (2023-12-25 revision 5124f9ac75) [arm64-darwin23]\nWarming up --------------------------------------\n   Keyword arguments     5.345k i/100ms\nPositional arguments     2.692k i/100ms\n  Constructor method     2.663k i/100ms\nConstructor keywords     5.354k i/100ms\nCalculating -------------------------------------\n   Keyword arguments     52.390k (± 1.9%) i/s -    261.905k in   5.000971s\nPositional arguments     26.162k (± 1.1%) i/s -    131.908k in   5.042607s\n  Constructor method     26.149k (± 0.8%) i/s -    133.150k in   5.092403s\nConstructor keywords     51.813k (± 1.6%) i/s -    262.346k in   5.064698s\n\nComparison:\n   Keyword arguments:    52390.1 i/s\nConstructor keywords:    51812.7 i/s - same-ish: difference falls within error\nPositional arguments:    26162.3 i/s - 2.00x  slower\n  Constructor method:    26148.6 i/s - 2.00x  slower\n```\n\n### Benchmarking with `memory`\n\nThis benchmark is run with `benchmark-memory` gem.\n\nThis test is probably unnecessary cause in the end it creates the same thing.\n\n```bash\nComparing ways to instantiate a Data.define object - Benchmark with memory\nCalculating -------------------------------------\n   Keyword arguments    36.792k memsize (     0.000  retained)\n                         2.000  objects (     0.000  retained)\n                         0.000  strings (     0.000  retained)\nPositional arguments    36.792k memsize (     0.000  retained)\n                         2.000  objects (     0.000  retained)\n                         0.000  strings (     0.000  retained)\n  Constructor method    36.792k memsize (     0.000  retained)\n                         2.000  objects (     0.000  retained)\n                         0.000  strings (     0.000  retained)\nConstructor keywords    36.792k memsize (     0.000  retained)\n                         2.000  objects (     0.000  retained)\n                         0.000  strings (     0.000  retained)\n\nComparison:\n   Keyword arguments:      36792 allocated\nPositional arguments:      36792 allocated - same\n  Constructor method:      36792 allocated - same\nConstructor keywords:      36792 allocated - same\n```\n\n### Comparing accessing data from objects created differently\n\nTaking in consideration the following data :\n\n```ruby\nkeys = 1000.times.map { |i| \"key#{i}\".to_sym }\nvalues = 1000.times.map { |i| \"value#{i}\" }\nkeys_and_values = Hash[keys.zip(values)]\n```\n\nAnd creating the following objects:\n\n```ruby\nDataDefine = Data.define(*keys)\nkeyword_args = DataDefine.new(**keys_and_values)\npositional_args = DataDefine.new(*values)\nconstructor_method = DataDefine[*values]\nconstructor_with_keyword_args = DataDefine[**keys_and_values]\n```\n\nThe benchmarks are comparing the following code:\n\n```ruby\n#Keyword arguments\nkeys.each { keyword_args.send(_1) }\n\n#Positional arguments\nkeys.each { positional_args.send(_1) }\n\n#Constructor method\nkeys.each { constructor_method.send(_1) }\n\n#Constructor keywords\nkeys.each { constructor_with_keyword_args.send(_1) }\n```\n\n#### Benchmark with `bmbm`\n\nThis benchmark is run with Ruby default benchmark using `bmbm`\n\n```bash\nComparing accessing data for Data.define object - Benchmark with bmbm\nRehearsal --------------------------------------------------------\nKeyword arguments      0.000072   0.000002   0.000074 (  0.000074)\nPositional arguments   0.000046   0.000001   0.000047 (  0.000047)\nConstructor method     0.000047   0.000000   0.000047 (  0.000047)\nConstructor keywords   0.000047   0.000001   0.000048 (  0.000048)\n----------------------------------------------- total: 0.000216sec\n\n                           user     system      total        real\nKeyword arguments      0.000048   0.000001   0.000049 (  0.000048)\nPositional arguments   0.000047   0.000000   0.000047 (  0.000047)\nConstructor method     0.000047   0.000000   0.000047 (  0.000048)\nConstructor keywords   0.000047   0.000001   0.000048 (  0.000047)\n```\n\n#### Benchmark with `ips`\n\nThis benchmark is run with `benchmark-ips` gem.\n\n```bash\nComparing accessing data for Data.define object - Benchmark with bmbm\nruby 3.3.0 (2023-12-25 revision 5124f9ac75) [arm64-darwin23]\nWarming up --------------------------------------\n   Keyword arguments     2.669k i/100ms\nPositional arguments     2.657k i/100ms\n  Constructor method     2.685k i/100ms\nConstructor keywords     2.690k i/100ms\nCalculating -------------------------------------\n   Keyword arguments     26.925k (± 0.5%) i/s -    136.119k in   5.055637s\nPositional arguments     26.835k (± 0.4%) i/s -    135.507k in   5.049741s\n  Constructor method     26.895k (± 0.3%) i/s -    136.935k in   5.091470s\nConstructor keywords     26.794k (± 0.4%) i/s -    134.500k in   5.019767s\n\nComparison:\n   Keyword arguments:    26924.8 i/s\n  Constructor method:    26895.3 i/s - same-ish: difference falls within error\nPositional arguments:    26834.9 i/s - same-ish: difference falls within error\nConstructor keywords:    26794.4 i/s - same-ish: difference falls within error\n```\n\n## Comparing creating new objects with small number of attributes\n\nIn these tests I run creating new objects with 6 attributes.\n\nI compared `Data.define` with `Struct`, `OpenStruct`, plain Ruby object with positional arguments and plain Ruby object with keyword arguments.\n\nThe tests are done only with `ips` and `bmbm`.\nDid not do a memory test becuase I did not wanted to try to replicate in the custom class the same logic that `Data.define` or `Struct` can offer.\n\nHaving defined the following data:\n\n```ruby\nkeys = [:key1, :key2, :key3, :key4, :key5, :key6]\nvalues = 6.times.map { |i| \"value#{i}\" }\nkeys_and_values = Hash[keys.zip(values)]\n```\n\nAnd the following classes:\n\n```ruby\nDataStructKeyword = Struct.new(*keys, keyword_init: true)\nDataStructPositional = Struct.new(*keys)\nDataDefine = Data.define(*keys)\n\nclass MyValueObjectWithKeywordArgs\n  attr_reader :key1, :key2, :key3, :key4, :key5, :key6\n\n  def initialize(key1:, key2:, key3:, key4:, key5:, key6:)\n    @key1 = key1\n    @key2 = key2\n    @key3 = key3\n    @key4 = key4\n    @key5 = key5\n    @key6 = key6\n  end\nend\n\nclass MyValueObjectWithPositionalArgs\n  attr_reader :key1, :key2, :key3, :key4, :key5, :key6\n\n  def initialize(key1, key2, key3, key4, key5, key6)\n    @key1 = key1\n    @key2 = key2\n    @key3 = key3\n    @key4 = key4\n    @key5 = key5\n    @key6 = key6\n  end\nend\n```\n\nThe benchmarks will compare the following code:\n\n```ruby\n# Struct - positional\nDataStructPositional.new(*values)\n\n# Struct - keywords\nDataStructKeyword.new(**keys_and_values)\n\n# Data - positional\nDataDefine.new(*values)\n\n# Data - keywords\nDataDefine.new(**keys_and_values)\n\n# OpenStruct.new\nOpenStruct.new(**keys_and_values)\n\n# PORO - positional\nMyValueObjectWithPositionalArgs.new(*values)\n\n# PORO - keywords\nMyValueObjectWithKeywordArgs.new(**keys_and_values)\n```\n\n### Benchamrk with `bmbm`\n\n```bash\nCreating a new object - Benchmark with bmbm - small numbers\nRehearsal -------------------------------------------------------\nStruct - positional   0.000003   0.000001   0.000004 (  0.000002)\nStruct - keywords     0.000002   0.000000   0.000002 (  0.000002)\nData - positional     0.000002   0.000001   0.000003 (  0.000004)\nData - keywords       0.000002   0.000000   0.000002 (  0.000001)\nOpenStruct.new        0.000019   0.000001   0.000020 (  0.000019)\nPORO - positional     0.000002   0.000000   0.000002 (  0.000002)\nPORO - keywords       0.000001   0.000001   0.000002 (  0.000002)\n---------------------------------------------- total: 0.000035sec\n\n                          user     system      total        real\nStruct - positional   0.000001   0.000000   0.000001 (  0.000000)\nStruct - keywords     0.000002   0.000000   0.000002 (  0.000001)\nData - positional     0.000002   0.000000   0.000002 (  0.000002)\nData - keywords       0.000001   0.000000   0.000001 (  0.000001)\nOpenStruct.new        0.000018   0.000000   0.000018 (  0.000017)\nPORO - positional     0.000001   0.000000   0.000001 (  0.000001)\nPORO - keywords       0.000002   0.000000   0.000002 (  0.000001)\n```\n\n### Benchmark with `ips`\n\n```bash\nCreating a new object - Benchmark with ips - small numbers\nruby 3.3.0 (2023-12-25 revision 5124f9ac75) [arm64-darwin23]\nWarming up --------------------------------------\n Struct - positional   913.180k i/100ms\n   Struct - keywords   464.416k i/100ms\n   Data - positional   335.700k i/100ms\n     Data - keywords   484.506k i/100ms\n      OpenStruct.new    10.869k i/100ms\n   PORO - positional   796.702k i/100ms\n     PORO - keywords   467.403k i/100ms\nCalculating -------------------------------------\n Struct - positional      8.924M (± 0.1%) i/s -     44.746M in   5.014174s\n   Struct - keywords      4.583M (± 0.2%) i/s -     23.221M in   5.067154s\n   Data - positional      3.289M (± 0.2%) i/s -     16.449M in   5.001151s\n     Data - keywords      4.786M (± 0.1%) i/s -     24.225M in   5.061479s\n      OpenStruct.new    109.959k (± 1.1%) i/s -    554.319k in   5.041756s\n   PORO - positional      7.791M (± 0.3%) i/s -     39.038M in   5.010537s\n     PORO - keywords      4.659M (± 0.5%) i/s -     23.370M in   5.016246s\n\nComparison:\n Struct - positional:  8923882.2 i/s\n   PORO - positional:  7791346.2 i/s - 1.15x  slower\n     Data - keywords:  4786215.7 i/s - 1.86x  slower\n     PORO - keywords:  4659024.3 i/s - 1.92x  slower\n   Struct - keywords:  4582626.3 i/s - 1.95x  slower\n   Data - positional:  3289111.1 i/s - 2.71x  slower\n      OpenStruct.new:   109959.0 i/s - 81.16x  slower\n```\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flucianghinda%2Fvalue-object-in-ruby-benchmarks","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flucianghinda%2Fvalue-object-in-ruby-benchmarks","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flucianghinda%2Fvalue-object-in-ruby-benchmarks/lists"}