Digesting MRI by Studying Alternative Ruby Implementations

Digesting MRI by Studying Alternative Ruby Implementations

Pointers, managing memory and static typing - writing C code is hard! However, most programming languages, including Matz's Ruby Interpreter (MRI), are implemented in a low level programming language. So you think without knowing these concepts, you can not contribute to Ruby? Wrong! Although MRI is implemented in C, fortunately there are Ruby's in Java, Go and even Ruby itself.

If you ever wanted to learn about Ruby internals without being a C expert, this talk is for you. Join me on my journey of re-implementing hash maps in JRuby, breaking bundler and actually learn to write (some) C code.

Efa305e8e1b4eca01dd9f5c8bea4294b?s=128

Christian Bruckmayer

September 29, 2019
Tweet

Transcript

  1. https://www.flickr.com/photos/altus Story time

  2. What defines a “Senior Developer”?

  3. Experience

  4. Leadership

  5. Mentor

  6. Communication

  7. “Masters” Programming

  8. “Masters” Ruby

  9. Digesting MRI by Studying Alternative Ruby Implementations

  10. Hello! I am 
 Christian Bruckmayer
 
 @bruckmayer
 bruckmayer.net

  11. Bristol

  12. None
  13. MRI Rubinius / Opal JRuby

  14. MRI / CRuby 1993 Programming language: C Known for: Reference

    implementation, Matz
  15. Ruby is a computer program

  16. None
  17. None
  18. array = [1,2,3,4,5,6,7,8,9] # => [1, 2, 3, 4, 5,

    6, 7, 8, 9] puts array.minmax # => [1, 9] puts [array.min, array.max] # => [1, 9]
  19. require 'benchmark-driver' Benchmark.driver do |x| x.prelude <<~RUBY arr = (1..1000).map

    { rand } RUBY x.report %{ arr.minmax } x.report %{ [arr.min, arr.max] } end
  20. Warming up -------------------------------------- arr.minmax 36.970k i/s - 40.436k times in

    1.093742s (27.05μs/i) [arr.min, arr.max] 67.366k i/s - 72.435k times in 1.075249s (14.84μs/i) Calculating ------------------------------------- arr.minmax 36.784k i/s - 110.910k times in 3.015196s (27.19μs/i) [arr.min, arr.max] 67.324k i/s - 202.097k times in 3.001869s (14.85μs/i) Comparison: [arr.min, arr.max] : 67323.7 i/s arr.minmax : 36783.7 i/s - 1.83x slower
  21. None
  22. None
  23. None
  24. Embrace Failure

  25. Challenged

  26. Inspired

  27. MRI Rubinius / Opal JRuby

  28. Rubinius 2006 Programming language: Ruby, C, C++ Known for: Implemented

    as much as possible in Ruby
  29. Opal 2010 Programming language: Java Script, Ruby Known for: Ruby

    to Java Script, Ruby in the Browser
  30. None
  31. puts "Hello Indonesia".delete_prefix("Hello ") # Indonesia

  32. def delete_prefix(prefix) prefix = Rubinius::Type.check_convert_type prefix, String, :to_str return self[prefix.size..-1]

    if self.start_with?(prefix) dup end Rubinius
  33. def delete_prefix(prefix) %x{ if (!prefix.$$is_string) { #{prefix = Opal.coerce_to(prefix, String,

    :to_str)} } if (self.slice(0, prefix.length) === prefix) { return self.$$cast(self.slice(prefix.length)); } else { return self; } } end Opal
  34. “Hello Indonesia".delete_prefix(:Hello) # TypeError (no implicit conversion of Symbol into

    String)
  35. def delete_prefix(prefix) prefix = Rubinius::Type.check_convert_type prefix, String, :to_str return self[prefix.size..-1]

    if self.start_with?(prefix) dup end Rubinius
  36. “Hello Indonesia".delete_prefix(:Hello) # TypeError (no implicit conversion of Symbol into

    String) :Hello.to_s # "foo" :Hello.to_str # NoMethodError (undefined method `to_str' for :foo:Symbol)
  37. vs prefix = :Hello.to_s “Hello ID”.delete_prefix(prefix) class Prefix def to_str

    “Hello" end end “Hello ID”. delete_prefix(Prefix.new) Explicit Implicit
  38. class Path def to_str "/chris" end end puts "home" +

    Path.new # home/chris
  39. class Path def to_s "/chris" end end puts "home" +

    Path.new # Traceback (most recent call last): # `+': no implicit conversion of Path into String (TypeError)
  40. None
  41. None
  42. Three Lines of Code

  43. MRI Rubinius / Opal JRuby

  44. JRuby 2001 Programming language: Java Known for: JVM, Concurrency, Fast

  45. It aims to be a complete, correct and fast implementation

    of Ruby
  46. None
  47. 40 % Faster

  48. None
  49. Rubinius 2006 Programming language: Ruby, C, C++ Known for: Implemented

    as much as possible in Ruby
  50. None
  51. /* Find an entry with KEY in table TAB. Return

    non-zero if we found it. Set up *RESULT to the found table entry key. */ int st_get_key(st_table *tab, st_data_t key, st_data_t *result) { st_index_t bin; st_hash_t hash = do_hash(key, tab); rb_p(key); .... }
  52. make: *** [encdb.h] Error 1 make: *** Waiting for unfinished

    jobs.... ../ruby/tool/transform_mjit_header.rb:5: [BUG] Segmentation fault at 0x0000000000000093 ruby 2.7.0dev (2019-09-03T19:17:53Z implement-ary-minmax ebebc4b80d) [x86_64-darwin18] -- Crash Report log information -------------------------------------------- See Crash Report log file under the one of following: * ~/Library/Logs/DiagnosticReports * /Library/Logs/DiagnosticReports for more details. Don't forget to include the above Crash Report log file in bug reports.
  53. None
  54. hash = {} hash[:key] = “value" hash[:key] # "value"

  55. class Hash def initialize @bins = Array.new(7) { [] }

    end private attr_reader :bins end Separate Chaining
  56. class Hash def initialize @bins = Array.new(7) { [] }

    end private attr_reader :bins end Separate Chaining
  57. def index(key) key.hash % bins.length end Separate Chaining

  58. def bin(key) bins[index(key)] end def index(key) key.hash % bins.length end

    Separate Chaining
  59. def find(key) bin(key).find do |entry| entry.key == key end end

    Separate Chaining
  60. def []=(key, value) if find(key) find(key).value = value else bin(key)

    << Entry.new(key, value) end end class Entry < Struct.new(:key, :value) end Separate Chaining
  61. Cache Locality

  62. If a particular storage location is referenced […], then it

    is likely that nearby memory locations will be referenced in the near future. Wikipedia
  63. [1,2,3,4,5,6,7,8,9].each do |i| puts i end

  64. class Hash def initialize @bins = Array.new(7) { [] }

    end private attr_reader :bins end Open Addressing
  65. def bin(key) index = index(key) entry = bins[index] while entry

    != nil && entry.key != key index += 1 entry = bins[index] end index end Open Addressing
  66. def []=(key, value) if find(key) find(key).value = value else bins[bin(key)]

    = Entry.new(key, value) end end Open Addressing
  67. None
  68. Refactoring

  69. The Pragmatic Programmer “Different languages solve the same problems in

    different ways”
  70. Release Early, Release Often

  71. def []=(key, value) if find(key) find(key).value = value else bins[bin(key)]

    = Entry.new(key, value) end end class Entry < Struct.new(:key, :value) end Open Addressing
  72. def []=(key, value) if find(key) find(key).value = value else index

    = bin(key) bins[index] = key bins[index + 1] = value end end Open Addressing
  73. None
  74. None
  75. 2 months

  76. +682 LOC - 426 LOC

  77. None
  78. None
  79. Prototype

  80. Ask for Help

  81. MRI Rubinius / Opal JRuby

  82. What defines a “Senior Developer”?

  83. “Masters” Ruby

  84. Never stop learning

  85. Community

  86. Thank You!
 
 @bruckmayer
 bruckmayer.net