 
                    Introduction
In this article, I’ll be benchmarking Ruby 3.4.2, I’ve had my previous article, Revisiting Performance in Ruby 3.4.1, published and have received various reactions regarding it through a reddit page. I would like to say I’m very thankful for those who have provided their feedback so that I could improve on benchmarking code and presenting my observations.
There are 3 points that have come to importance from all the feedback:
- 
    The articles (the first, which is an Alchemist article and second, which is a medium article) I provided do not support my past observation that “Structs are powerful and could be used to define some of the code in place of the Class” 
- 
    Use benchmark-ips to better benchmark the code I’m benchmarking. 
- 
    My new conclusion that Classes are now faster than Structs holds false when I use benchmark-ips 
I understand these points challenge my observations and I would like to further dive deeper to support my initial findings.
Past observation: Structs are powerful and could be used to define some of the code in place of the Class
I’ve been reading articles and comments that claim Structs could be used instead of other code. Some said in place of Hashes, some said in place of Classes. Structs provide structure, organisation, and readability to your data so it’s better to use instead of Hashes in that regard.
- This Stack Overflow comment said that structs are better than hashes.
- This Stack Overflow comment said that in ruby 3.0.0p0, structs are faster than hashes.
- This Stack Overflow comment points out the real advantage of Structs in 2015 and that is it’s faster.
- This GitHub gist that compares hashes, ostructs, and structs shows the initialisation (write-only) of data. Hashes are faster in here, but there’s still a clear advantage of using Structs.
- This Crystal forum page shows that in 2020, structs are faster than classes. Things like these, practically performance comparisons with classes, are what appealed to me.
- This Honeybadger article’s point is to not use Open Structs. However, it also shows in the benchmarks that Structs were faster compared to the classes.
So, there you go. I’ve added more links to help give a general understanding of what I understood the majority claims in previous years, that Structs are faster than Classes, and it’s great to make use of it as much as possible when your coding situation permits it. The Alchemist article provides a great explanation on when to use it.
Should have checked three times!
In my previous article I’ve claimed that throughout the years, Ruby may have improved Classes to the point that in certain cases they are faster than Structs. When I initially tested it, I was shocked to find it out, and was very excited to share it to the world. I made adjustments to the benchmarks to ensure that I’m definitely seeing this correctly. Then I’ve put the article for the world to see.
One of the first comments in the Reddit thread was a suggestion to use benchmark-ips, and that my code should separate the reads and the writes. I followed his advice on the benchmark-ips but while trying to retain my code (to explain later), and what do you know? Turns out that Struct is still faster than Classes. I’ve been wrong about it! I guessed that I should have probably checked three times before!
Here’s the result when using the benchamrk-ips to my benchmarking code. attr_reader is the Class object.
ruby 3.4.2 (2025-02-15 revision d2930f8e7a) +PRISM [arm64-darwin24]
Calculating -------------------------------------
               array     27.014M (± 1.6%) i/s   (37.02 ns/i) -    136.720M in   5.062568s
            sym_hash     21.751M (± 2.4%) i/s   (45.98 ns/i) -    110.675M in   5.091684s
            str_hash     20.719M (± 4.6%) i/s   (48.27 ns/i) -    105.263M in   5.094066s
         attr_reader      7.954M (± 1.0%) i/s  (125.72 ns/i) -     40.392M in   5.078593s
              struct     10.973M (± 1.7%) i/s   (91.13 ns/i) -     54.974M in   5.011294s
                data      6.813M (± 1.3%) i/s  (146.77 ns/i) -     34.326M in   5.038833s
Comparison:
               array: 27013631.8 i/s
            sym_hash: 21750676.4 i/s - 1.24x  slower
            str_hash: 20718679.0 i/s - 1.30x  slower
              struct: 10973472.4 i/s - 2.46x  slower
         attr_reader:  7954235.5 i/s - 3.40x  slower
                data:  6813492.5 i/s - 3.96x  slower
