Struct vs. OpenStruct benchmark

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

OpenStruct.new(latitude: 53.6, longitude: 11.4)

Class

class GeoClass
  attr_accessor :latitude, :longitude

  def initialize(latitude:, longitude:)
    @latitude = latitude
    @longitude = longitude
  end
end
GeoClass.new(latitude: 53.6, longitude: 11.4)

Hash

{ 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.