require 'benchmark'
require 'ostruct'

opt = {
  trace_instruction: false,
  tailcall_optimization: true,
  peephole_optimization: true,
}
RubyVM::InstructionSequence.compile(src = <<END, __FILE__, __FILE__, __LINE__, opt).eval
class Array
  def dig_2(idx, *rest)
    if rest.empty?
      self[idx]
    elsif !nil.equal?(v = self[idx])
      v.dig_2(*rest)
    end
  end
end
class Hash
  def dig_2(idx, *rest)
    if rest.empty?
      self[idx]
    elsif !nil.equal?(v = self[idx])
      v.dig_2(*rest)
    end
  end
end
class OpenStruct
  def dig(name, *names)
    name = name.to_sym
    table = @table
    if names.empty?
      table[name]
    elsif !nil.equal?(name = self[name])
      name.dig(*names)
    end
  end
  def dig_2(name, *names)
    name = name.to_sym
    table = @table
    if names.empty?
      table[name]
    elsif !nil.equal?(name = self[name])
      name.dig_2(*names)
    end
  end
end
END

a = []
100.times {a = [a]}
l = [:a]*100
o = a
h = a
mh = []
mo = []
l.reverse_each {|i|
  h = {i => h}
  o = OpenStruct.new(i => o)
  mh = {i => [mh]}
  mo = OpenStruct.new(i => [mo])
}
l2 = l + [0]*100
l3 = [:a, 0]*100

N = 20_000

Benchmark.bm(24) do |x|
  x.report("Hash only(C)")            {N.times {h.dig(*l)}}
  x.report("Hash only(Ruby)")         {N.times {h.dig_2(*l)}}
  x.report("Hash+Array (C)")          {N.times {h.dig(*l2)}}
  x.report("Hash+Array (Ruby)")       {N.times {h.dig_2(*l2)}}
  x.report("OpenStruct (C)")          {N.times {o.dig(*l)}}
  x.report("OpenStruct (Ruby)")       {N.times {o.dig_2(*l)}}
  x.report("OpenStruct+Array (C)")    {N.times {o.dig(*l2)}}
  x.report("OpenStruct+Array (Ruby)") {N.times {o.dig_2(*l2)}}
  x.report("Mix Hash (C)")            {N.times {mh.dig(*l3)}}
  x.report("Mix Hash (RUBY)")         {N.times {mh.dig_2(*l3)}}
  x.report("Mix OpenStruct (C)")      {N.times {mo.dig(*l3)}}
  x.report("Mix OpenStruct (Ruby)")   {N.times {mo.dig_2(*l3)}}
end