An unbelievable twist!
There was another comment that came about in the Reddit thread. I’ve already spent days trying to grind at my job. So I forgot to check on it. The commenter said “Am I reading the same articles? The first(Alchemist) articles mentions that OpenStruct is terrible for performance (among other reasons), and it states “Performance has waned recently where structs used to be more performant than classes”
It was odd for me because I definitely understood that the articles I referenced are promoting the use of Structs and support my understanding that the general opinion is to make use of them when you can over classes and hashes. So, I re-read both articles, Medium article, which was a faster read, then the Alchemist article. This took a long time, but I enjoyed re-reading it. I noticed that the writer of the article wrote “Performance has waned recently where structs used to be more performant than classes” in the article, and I was sure that I have never read that before. I took a look at when it’s last updated. Turns out it got updated after I wrote the article, and the Alchemist article got updated the same day as my previous article. February 4, 2025 That makes sense, now I understand why some readers seemed confused in their comments about it.
What strikes me is that the Alchemist article changed its stance to support the claim I made in my previous article! Yes, indeed, my article became thoroughly confusing because of that. However, it’s more interesting that the Alchemist article supports my initial claim!
The article’s benchmark was great because it has 5 attributes instantiated into the objects. It’s closer to real-life use, as we’re silly to simply use these different data structures, yet provide only one attribute.
I’ll copy the code it provided, but I’ll try to add more code into it to provide more scenarios. Let’s see how these things fare in 2025.
Why Benchmark both Read & Write?
When benchmarking these objects A reddit user mentioned that it’s best to test the read and write of the objects in isolation. However, I cannot agree with that as I see in the multitude of codebases I’ve touched, there’s always a write and there can be more than one read when using these objects. I prefer to be close to the real life scenarios.
In my previous article’s benchmarks, I’ve only simulated 1:1 read-write benchmarking. But today, I’ll double down on this perspective and benchmark 1:1, 2:1, 3:1, 5:1, and 10:1 read-write situations. This will give us a better understanding of the real-life scenarios for these objects.
Benchmarking
We’re using the benchmarking code from the Alchemist’s article, and we’re adding a few more things there. Here’s the new code for benchmarking. I’ve also added a “Hash string” test so that we can also determine the difference between symbolized hashes and stringified hashes (with frozen string literal comment). I didn’t use YJIT for this case because there’s already a lot of code and benchmarking results. Try the benchmarking code on your end for YJIT:
Benchmarking Code
#! /usr/bin/env ruby
# frozen_string_literal: true
# Save as benchmark, then chmod 755 benchmark, and run as ./benchmark.
require "bundler/inline"
gemfile true do
  source "https://rubygems.org"
  gem "benchmark-ips"
  gem "debug"
  gem "ostruct"
end
Warning[:performance] = false
require "ostruct"
DataDemo = Data.define :a, :b, :c, :d, :e
StructDemo = Struct.new :a, :b, :c, :d, :e
ClassDemo = Class.new do
  attr_reader :a, :b, :c, :d, :e
  def initialize a:, b:, c:, d:, e:
    @a = a
    @b = b
    @c = c
    @d = d
    @e = e
  end
end
DataDemoTen = Data.define(:a, :b, :c, :d, :e, :f, :g, :h, :i, :j)
StructDemoTen = Struct.new(:a, :b, :c, :d, :e, :f, :g, :h, :i, :j)
ClassDemoTen = Class.new do
  attr_reader :a, :b, :c, :d, :e, :f, :g, :h, :i, :j
  def initialize a:, b:, c:, d:, e:, f:, g:, h:, i:, j:
    @a = a
    @b = b
    @c = c
    @d = d
    @e = e
    @f = f
    @g = g
    @h = h
    @i = i
    @j = j
  end
