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

Db953d125f5cc49756edb6149f1b813e?s=128

Richard Schneeman

November 18, 2016
Tweet

Transcript

  1. 1.
  2. 2.
  3. 8.
  4. 11.
  5. 13.
  6. 18.
  7. 19.
  8. 26.
  9. 27.

    BTW

  10. 29.
  11. 32.
  12. 33.
  13. 34.

    is

  14. 35.
  15. 36.
  16. 37.
  17. 44.

    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
  18. 45.

    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
  19. 46.

    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
  20. 48.

    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
  21. 49.

    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
  22. 50.

    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
  23. 51.

    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
  24. 52.

    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
  25. 53.

    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
  26. 54.

    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
  27. 57.

    $ 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
  28. 58.

    $ 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
  29. 60.

    $ 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
  30. 61.

    $ 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
  31. 62.

    $ 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
  32. 68.
  33. 70.

    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
  34. 72.

    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
  35. 73.
  36. 74.
  37. 75.

    /** @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); } }
  38. 76.

    /** @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); } }
  39. 77.
  40. 78.

    /** @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); }
  41. 79.

    /** @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); }
  42. 81.

    BTW

  43. 83.

    /** @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
  44. 86.
  45. 88.
  46. 91.
  47. 93.

    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
  48. 98.
  49. 99.
  50. 101.
  51. 102.

    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
  52. 103.

    $ 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
  53. 104.

    $ 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
  54. 105.

    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'
  55. 107.
  56. 114.

    if String elsif Symbol elsif Fixnum elsif Bignum elsif TrueClass

    elsif FalseClass elsif NilClass elsif Array
  57. 115.

    if String elsif Symbol elsif Fixnum elsif Bignum elsif TrueClass

    elsif FalseClass elsif NilClass elsif Array elsif Hash
  58. 116.

    if String elsif Symbol elsif Fixnum elsif Bignum elsif TrueClass

    elsif FalseClass elsif NilClass elsif Array elsif Hash elsif Set
  59. 117.

    if String elsif Symbol elsif Fixnum elsif Bignum elsif TrueClass

    elsif FalseClass elsif NilClass elsif Array elsif Hash elsif Set elsif Encoding
  60. 119.

    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
  61. 124.

    or

  62. 126.
  63. 127.

    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
  64. 128.
  65. 133.

    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 },
  66. 134.

    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 },
  67. 135.

    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 },
  68. 136.

    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 },
  69. 137.
  70. 139.
  71. 150.
  72. 155.
  73. 156.
  74. 158.
  75. 159.
  76. 160.
  77. 161.
  78. 163.
  79. 164.
  80. 165.
  81. 166.
  82. 169.
  83. 176.
  84. 185.
  85. 190.
  86. 197.
  87. 198.
  88. 199.
  89. 200.