Upgrade to Pro — share decks privately, control downloads, hide ads and more …

SLOMO

 SLOMO

No one wants to be stuck in the slow lane, especially Rubyists. In this talk we'll look at the slow process of writing fast code. We'll look at several real world performance optimizations that may surprise you. We'll then rewind to see how these slow spots were found and fixed. Come to this talk and we will "C" how fast your Ruby can "Go".

Richard Schneeman

November 18, 2016
Tweet

More Decks by Richard Schneeman

Other Decks in Programming

Transcript

  1. BTW

  2. is

  3. task "assets:profile" do puts "==============" StackProf.run(mode: :wall, out: "tmp/stackprof.dump") do

    Rake::Task["assets:precompile"].invoke end puts "Running: $ stackprof tmp/stackprof.dump" puts `stackprof tmp/stackprof.dump` end
  4. task "assets:profile" do puts "==============" StackProf.run(mode: :wall, out: "tmp/stackprof.dump") do

    Rake::Task["assets:precompile"].invoke end puts "Running: $ stackprof tmp/stackprof.dump" puts `stackprof tmp/stackprof.dump` end
  5. task "assets:profile" do puts "==============" StackProf.run(mode: :wall, out: "tmp/stackprof.dump") do

    Rake::Task["assets:precompile"].invoke end puts "Running: $ stackprof tmp/stackprof.dump" puts `stackprof tmp/stackprof.dump` end
  6. Running: $ stackprof tmp/stackprof.dump ================================== Mode: wall(1000) Samples: 2083 (62.23%

    miss rate) GC: 282 (13.54%) ================================== TOTAL (pct) SAMPLES (pct) FRAME 313 (15.0%) 313 (15.0%) Set#include? 307 (14.7%) 243 (11.7%) Sprockets::DigestUtils#digest 743 (35.7%) 137 (6.6%) Kernel#require 476 (22.9%) 124 (6.0%) Kernel#require 157 (7.5%) 92 (4.4%) SassC::Rails::Importer#imports 243 (11.7%) 81 (3.9%) #<Module:0x007fc76f34eb10> 67 (3.2%) 67 (3.2%) NumericWithFormat#to_s 160 (7.7%) 53 (2.5%) PathUtils#atomic_write 44 (2.1%) 44 (2.1%) #<Module:0x007fc76bdfb558> 239 (11.5%) 37 (1.8%) Sprockets::Cache::FileStore#set
  7. Running: $ stackprof tmp/stackprof.dump ================================== Mode: wall(1000) Samples: 2083 (62.23%

    miss rate) GC: 282 (13.54%) ================================== TOTAL (pct) SAMPLES (pct) FRAME 313 (15.0%) 313 (15.0%) Set#include? 307 (14.7%) 243 (11.7%) Sprockets::DigestUtils#digest 743 (35.7%) 137 (6.6%) Kernel#require 476 (22.9%) 124 (6.0%) Kernel#require 157 (7.5%) 92 (4.4%) SassC::Rails::Importer#imports 243 (11.7%) 81 (3.9%) #<Module:0x007fc76f34eb10> 67 (3.2%) 67 (3.2%) NumericWithFormat#to_s 160 (7.7%) 53 (2.5%) PathUtils#atomic_write 44 (2.1%) 44 (2.1%) #<Module:0x007fc76bdfb558> 239 (11.5%) 37 (1.8%) Sprockets::Cache::FileStore#set Class and method
  8. Running: $ stackprof tmp/stackprof.dump ================================== Mode: wall(1000) Samples: 2083 (62.23%

    miss rate) GC: 282 (13.54%) ================================== TOTAL (pct) SAMPLES (pct) FRAME 313 (15.0%) 313 (15.0%) Set#include? 307 (14.7%) 243 (11.7%) Sprockets::DigestUtils#digest 743 (35.7%) 137 (6.6%) Kernel#require 476 (22.9%) 124 (6.0%) Kernel#require 157 (7.5%) 92 (4.4%) SassC::Rails::Importer#imports 243 (11.7%) 81 (3.9%) #<Module:0x007fc76f34eb10> 67 (3.2%) 67 (3.2%) NumericWithFormat#to_s 160 (7.7%) 53 (2.5%) PathUtils#atomic_write 44 (2.1%) 44 (2.1%) #<Module:0x007fc76bdfb558> 239 (11.5%) 37 (1.8%) Sprockets::Cache::FileStore#set Total # of samples
  9. Running: $ stackprof tmp/stackprof.dump ================================== Mode: wall(1000) Samples: 2083 (62.23%

    miss rate) GC: 282 (13.54%) ================================== TOTAL (pct) SAMPLES (pct) FRAME 313 (15.0%) 313 (15.0%) Set#include? 307 (14.7%) 243 (11.7%) Sprockets::DigestUtils#digest 743 (35.7%) 137 (6.6%) Kernel#require 476 (22.9%) 124 (6.0%) Kernel#require 157 (7.5%) 92 (4.4%) SassC::Rails::Importer#imports 243 (11.7%) 81 (3.9%) #<Module:0x007fc76f34eb10> 67 (3.2%) 67 (3.2%) NumericWithFormat#to_s 160 (7.7%) 53 (2.5%) PathUtils#atomic_write 44 (2.1%) 44 (2.1%) #<Module:0x007fc76bdfb558> 239 (11.5%) 37 (1.8%) Sprockets::Cache::FileStore#set Percentage
  10. Running: $ stackprof tmp/stackprof.dump ================================== Mode: wall(1000) Samples: 2083 (62.23%

    miss rate) GC: 282 (13.54%) ================================== TOTAL (pct) SAMPLES (pct) FRAME 313 (15.0%) 313 (15.0%) Set#include? 307 (14.7%) 243 (11.7%) Sprockets::DigestUtils#digest 743 (35.7%) 137 (6.6%) Kernel#require 476 (22.9%) 124 (6.0%) Kernel#require 157 (7.5%) 92 (4.4%) SassC::Rails::Importer#imports 243 (11.7%) 81 (3.9%) #<Module:0x007fc76f34eb10> 67 (3.2%) 67 (3.2%) NumericWithFormat#to_s 160 (7.7%) 53 (2.5%) PathUtils#atomic_write 44 (2.1%) 44 (2.1%) #<Module:0x007fc76bdfb558> 239 (11.5%) 37 (1.8%) Sprockets::Cache::FileStore#set Total at TOP of stack
  11. Running: $ stackprof tmp/stackprof.dump ================================== Mode: wall(1000) Samples: 2083 (62.23%

    miss rate) GC: 282 (13.54%) ================================== TOTAL (pct) SAMPLES (pct) FRAME 313 (15.0%) 313 (15.0%) Set#include? 307 (14.7%) 243 (11.7%) Sprockets::DigestUtils#digest 743 (35.7%) 137 (6.6%) Kernel#require 476 (22.9%) 124 (6.0%) Kernel#require 157 (7.5%) 92 (4.4%) SassC::Rails::Importer#imports 243 (11.7%) 81 (3.9%) #<Module:0x007fc76f34eb10> 67 (3.2%) 67 (3.2%) NumericWithFormat#to_s 160 (7.7%) 53 (2.5%) PathUtils#atomic_write 44 (2.1%) 44 (2.1%) #<Module:0x007fc76bdfb558> 239 (11.5%) 37 (1.8%) Sprockets::Cache::FileStore#set Percent at TOP of stack
  12. Running: $ stackprof tmp/stackprof.dump ================================== Mode: wall(1000) Samples: 2083 (62.23%

    miss rate) GC: 282 (13.54%) ================================== TOTAL (pct) SAMPLES (pct) FRAME 313 (15.0%) 313 (15.0%) Set#include? 307 (14.7%) 243 (11.7%) Sprockets::DigestUtils#digest 743 (35.7%) 137 (6.6%) Kernel#require 476 (22.9%) 124 (6.0%) Kernel#require 157 (7.5%) 92 (4.4%) SassC::Rails::Importer#imports 243 (11.7%) 81 (3.9%) #<Module:0x007fc76f34eb10> 67 (3.2%) 67 (3.2%) NumericWithFormat#to_s 160 (7.7%) 53 (2.5%) PathUtils#atomic_write 44 (2.1%) 44 (2.1%) #<Module:0x007fc76bdfb558> 239 (11.5%) 37 (1.8%) Sprockets::Cache::FileStore#set
  13. $ stackprof tmp/stackprof.dump --method Set#include? Set#include? (/Users/richardschneeman/.rubies/ruby-2.3.1/lib/ruby/2.3.0/set.rb:214) samples: 313 self

    (15.0%) / 313 total (15.0%) callers: 312 ( 99.7%) Sprockets::ProcessorUtils#valid_processor_metadata_value? 1 ( 0.3%) Sprockets::Utils#dfs_paths code: | 214 | def include?(o) 313 (15.0%) / 313 (15.0%) | 215 | @hash[o] | 216 | end
  14. $ stackprof tmp/stackprof.dump --method Set#include? Set#include? (/Users/richardschneeman/.rubies/ruby-2.3.1/lib/ruby/2.3.0/set.rb:214) samples: 313 self

    (15.0%) / 313 total (15.0%) callers: 312 ( 99.7%) Sprockets::ProcessorUtils#valid_processor_metadata_value? 1 ( 0.3%) Sprockets::Utils#dfs_paths code: | 214 | def include?(o) 313 (15.0%) / 313 (15.0%) | 215 | @hash[o] | 216 | end
  15. $ stackprof tmp/stackprof.dump --method Sprockets::ProcessorUtils#valid_processor_metadata_value? Sprockets::ProcessorUtils#valid_processor_metadata_value? (/Users/richardschneeman/.gem/ruby/2.3.1/bundler/ gems/sprockets-3b0d6732c13f/lib/sprockets/processor_utils.rb:170) samples: 24

    self (1.2%) / 2129 total (102.2%) callers: 1793 ( 84.2%) Sprockets::ProcessorUtils#valid_processor_metadata_value? 336 ( 15.8%) Sprockets::ProcessorUtils#validate_processor_result! callees (2105 total): 1793 ( 85.2%) Sprockets::ProcessorUtils#valid_processor_metadata_value? 312 ( 14.8%) Set#include? code: | 170 | def valid_processor_metadata_value?(value) 261 (12.5%) / 2 (0.1%) | 171 | if VALID_METADATA_VALUE_TYPES.include?(value.class) | 172 | true 61 (2.9%) / 8 (0.4%) | 173 | elsif VALID_METADATA_COMPOUND_TYPES.include?(value.class) 1806 (86.7%) / 13 (0.6%) | 174 | value.all? { |v| valid_processor_metadata_value?(v) } | 175 | else 1 (0.0%) / 1 (0.0%) | 176 | false | 177 | end
  16. $ stackprof tmp/stackprof.dump --method Sprockets::ProcessorUtils#valid_processor_metadata_value? Sprockets::ProcessorUtils#valid_processor_metadata_value? (/Users/richardschneeman/.gem/ruby/2.3.1/bundler/ gems/sprockets-3b0d6732c13f/lib/sprockets/processor_utils.rb:170) samples: 24

    self (1.2%) / 2129 total (102.2%) callers: 1793 ( 84.2%) Sprockets::ProcessorUtils#valid_processor_metadata_value? 336 ( 15.8%) Sprockets::ProcessorUtils#validate_processor_result! callees (2105 total): 1793 ( 85.2%) Sprockets::ProcessorUtils#valid_processor_metadata_value? 312 ( 14.8%) Set#include? code: | 170 | def valid_processor_metadata_value?(value) 261 (12.5%) / 2 (0.1%) | 171 | if VALID_METADATA_VALUE_TYPES.include?(value.class) | 172 | true 61 (2.9%) / 8 (0.4%) | 173 | elsif VALID_METADATA_COMPOUND_TYPES.include?(value.class) 1806 (86.7%) / 13 (0.6%) | 174 | value.all? { |v| valid_processor_metadata_value?(v) } | 175 | else 1 (0.0%) / 1 (0.0%) | 176 | false | 177 | end
  17. $ stackprof tmp/stackprof.dump --method Sprockets::ProcessorUtils#valid_processor_metadata_value? Sprockets::ProcessorUtils#valid_processor_metadata_value? (/Users/richardschneeman/.gem/ruby/2.3.1/bundler/ gems/sprockets-3b0d6732c13f/lib/sprockets/processor_utils.rb:170) samples: 24

    self (1.2%) / 2129 total (102.2%) callers: 1793 ( 84.2%) Sprockets::ProcessorUtils#valid_processor_metadata_value? 336 ( 15.8%) Sprockets::ProcessorUtils#validate_processor_result! callees (2105 total): 1793 ( 85.2%) Sprockets::ProcessorUtils#valid_processor_metadata_value? 312 ( 14.8%) Set#include? code: | 170 | def valid_processor_metadata_value?(value) 261 (12.5%) / 2 (0.1%) | 171 | if VALID_METADATA_VALUE_TYPES.include?(value.class) | 172 | true 61 (2.9%) / 8 (0.4%) | 173 | elsif VALID_METADATA_COMPOUND_TYPES.include?(value.class) 1806 (86.7%) / 13 (0.6%) | 174 | value.all? { |v| valid_processor_metadata_value?(v) } | 175 | else 1 (0.0%) / 1 (0.0%) | 176 | false | 177 | end
  18. code = " foo = Hash.new foo[:bar] " puts RubyVM::InstructionSequence.compile(code).disasm

    # 0000 trace 1 ( 2) # 0002 getinlinecache 9, <is:0> # 0005 getconstant :Hash # 0007 setinlinecache <is:0> # 0009 opt_send_without_block <callinfo!mid:new, argc:0, ARGS_SIMPLE>, <callcache> # 0012 setlocal_OP__WC__0 2 # 0014 trace 1 ( 3) # 0016 getlocal_OP__WC__0 2 # 0018 putobject :bar # 0020 opt_aref <callinfo!mid:[], argc:1, ARGS_SIMPLE>, <callcache> # 0023 leave
  19. code = " s = Set.new s.include?(:bar) " puts RubyVM::InstructionSequence.compile(code).disasm

    # 0000 trace 1 ( 2) # 0002 getinlinecache 9, <is:0> # 0005 getconstant :Set # 0007 setinlinecache <is:0> # 0009 opt_send_without_block <callinfo!mid:new, argc:0, ARGS_SIMPLE>, <callcache> # 0012 setlocal_OP__WC__0 2 # 0014 trace 1 ( 3) # 0016 getlocal_OP__WC__0 2 # 0018 putobject :bar # 0020 opt_send_without_block <callinfo!mid:include?, argc:1, ARGS_SIMPLE>, <callcache> # 0023 leave
  20. /** @c optimize @e [] @j 最適化された recv[obj]。 */ DEFINE_INSN

    opt_aref (CALL_INFO ci, CALL_CACHE cc) (VALUE recv, VALUE obj) (VALUE val) { if (!SPECIAL_CONST_P(recv)) { if (RBASIC_CLASS(recv) == rb_cArray && BASIC_OP_UNREDEFINED_P(BOP_AREF, ARRAY_REDEFINED_OP_FLAG) && FIXNUM_P(obj)) { val = rb_ary_entry(recv, FIX2LONG(obj)); } else if (RBASIC_CLASS(recv) == rb_cHash && BASIC_OP_UNREDEFINED_P(BOP_AREF, HASH_REDEFINED_OP_FLAG)) { val = rb_hash_aref(recv, obj); } else { goto INSN_LABEL(normal_dispatch); } } else { INSN_LABEL(normal_dispatch): PUSH(recv); PUSH(obj); CALL_SIMPLE_METHOD(recv); } }
  21. /** @c optimize @e [] @j 最適化された recv[obj]。 */ DEFINE_INSN

    opt_aref (CALL_INFO ci, CALL_CACHE cc) (VALUE recv, VALUE obj) (VALUE val) { if (!SPECIAL_CONST_P(recv)) { if (RBASIC_CLASS(recv) == rb_cArray && BASIC_OP_UNREDEFINED_P(BOP_AREF, ARRAY_REDEFINED_OP_FLAG) && FIXNUM_P(obj)) { val = rb_ary_entry(recv, FIX2LONG(obj)); } else if (RBASIC_CLASS(recv) == rb_cHash && BASIC_OP_UNREDEFINED_P(BOP_AREF, HASH_REDEFINED_OP_FLAG)) { val = rb_hash_aref(recv, obj); } else { goto INSN_LABEL(normal_dispatch); } } else { INSN_LABEL(normal_dispatch): PUSH(recv); PUSH(obj); CALL_SIMPLE_METHOD(recv); } }
  22. /** @c optimize @e Invoke method without block @j Invoke

    method without block */ DEFINE_INSN opt_send_without_block (CALL_INFO ci, CALL_CACHE cc) (...) (VALUE val) // inc += -ci->orig_argc; { struct rb_calling_info calling; calling.blockptr = NULL; vm_search_method(ci, cc, calling.recv = TOPN(calling.argc = ci->orig_argc)); CALL_METHOD(&calling, ci, cc); }
  23. /** @c optimize @e Invoke method without block @j Invoke

    method without block */ DEFINE_INSN opt_send_without_block (CALL_INFO ci, CALL_CACHE cc) (...) (VALUE val) // inc += -ci->orig_argc; { struct rb_calling_info calling; calling.blockptr = NULL; vm_search_method(ci, cc, calling.recv = TOPN(calling.argc = ci->orig_argc)); CALL_METHOD(&calling, ci, cc); }
  24. BTW

  25. /** @c optimize @e [] @j 最適化された recv[obj]。 */ DEFINE_INSN

    opt_aref (CALL_INFO ci, CALL_CACHE cc) (VALUE recv, VALUE obj) (VALUE val) { if (!SPECIAL_CONST_P(recv)) { if (RBASIC_CLASS(recv) == rb_cArray && BASIC_OP_UNREDEFINED_P(BOP_AREF, ARRAY_REDEFINED_OP_FLAG) && FIXNUM_P(obj)) { val = rb_ary_entry(recv, FIX2LONG(obj)); } else if (RBASIC_CLASS(recv) == rb_cHash && BASIC_OP_UNREDEFINED_P(BOP_AREF, HASH_REDEFINED_OP_FLAG)) { val = rb_hash_aref(recv, obj); } else { goto INSN_LABEL(normal_dispatch); } } else { INSN_LABEL(normal_dispatch): PUSH(recv); PUSH(obj); CALL_SIMPLE_METHOD(recv); } } You lose speed
  26. VALID_METADATA_VALUE_TYPES_HASH = VALID_METADATA_VALUE_TYPES. each_with_object({}) do |type, hash| hash[type] = true

    end.freeze def valid_processor_metadata_value?(value) if VALID_METADATA_VALUE_TYPES_HASH[value.class] true elsif VALID_METADATA_COMPOUND_TYPES_HASH[value.class] value.all? { |v| valid_processor_metadata_value?(v) } else false end end
  27. TOTAL (pct) SAMPLES (pct) FRAME 2328 (109.6%) 362 (17.0%) Sprockets::ProcessorUtils#

    valid_processor_metadata_value? 348 (16.4%) 256 (12.1%) Sprockets::DigestUtils#digest 486 (22.9%) 106 (5.0%) Kernel#require 97 (4.6%) 97 (4.6%) ActiveSupport:: NumericWithFormat#to_s 123 (5.8%) 94 (4.4%) Sprockets::PathUtils#atomic_write 581 (27.4%) 76 (3.6%) Kernel#require 61 (2.9%) 61 (2.9%) #<Module:0x007fb0a6027728> .mechanism 193 (9.1%) 52 (2.4%) Sprockets::Cache::FileStore#set 95 (4.5%) 48 (2.3%) SassC::Rails::Importer# imports 36 (1.7%) 36 (1.7%) ExecJS::ExternalRuntime# exec_runtime 59 (2.8%) 25 (1.2%) Kernel#require 75 (3.5%) 25 (1.2%) Module#delegate
  28. $ stackprof tmp/stackprof.dump --method Sprockets::DigestUtils#digest # . . . Sprockets::DigestUtils#digest

    (lib/sprockets/digest_utils.rb:46) samples: 4 self (0.2%) / 7 total (0.3%) callers: 5 ( 71.4%) Sprockets::Cache#expand_key 2 ( 28.6%) Sprockets::Loader#load_from_unloaded callees (3 total): 3 ( 100.0%) Sprockets::DigestUtils#digest_class code: | 46 | def digest(obj) 4 (0.2%) / 1 (0.0%) | 47 | digest = digest_class.new | 48 | queue = [obj] | 49 | | 50 | while queue.length > 0 | 51 | obj = queue.shift | 52 | klass = obj.class | 53 | 2 (0.1%) / 2 (0.1%) | 54 | if klass == String | 55 | digest << obj | 56 | elsif klass == Symbol | 57 | digest << 'Symbol' | 58 | digest << obj.to_s | 59 | elsif klass == Fixnum
  29. $ stackprof tmp/stackprof.dump --method Sprockets::DigestUtils#digest # . . . Sprockets::DigestUtils#digest

    (lib/sprockets/digest_utils.rb:46) samples: 4 self (0.2%) / 7 total (0.3%) callers: 5 ( 71.4%) Sprockets::Cache#expand_key 2 ( 28.6%) Sprockets::Loader#load_from_unloaded callees (3 total): 3 ( 100.0%) Sprockets::DigestUtils#digest_class code: | 46 | def digest(obj) 4 (0.2%) / 1 (0.0%) | 47 | digest = digest_class.new | 48 | queue = [obj] | 49 | | 50 | while queue.length > 0 | 51 | obj = queue.shift | 52 | klass = obj.class | 53 | 2 (0.1%) / 2 (0.1%) | 54 | if klass == String | 55 | digest << obj | 56 | elsif klass == Symbol | 57 | digest << 'Symbol' | 58 | digest << obj.to_s | 59 | elsif klass == Fixnum
  30. def digest(obj) digest = digest_class.new queue = [obj] while queue.length

    > 0 obj = queue.shift klass = obj.class if klass == String digest << obj elsif klass == Symbol digest << 'Symbol' digest << obj.to_s elsif klass == Fixnum digest << 'Fixnum' digest << obj.to_s elsif klass == Bignum digest << 'Bignum' digest << obj.to_s elsif klass == TrueClass digest << 'TrueClass' elsif klass == FalseClass digest << 'FalseClass'
  31. if String elsif Symbol elsif Fixnum elsif Bignum elsif TrueClass

    elsif FalseClass elsif NilClass elsif Array
  32. if String elsif Symbol elsif Fixnum elsif Bignum elsif TrueClass

    elsif FalseClass elsif NilClass elsif Array elsif Hash
  33. if String elsif Symbol elsif Fixnum elsif Bignum elsif TrueClass

    elsif FalseClass elsif NilClass elsif Array elsif Hash elsif Set
  34. if String elsif Symbol elsif Fixnum elsif Bignum elsif TrueClass

    elsif FalseClass elsif NilClass elsif Array elsif Hash elsif Set elsif Encoding
  35. if String elsif Symbol elsif Fixnum elsif Bignum elsif TrueClass

    elsif FalseClass elsif NilClass elsif Array elsif Hash elsif Set elsif Encoding Expand and Iterate
  36. or

  37. def digest(obj) digest = digest_class.new queue = [obj] while queue.length

    > 0 obj = queue.shift klass = obj.class if klass == String digest << obj elsif klass == Symbol digest << 'Symbol' digest << obj.to_s elsif klass == Fixnum digest << 'Fixnum' digest << obj.to_s elsif klass == Bignum digest << 'Bignum' digest << obj.to_s elsif klass == TrueClass digest << 'TrueClass' elsif klass == FalseClass digest << 'FalseClass' elsif klass == NilClass digest << 'NilClass'.freeze elsif klass == Array digest << 'Array' queue.concat(obj) elsif klass == Hash digest << 'Hash' queue.concat(obj.sort) elsif klass == Set digest << 'Set' queue.concat(obj.to_a) elsif klass == Encoding digest << 'Encoding' digest << obj.name else raise TypeError, "couldn't digest #{klass}" end end digest.digest end
  38. ADD_VALUE_TO_DIGEST = { String => ->(val, digest) { digest <<

    val }, FalseClass => ->(val, digest) { digest << 'FalseClass'.freeze }, TrueClass => ->(val, digest) { digest << 'TrueClass'.freeze }, NilClass => ->(val, digest) { digest << 'NilClass'.freeze }, Symbol => ->(val, digest) { digest << 'Symbol'.freeze digest << val.to_s }, Integer => ->(val, digest) { digest << 'Integer'.freeze digest << val.to_s }, Array => ->(val, digest) { digest << 'Array'.freeze val.each do |element| ADD_VALUE_TO_DIGEST[element.class].call(element, digest) end },
  39. ADD_VALUE_TO_DIGEST = { String => ->(val, digest) { digest <<

    val }, FalseClass => ->(val, digest) { digest << 'FalseClass'.freeze }, TrueClass => ->(val, digest) { digest << 'TrueClass'.freeze }, NilClass => ->(val, digest) { digest << 'NilClass'.freeze }, Symbol => ->(val, digest) { digest << 'Symbol'.freeze digest << val.to_s }, Integer => ->(val, digest) { digest << 'Integer'.freeze digest << val.to_s }, Array => ->(val, digest) { digest << 'Array'.freeze val.each do |element| ADD_VALUE_TO_DIGEST[element.class].call(element, digest) end },
  40. ADD_VALUE_TO_DIGEST = { String => ->(val, digest) { digest <<

    val }, FalseClass => ->(val, digest) { digest << 'FalseClass'.freeze }, TrueClass => ->(val, digest) { digest << 'TrueClass'.freeze }, NilClass => ->(val, digest) { digest << 'NilClass'.freeze }, Symbol => ->(val, digest) { digest << 'Symbol'.freeze digest << val.to_s }, Integer => ->(val, digest) { digest << 'Integer'.freeze digest << val.to_s }, Array => ->(val, digest) { digest << 'Array'.freeze val.each do |element| ADD_VALUE_TO_DIGEST[element.class].call(element, digest) end },
  41. ADD_VALUE_TO_DIGEST = { String => ->(val, digest) { digest <<

    val }, FalseClass => ->(val, digest) { digest << 'FalseClass'.freeze }, TrueClass => ->(val, digest) { digest << 'TrueClass'.freeze }, NilClass => ->(val, digest) { digest << 'NilClass'.freeze }, Symbol => ->(val, digest) { digest << 'Symbol'.freeze digest << val.to_s }, Integer => ->(val, digest) { digest << 'Integer'.freeze digest << val.to_s }, Array => ->(val, digest) { digest << 'Array'.freeze val.each do |element| ADD_VALUE_TO_DIGEST[element.class].call(element, digest) end },