end
puts "--- 1 Read to 1 Write ---"
Benchmark.ips do |benchmark|
  benchmark.config time: 5, warmup: 2
  benchmark.report("Array") { arr = [1, 2, 3, 4, 5]; arr[0] }
  benchmark.report("Hash") { hash = {a: 1, b: 2, c: 3, d: 4, e: 
  5}; hash[:a] }
  benchmark.report("Hash String") { hash = {'a' => 1, 'b' => 2, 
  'c' => 3, 'd' => 4, 'e' => 5}; hash['a'] }
  benchmark.report("Data") { data = DataDemo[a: 1, b: 2, c: 3, 
  d: 4, e: 5]; data.a }
  benchmark.report("Struct") { struct = StructDemo[a: 1, b: 2, 
  c: 3, d: 4, e: 5]; struct.a }
  benchmark.report("OpenStruct") { ostruct = OpenStruct.new a: 
  1, b: 2, c: 3, d: 4, e: 5; ostruct.a }
  benchmark.report("Class") { klass = ClassDemo.new a: 1, b: 2, 
  c: 3, d: 4, e: 5; klass.a }
  benchmark.compare!
end
puts "--- 2 Reads to 1 Write ---"
Benchmark.ips do |benchmark|
  benchmark.config time: 5, warmup: 2
  benchmark.report("Array") { arr = [1, 2, 3, 4, 5]; arr[0]; 
  arr[1] }
  benchmark.report("Hash") { hash = {a: 1, b: 2, c: 3, d: 4, 
  e: 5}; hash[:a]; hash[:b] }
  benchmark.report("Hash String") { hash = {'a' => 1, 'b' => 
  2, 'c' => 3, 'd' => 4, 'e' => 5}; hash['a']; hash['b'] }
  benchmark.report("Data") { data = DataDemo[a: 1, b: 2, c: 
  3, d: 4, e: 5]; data.a; data.b }
  benchmark.report("Struct") { struct = StructDemo[a: 1, b: 
  2, c: 3, d: 4, e: 5]; struct.a; struct.b }
  benchmark.report("OpenStruct") { ostruct = OpenStruct.new 
  a: 1, b: 2, c: 3, d: 4, e: 5; ostruct.a; ostruct.b }
  benchmark.report("Class") { klass = ClassDemo.new a: 1, b: 
  2, c: 3, d: 4, e: 5; klass.a; klass.b }
  benchmark.compare!
end
puts "--- 3 Reads to 1 Write ---"
Benchmark.ips do |benchmark|
  benchmark.config time: 5, warmup: 2
  benchmark.report("Array") { arr = [1, 2, 3, 4, 5]; arr[0]; 
  arr[1]; arr[2] }
  benchmark.report("Hash") { hash = {a: 1, b: 2, c: 3, d: 4, 
  e: 5}; hash[:a]; hash[:b]; hash[:c] }
  benchmark.report("Hash String") { hash = {'a' => 1, 'b' => 
  2, 'c' => 3, 'd' => 4, 'e' => 5}; hash['a']; hash['b']; hash['c'] }
  benchmark.report("Data") { data = DataDemo[a: 1, b: 2, c: 
  3, d: 4, e: 5]; data.a; data.b; data.c }
  benchmark.report("Struct") { struct = StructDemo[a: 1, b: 
  2, c: 3, d: 4, e: 5]; struct.a; struct.b; struct.c }
  benchmark.report("OpenStruct") { ostruct = OpenStruct.new 
  a: 1, b: 2, c: 3, d: 4, e: 5; ostruct.a; ostruct.b; ostruct.c }
  benchmark.report("Class") { klass = ClassDemo.new a: 1, b: 
  2, c: 3, d: 4, e: 5; klass.a; klass.b; klass.c }
  benchmark.compare!
