Skip to content

Instantly share code, notes, and snippets.

@schneems
Created October 3, 2019 19:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save schneems/3fb96b12d2f88d0700acc5dd7cb437ee to your computer and use it in GitHub Desktop.
Save schneems/3fb96b12d2f88d0700acc5dd7cb437ee to your computer and use it in GitHub Desktop.
# frozen_string_literal: true

require "benchmark/ips"

RECURSIVE_HASH_DUP = {
  Integer => -> (val) { val },
  Float => -> (val) { val },
  TrueClass => -> (val) { val },
  FalseClass => -> (val) { val },
  NilClass => -> (val) { val },
  NilClass => -> (val) { val },
  String => ->(val) { val.dup },
  Array => ->(val) {
    duped = Array.new(val.size)
    duped.each_with_index do |value, index|
      duped[index] = RECURSIVE_HASH_DUP[value.class].call(value)
    end
  },
  Hash => ->(val) {
    duped = val.dup
    duped.each_pair do |key, value|
      duped[key] = RECURSIVE_HASH_DUP[value.class].call(value)
    end
  }
}
RECURSIVE_HASH_DUP.default_proc = ->(_, val) { |_, hash|
  case val
  when Array
    RECURSIVE_HASH_DUP[Array].call(val)
  when Hash
    RECURSIVE_HASH_DUP[Hash].call(val)
  else
    val.dup
  end
}
RECURSIVE_HASH_DUP.compare_by_identity.rehash

def recursive_hash_dup(obj)
  RECURSIVE_HASH_DUP[obj.class].call(obj)
end

def deep_dup(obj)
  case obj
  when Integer, Float, TrueClass, FalseClass, NilClass
    return obj
  when String
    return obj.dup
  when Array
    duped = Array.new(obj.size)
    duped.each_with_index do |value, index|
      duped[index] = deep_dup(value)
    end
  when Hash
    duped = obj.dup
    duped.each_pair do |key, value|
      duped[key] = deep_dup(value)
    end
  else
    duped = obj.dup
  end

  duped
end

def deep_dup_without_case(obj)
  if Integer === obj || Float === obj || TrueClass === obj || FalseClass === obj || NilClass === obj
    return obj
  elsif String === obj
    return obj.dup
  elsif Array === obj
    duped = Array.new(obj.size)
    obj.each_with_index do |value, index|
      duped[index] = deep_dup_without_case(value)
    end
  elsif Hash === obj
    duped = obj.dup
    duped.each_pair do |key, value|
      duped[key] = deep_dup_without_case(value)
    end
  else
    duped = obj.dup
  end

  duped
end


obj = { "class" => "FooWorker", "args" => [1, 2, 3, "foobar"], "jid" => "123987123" }

Benchmark.ips do |x|
  x.report("marshal") do
    Marshal.load(Marshal.dump(obj))
  end

  x.report("deep_dup") do
    deep_dup(obj)
  end

  x.report("deep_dup_without_case") do
    deep_dup_without_case(obj)
  end

  x.report("hash deep dup") do
    recursive_hash_dup(obj)
  end

  x.compare!
end
$ ruby scratch.rb
Warming up --------------------------------------
             marshal    14.343k i/100ms
            deep_dup    27.024k i/100ms
deep_dup_without_case
                        31.578k i/100ms
       hash deep dup    38.321k i/100ms
Calculating -------------------------------------
             marshal    148.202k (± 3.5%) i/s -    745.836k in   5.039228s
            deep_dup    285.230k (± 2.4%) i/s -      1.432M in   5.024573s
deep_dup_without_case
                        338.379k (± 5.1%) i/s -      1.705M in   5.056639s
       hash deep dup    396.849k (± 8.9%) i/s -      1.993M in   5.066124s

Comparison:
       hash deep dup:   396849.2 i/s
deep_dup_without_case:   338379.3 i/s - 1.17x  slower
            deep_dup:   285229.5 i/s - 1.39x  slower
             marshal:   148201.7 i/s - 2.68x  slower
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment