In Ruby, data can be encapsulated in a various ways. For example with Classes, but also with Hashes, Structs and OpenStructs, depending on the use case.
In case of simple data containers without any logic, Hashes are typically used. But Classes are the tool of choice as soon as logic has to be bound to the data. Somewhere in between there are Struct and OpenStruct. However, accessing the data is almost equally comfortable.
But what about the performance perspective?
Assertion
The instantiation of simple data containers (such as Hashes) should be faster than the instantiation of more complex Classes.
A benchmark test is meant to prove the assertion. First, the instantiations:
Struct
GeoStruct = Struct.new(:latitude, :longitude)
GeoStruct.new(53.6, 11.4)
OpenStruct.new(latitude: 53.6, longitude: 11.4)
class GeoClass
attr_accessor :latitude, :longitude
def initialize(latitude:, longitude:)
@latitude = latitude
@longitude = longitude
end
end
GeoClass.new(latitude: 53.6, longitude: 11.4)
{ latitude: 53.6, longitude: 11.4 }
Benchmarks
The benchmarking Gem of choice is * benchmark-ips *. It measures the number of iterations per second. In addition, the Ruby Garbage Collector (GC) can be disabled during the benchmark.
The benchmark script:
require 'benchmark/ips'
require 'ostruct'
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
Benchmark.ips do |bm|
bm.config(suite: suite)
bm.report('GeoStruct.new') do
GeoStruct.new(53.6, 11.4)
end
bm.report('Hash {}') do
{ latitude: 53.6, longitude: 11.4 }
end
bm.report('GeoClass.new') do
GeoClass.new(latitude: 53.6, longitude: 11.4)
end
bm.report('OpenStruct.new') do
OpenStruct.new(latitude: 53.6, longitude: 11.4)
end
end
Results
The results are amazing:
Warming up --------------------------------------
GeoStruct.new 99.444k i/100ms
Hash {} 80.241k i/100ms
GeoClass.new 45.358k i/100ms
OpenStruct.new 39.240k i/100ms
Calculating -------------------------------------
GeoStruct.new 2.517M (± 3.6%) i/s - 12.629M in 5.024347s
Hash {} 1.606M (± 9.9%) i/s - 8.024M in 5.036606s
GeoClass.new 730.282k (± 1.1%) i/s - 3.674M in 5.031556s
OpenStruct.new 574.970k (± 3.0%) i/s - 2.904M in 5.055057s
Obviously the instantiation of Structs requires the least resources. In the same time, approximately 60% more Struct objects can be instantiated than Hashes. Struct is even faster than Class by the factor 3.5.
Conclusion
When it comes to performance, Struct is the winner. Hash as data container is also still with good performance. Classes should be used when creating objects that contain more extensive logic.
OpenStruct is, on the other hand, more suitable for prototyping, but less for high-performance instantiation of massive objects.