end
puts "--- 5 Reads to 1 Write ---"
Benchmark.ips do |benchmark|
  benchmark.config time: 5, warmup: 2
  benchmark.report("Array") { arr = [1, 2, 3, 4, 5]; arr[0]; 
  arr[1]; arr[2]; arr[3]; arr[4] }
  benchmark.report("Hash") { hash = {a: 1, b: 2, c: 3, d: 4, 
  e: 5}; hash[:a]; hash[:b]; hash[:c]; hash[:d]; hash[:e] }
  benchmark.report("Hash String") { hash = {'a' => 1, 'b' => 
  2, 'c' => 3, 'd' => 4, 'e' => 5}; hash['a']; hash['b']; hash['c']; hash['d']; hash['e'] }
  benchmark.report("Data") { data = DataDemo[a: 1, b: 2, c: 
  3, d: 4, e: 5]; data.a; data.b; data.c; data.d; data.e }
  benchmark.report("Struct") { struct = StructDemo[a: 1, b: 
  2, c: 3, d: 4, e: 5]; struct.a; struct.b; struct.c; struct.d; struct.e }
  benchmark.report("OpenStruct") { ostruct = OpenStruct.new a: 
  1, b: 2, c: 3, d: 4, e: 5; ostruct.a; ostruct.b; ostruct.c; ostruct.d; ostruct.e }
  benchmark.report("Class") { klass = ClassDemo.new a: 1, b: 
  2, c: 3, d: 4, e: 5; klass.a; klass.b; klass.c; klass.d; klass.e }
  benchmark.compare!
end
puts "--- 10 Reads to 1 Write (5 attributes) ---"
Benchmark.ips do |benchmark|
  benchmark.config time: 5, warmup: 2
  benchmark.report("Array") { arr = [1, 2, 3, 4, 5]; arr[0]; arr[1]; 
  arr[2]; arr[3]; arr[4]; arr[0]; arr[1]; arr[2]; arr[3]; arr[4] }
  benchmark.report("Hash") { hash = {a: 1, b: 2, c: 3, d: 4, e: 5}; 
  hash[:a]; hash[:b]; hash[:c]; hash[:d]; hash[:e]; hash[:a]; hash[:b]; 
  hash[:c]; hash[:d]; hash[:e] }
  benchmark.report("Hash String") { hash = {'a' => 1, 'b' => 2, 'c' 
  => 3, 'd' => 4, 'e' => 5}; hash['a']; hash['b']; hash['c']; hash['d']; 
  hash['e']; hash['a']; hash['b']; hash['c']; hash['d']; hash['e'] }
  benchmark.report("Data") { data = DataDemo[a: 1, b: 2, c: 3, d: 4, e: 
  5]; data.a; data.b; data.c; data.d; data.e; data.a; data.b; data.c; data.d; data.e }
  benchmark.report("Struct") { struct = StructDemo[a: 1, b: 2, c: 3, d: 4, 
  e: 5]; struct.a; struct.b; struct.c; struct.d; struct.e; struct.a; struct.b; 
  struct.c; struct.d; struct.e }
  benchmark.report("OpenStruct") { ostruct = OpenStruct.new a: 1, b: 2, c: 
  3, d: 4, e: 5; ostruct.a; ostruct.b; ostruct.c; ostruct.d; ostruct.e; 
  ostruct.a; ostruct.b; ostruct.c; ostruct.d; ostruct.e }
  benchmark.report("Class") { klass = ClassDemo.new a: 1, b: 2, c: 3, d: 4, 
  e: 5; klass.a; klass.b; klass.c; klass.d; klass.e; klass.a; klass.b; klass.c; 
  klass.d; klass.e }
  benchmark.compare!
