Rinline: An inline expansion optimizar for Ruby

Rinline: An inline expansion optimizar for Ruby

7bc6612fa20296bf652f6b0357db81c1?s=128

pocke

May 22, 2019
Tweet

Transcript

  1. Rinline: An inline expansion optimizar for Ruby RubyちこくKaigi 22nd, May.

    2019 https://drecom.connpass.com/event/128107/
  2. Agenda I created an optimizer for Ruby with inline expansion.

  3. Agenda • What is inline expansion • What is Rinline

    • How Rinline works • The future of Rinline
  4. What is inline expansion

  5. What is inline expansion • Replacing method call with body

    of the called method. • It reduce overhead of method call.
  6. Inline expansion example # Before def foo bar end def

    bar puts 42 end # After def foo puts 42 end
  7. Benchmark • `m + n` vs `1 + 2` ◦

    def m() 1 end; def n() 2 end • `m + n`: 5.71s ◦ x1.56 slower • `1 + 2`: 3.66s
  8. What is Rinline

  9. Rinline • Rinline is a PoC of inline expansion optimizer

    for Ruby • https://github.com/pocke/rinline • It makes Ruby faster! ◦ (I hope)
  10. Usage Rinline.optimize do |r| r.optimize_instance_method(Klass, :method_name) end

  11. DEMO See https://github.com/pocke/rinline/blob/master/benchma rk/simple.rb

  12. How Rinline works

  13. Data flow

  14. Data flow • Method name → UnboundMethod → RubyVM::AST::Node →

    String of code → Inline expanded code → Ruby program by eval
  15. Data flow example def foo bar end def bar 1

    end
  16. Method name → UnboundMethod • Use Module#instance_method m = Klass.instance_method(:foo)

    # => #<UnboundMethod: Klass#foo>
  17. UnboundMethod → RubyVM::AST::Node • Use RubyVM::AST.of node = RubyVM::AbstractSyntaxTree.of(m) #

    => #<RubyVM::AbstractSyntaxTree::Node:SCOPE@1:0-3: 3>
  18. RubyVM::AST::Node → String of Code • RubyVM::AST::Node#to_source ◦ It defined

    by AstExt (refinements patch)
  19. RubyVM::AST::Node → String of Code • Use File.binread path =

    m.source_location[0] range = node.first_index..node.last_index source = File.binread(path)[range] # => “def foo() bar() end” note: first|last_index are defined here https://github.com/pocke/rinline/blob/2cb6ffb055c619998973f5bd2a349fcb6599e95c/lib/rinline/ext/ast_ext.rb#L144-L161
  20. String of code → Inline expanded code • Replacing method

    call with called method body • I will talk it on the next section!
  21. Inline expanded code → Ruby program by eval • Use

    class_eval Object.class_eval(inline_expanded_code)
  22. Replacing method call

  23. Method call in Ruby • FCALL → method call with

    arguments ▪ foo(1, 2) • VCALL → method call without arguments ▪ foo • CALL → method call with receiver ▪ x.foo • OPCALL → method call with operator ▪ foo + bar
  24. Rinline supports FCALL and VCALL • Easy to support (Supporting

    CALL is difficult) ◦ CALL has a receiver, and receiver klass is unknown statically in most cases
  25. How to replace • RubyVM::AST::Node → … → Replaced code

  26. How to replace • RubyVM::AST::Node → VCALL/FCALL Node → UnboundMethod

    → RubyVM::AST::Node → String of called method → lvar renamed code → Replaced code
  27. Example def foo x = 1 bar p x *

    3 end def bar x = 21 p x * 2 end
  28. RubyVM::AST::Node → VCALL/FCALL Node • Traverse • Find VCALL /

    FCALL
  29. Traverse def traverse(&block) block.call(self) children.each do |c| c.traverse(&block) if c.is_a?(RubyVM::AST::Node)

    end end
  30. Find VCALL / FCALL def foo x = 1 bar

    # ← VCALL node p x * 3 end
  31. VCALL/FCALL Node → UnboundMethod → Method definition Node method_name =

    vcall_node.children[0] # => :bar method = Klass.instance_method(method_name) method_def_node = RubyVM::AST.of(method)
  32. RubyVM::AST::Node → String of called method def_src = method_def_node.to_source

  33. String of called method → lvar renamed code • Before

    embed, renaming lvar is needed
  34. Example def foo x = 1 bar p x *

    3 # => 3 end def bar x = 21 p x * 2 # => 42 end
  35. Example: It will be break! def foo x = 1

    x = 21 p x * 2 # => 42 p x * 3 # => 63 end def bar x = 21 p x * 2 end
  36. Fix local var collision def foo x = 1 x_XXX

    = 21 p x_XXX * 2 # => 42 p x * 3 # => 3 end def bar x = 21 p x * 2 end
  37. lvar renamed code → Replaced code def foo x =

    1 x_XXX = 21 p x_XXX * 2 p x * 3 end It is ok, but actually the replaced code will be enclosed with double braces
  38. lvar renamed code → Replaced code def foo x =

    1 ((x_XXX = 21 p x_XXX * 2)) p x * 3 end
  39. Why braces bar * 3 # => 9 def bar

    1 + 2 end
  40. Why braces bar * 3 # => 9 1 +

    2 * 3 # => 7 (1 + 2) * 3 # => 9 def bar 1 + 2 end
  41. Why double braces p bar def bar 1; 2 end

  42. Why double braces p (1; 2) # => SyntaxError! p

    ((1; 2)) # => Valid def bar 1; 2 end
  43. The future of Rinline

  44. Meaningful optimization • Currently it makes micro benchmark faster •

    But it does not make actual app faster ◦ For example, I tried to optimize optcarrot, but it does not change the FPS
  45. Fix many bugs • Rinline generates invalid code ◦ For

    example: It breaks on define_method
  46. Support super • Currently, Rinline just ignores `super` node •

    It can support `super` with UnboundMethod#super_method ◦ https://twitter.com/udzura/status/11303947244 58565633
  47. Conclusion

  48. Conclusion • RubyVM::AST.of is useful for “Dark Power” • Rinline

    is a PoC of inline expansion for Ruby
  49. pp self • Masataka “pocke” Kuwabara • Work for Bit

    Journey, Inc. Thank you for listening!