end
puts "--- 10 Reads to 1 Write (10 attributes) ---"
Benchmark.ips do |benchmark|
  benchmark.config time: 5, warmup: 2
  benchmark.report("Array") { arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; arr[0]; 
  arr[1]; arr[2]; arr[3]; arr[4]; arr[5]; arr[6]; arr[7]; arr[8]; arr[9] }
  benchmark.report("Hash") { hash = {a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 
  7, h: 8, i: 9, j: 10}; hash[:a]; hash[:b]; hash[:c]; hash[:d]; hash[:e]; 
  hash[:f]; hash[:g]; hash[:h]; hash[:i]; hash[:j] }
  benchmark.report("Hash String") { hash = {'a' => 1, 'b' => 2, 'c' => 3, 
  'd' => 4, 'e' => 5, 'f' => 6, 'g' => 7, 'h' => 8, 'i' => 9, 'j' => 10}; 
  hash['a']; hash['b']; hash['c']; hash['d']; hash['e']; hash['f']; hash['g']; 
  hash['h']; hash['i']; hash['j'] }
  benchmark.report("Data") { data = DataDemoTen.new(a: 1, b: 2, c: 3, d: 4, 
  e: 5, f: 6, g: 7, h: 8, i: 9, j: 10); data.a; data.b; data.c; data.d; data.e; 
  data.f; data.g; data.h; data.i; data.j }
  benchmark.report("Struct") { struct = StructDemoTen.new(1, 2, 3, 4, 5, 6, 7, 
  8, 9, 10); struct.a; struct.b; struct.c; struct.d; struct.e; struct.f; struct.g; 
  struct.h; struct.i; struct.j }
  benchmark.report("OpenStruct") { ostruct = OpenStruct.new(a: 1, b: 2, c: 3, 
  d: 4, e: 5, f: 6, g: 7, h: 8, i: 9, j: 10); ostruct.a; ostruct.b; ostruct.c; 
  ostruct.d; ostruct.e; ostruct.f; ostruct.g; ostruct.h; ostruct.i; ostruct.j }
  benchmark.report("Class") { klass = ClassDemoTen.new(a: 1, b: 2, c: 3, d: 4, 
  e: 5, f: 6, g: 7, h: 8, i: 9, j: 10); klass.a; klass.b; klass.c; klass.d; 
  klass.e; klass.f; klass.g; klass.h; klass.i; klass.j }
  benchmark.compare!
end
ruby 3.4.2 (2025-02-15 revision d2930f8e7a) +PRISM [arm64-darwin24]
1 read to 1 write
Calculating -------------------------------------
               Array     25.764M (± 0.9%) i/s   (38.81 ns/i) -    128.815M in   5.000339s
                Hash     21.860M (± 0.4%) i/s   (45.75 ns/i) -    111.235M in   5.088522s
         Hash String     20.215M (± 0.4%) i/s   (49.47 ns/i) -    102.154M in   5.053419s
                Data      4.158M (± 2.3%) i/s  (240.52 ns/i) -     21.125M in   5.083854s
              Struct      4.101M (± 1.9%) i/s  (243.83 ns/i) -     20.603M in   5.025646s
          OpenStruct    122.586k (± 0.7%) i/s    (8.16 μs/i) -    616.400k in   5.028558s
               Class      4.540M (± 1.7%) i/s  (220.25 ns/i) -     22.995M in   5.066432s
Comparison:
               Array: 25763513.4 i/s
                Hash: 21860209.5 i/s - 1.18x  slower
         Hash String: 20215108.6 i/s - 1.27x  slower
               Class:  4540193.3 i/s - 5.67x  slower
                Data:  4157661.5 i/s - 6.20x  slower
              Struct:  4101170.2 i/s - 6.28x  slower
          OpenStruct:   122586.4 i/s - 210.17x  slower
2 reads to 1 write
Calculating -------------------------------------
               Array     20.633M (± 0.7%) i/s   (48.47 ns/i) -    103.215M in   5.002565s
                Hash     18.106M (± 1.2%) i/s   (55.23 ns/i) -     92.276M in   5.097036s
         Hash String     16.850M (± 0.4%) i/s   (59.35 ns/i) -     84.474M in   5.013416s
                Data      4.088M (± 2.0%) i/s  (244.64 ns/i) -     20.519M in   5.021858s
              Struct      4.034M (± 1.6%) i/s  (247.90 ns/i) -     20.316M in   5.037631s
          OpenStruct    120.040k (± 1.0%) i/s    (8.33 μs/i) -    605.064k in   5.041019s
               Class      4.440M (± 1.5%) i/s  (225.21 ns/i) -     22.449M in   5.056871s
Comparison:
               Array: 20633383.1 i/s
                Hash: 18106481.9 i/s - 1.14x  slower
         Hash String: 16849875.2 i/s - 1.22x  slower
               Class:  4440226.6 i/s - 4.65x  slower
                Data:  4087571.4 i/s - 5.05x  slower
              Struct:  4033868.2 i/s - 5.12x  slower
          OpenStruct:   120039.9 i/s - 171.89x  slower
3 reads to 1 write
Calculating -------------------------------------
               Array     18.320M (± 0.9%) i/s   (54.58 ns/i) -     92.829M in   5.067386s
                Hash     16.198M (± 0.4%) i/s   (61.74 ns/i) -     82.530M in   5.095210s
         Hash String     14.845M (± 0.8%) i/s   (67.36 ns/i) -     74.947M in   5.048993s
                Data      3.993M (± 2.6%) i/s  (250.45 ns/i) -     20.235M in   5.071372s
              Struct      3.721M (± 8.3%) i/s  (268.72 ns/i) -     18.474M in   5.030555s
          OpenStruct    109.286k (±16.7%) i/s    (9.15 μs/i) -    504.820k in   5.042702s
               Class      4.311M (± 1.8%) i/s  (231.98 ns/i) -     21.626M in   5.018517s
Comparison:
               Array: 18320261.7 i/s
                Hash: 16197886.7 i/s - 1.13x  slower
         Hash String: 14844935.0 i/s - 1.23x  slower
               Class:  4310699.6 i/s - 4.25x  slower
                Data:  3992742.9 i/s - 4.59x  slower
              Struct:  3721375.0 i/s - 4.92x  slower
          OpenStruct:   109285.6 i/s - 167.64x  slower
5 reads to 1 write
Calculating -------------------------------------
               Array     15.308M (± 2.2%) i/s   (65.32 ns/i) -     77.630M in   5.073563s
                Hash     14.129M (±21.2%) i/s   (70.78 ns/i) -     64.798M in   4.984178s
         Hash String     12.384M (± 1.6%) i/s   (80.75 ns/i) -     62.810M in   5.073061s
                Data      3.740M (± 2.1%) i/s  (267.40 ns/i) -     18.929M in   5.063717s
              Struct      3.731M (± 1.6%) i/s  (267.99 ns/i) -     18.722M in   5.018610s
          OpenStruct    114.473k (± 1.0%) i/s    (8.74 μs/i) -    578.442k in   5.053565s
               Class      4.142M (± 1.2%) i/s  (241.42 ns/i) -     20.902M in   5.046783s
Comparison:
               Array: 15308341.0 i/s
                Hash: 14129046.7 i/s - same-ish: difference falls within error
         Hash String: 12384465.8 i/s - 1.24x  slower
               Class:  4142199.3 i/s - 3.70x  slower
                Data:  3739735.6 i/s - 4.09x  slower
              Struct:  3731458.8 i/s - 4.10x  slower
          OpenStruct:   114472.7 i/s - 133.73x  slower
10 reads to 1 write – 5 attributes
Calculating -------------------------------------
               Array     10.777M (± 0.7%) i/s   (92.79 ns/i) -     54.867M in   5.091625s
                Hash      8.618M (± 1.2%) i/s  (116.04 ns/i) -     43.838M in   5.087596s
         Hash String      7.962M (± 1.0%) i/s  (125.60 ns/i) -     40.308M in   5.062987s
                Data      3.440M (± 1.8%) i/s  (290.74 ns/i) -     17.278M in   5.025126s
              Struct      3.416M (± 1.2%) i/s  (292.75 ns/i) -     17.342M in   5.077662s
          OpenStruct    113.327k (± 0.8%) i/s    (8.82 μs/i) -    568.500k in   5.016805s
               Class      3.576M (± 1.4%) i/s  (279.64 ns/i) -     18.189M in   5.087567s
Comparison:
               Array: 10776504.7 i/s
                Hash:  8617805.7 i/s - 1.25x  slower
         Hash String:  7962076.7 i/s - 1.35x  slower
               Class:  3575982.6 i/s - 3.01x  slower
                Data:  3439532.9 i/s - 3.13x  slower
              Struct:  3415890.4 i/s - 3.15x  slower
          OpenStruct:   113326.8 i/s - 95.09x  slower
10 reads to 1 write – 10 attributes
Calculating -------------------------------------
               Array     10.711M (± 0.6%) i/s   (93.36 ns/i) -     53.734M in   5.016788s
                Hash      4.271M (± 1.7%) i/s  (234.13 ns/i) -     21.775M in   5.099576s
         Hash String      3.852M (± 2.0%) i/s  (259.59 ns/i) -     19.270M in   5.004605s
                Data      1.923M (± 2.3%) i/s  (520.06 ns/i) -      9.783M in   5.090560s
              Struct      6.601M (± 1.5%) i/s  (151.49 ns/i) -     33.252M in   5.038446s
          OpenStruct     59.513k (± 1.7%) i/s   (16.80 μs/i) -    297.550k in   5.001175s
               Class      1.885M (± 1.4%) i/s  (530.46 ns/i) -      9.427M in   5.001555s
Comparison:
               Array: 10711256.7 i/s
              Struct:  6601054.0 i/s - 1.62x  slower
                Hash:  4271204.4 i/s - 2.51x  slower
         Hash String:  3852188.8 i/s - 2.78x  slower
                Data:  1922861.9 i/s - 5.57x  slower
               Class:  1885149.8 i/s - 5.68x  slower
          OpenStruct:    59513.5 i/s - 179.98x  slower
Current Observations - What a rollercoaster!
That’s a lot of benchmarking! I was really hoping that with many reads, that Structs comes out more performant and it did! So, I’m happy with the results. What we can see is that Structs have performed very well even compared to Hashes when there are many attributes, in this case, in the 10 reads to 1 write – 10 attributes. So, while we are grateful that Classes has gotten more performant than Struct in the 5 attribute case, but Struct still is a great choice as a standard when passing around data, due to its good scalability.
Stringified Hashes are also performant under the frozen string literal comment, so there’s not much impact on using between symbolized and stringified Hashes.
Surprising Observation
The 5th Time
Someone in the new reddit thread has pointed out to me that my 10 reads to 1 write – 10 attributes case was written in such a way that we defined them inside the benchmark. I’m correcting the code, I have re-evaluated my observations once again. The mistake is what got me writing the Surprising Observation, wherein I thought that having more attributes greatly affects Classes, Structs, and Data compared to Hashes, but I was wrong. So, I’m very grateful for that as the correction has changed the narrative to recommend the usage of Structs vs Classes (and Hashes) if you’re solely looking for performance.
Struct as a Value Object
I think one of the most important thing with Structs (and Data) is that they’re value objects. In my own words, it means that you can compare them by themselves. Class instances cannot be compared by themselves, and that’s the only disadvantage I could see with classes, considering they’re more performant in most cases now.
Take a look at the Class code to show this behavior:
irb(main):001* class A
irb(main):002*   attr_reader :a
irb(main):003*   def initialize(a)
irb(main):004*     @a = a
irb(main):005*   end
irb(main):006> end
=> :initialize
irb(main):007> a = A.new(1)
=> #<A:0x000000012529f560 @a=1>
irb(main):008> b = A.new(1)
=> #<A:0x000000011fb11488 @a=1>
irb(main):009> a == b
=> false
Conclusion
I think it was a great decision to write this second article, because I’ve learned more things with the wonderful Ruby language. I hope you’ve enjoyed reading as I’ve enjoyed writing this.
Here are my takeaways on this:
- 
    In Ruby 3.4.2, Classes are slightly more performant than Structs when we use 5 attributes, but with 10 attributes, Structs come out on top even compared to Hashes. 
- 
    The order of priority (in terms of scalable performance) when using data structures are Arrays, Hashes, Structs, Classes, Data. But of course, these get used differently. When you want more structure, Structs are definitely on top of the list. 
- 
    Symbolised Hashes are better than Stringified Hashes even with the frozen string literal comment, but not very far off. 
- 
    Always use the frozen string literal comment. 
- 
    Don’t check twice, check 3, 4, 5 times! 
- 
    Articles you reference update themselves and make your referring article confusing. 
Ps. if you have any questions
Ask here