$30 off During Our Annual Pro Sale. View Details »

Rinline: An inline expansion optimizar for Ruby

Rinline: An inline expansion optimizar for Ruby

pocke

May 22, 2019
Tweet

More Decks by pocke

Other Decks in Programming

Transcript

  1. Rinline: An inline expansion
    optimizar for Ruby
    RubyちこくKaigi
    22nd, May. 2019
    https://drecom.connpass.com/event/128107/

    View Slide

  2. Agenda
    I created an optimizer for Ruby with inline expansion.

    View Slide

  3. Agenda
    ● What is inline expansion
    ● What is Rinline
    ● How Rinline works
    ● The future of Rinline

    View Slide

  4. What is inline expansion

    View Slide

  5. What is inline expansion
    ● Replacing method call with body of the called
    method.
    ● It reduce overhead of method call.

    View Slide

  6. Inline expansion example
    # Before
    def foo
    bar
    end
    def bar
    puts 42
    end
    # After
    def foo
    puts 42
    end

    View Slide

  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

    View Slide

  8. What is Rinline

    View Slide

  9. Rinline
    ● Rinline is a PoC of inline expansion optimizer for
    Ruby
    ● https://github.com/pocke/rinline
    ● It makes Ruby faster!
    ○ (I hope)

    View Slide

  10. Usage
    Rinline.optimize do |r|
    r.optimize_instance_method(Klass, :method_name)
    end

    View Slide

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

    View Slide

  12. How Rinline works

    View Slide

  13. Data flow

    View Slide

  14. Data flow
    ● Method name → UnboundMethod →
    RubyVM::AST::Node → String of code → Inline
    expanded code → Ruby program by eval

    View Slide

  15. Data flow example
    def foo
    bar
    end
    def bar
    1
    end

    View Slide

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

    View Slide

  17. UnboundMethod → RubyVM::AST::Node
    ● Use RubyVM::AST.of
    node = RubyVM::AbstractSyntaxTree.of(m)
    # =>
    #3>

    View Slide

  18. RubyVM::AST::Node → String of Code
    ● RubyVM::AST::Node#to_source
    ○ It defined by AstExt (refinements patch)

    View Slide

  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

    View Slide

  20. String of code → Inline expanded code
    ● Replacing method call with called method body
    ● I will talk it on the next section!

    View Slide

  21. Inline expanded code → Ruby program by eval
    ● Use class_eval
    Object.class_eval(inline_expanded_code)

    View Slide

  22. Replacing method
    call

    View Slide

  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

    View Slide

  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

    View Slide

  25. How to replace
    ● RubyVM::AST::Node → … → Replaced code

    View Slide

  26. How to replace
    ● RubyVM::AST::Node → VCALL/FCALL Node →
    UnboundMethod → RubyVM::AST::Node → String of
    called method → lvar renamed code → Replaced
    code

    View Slide

  27. Example
    def foo
    x = 1
    bar
    p x * 3
    end
    def bar
    x = 21
    p x * 2
    end

    View Slide

  28. RubyVM::AST::Node → VCALL/FCALL Node
    ● Traverse
    ● Find VCALL / FCALL

    View Slide

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

    View Slide

  30. Find VCALL / FCALL
    def foo
    x = 1
    bar # ← VCALL node
    p x * 3
    end

    View Slide

  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)

    View Slide

  32. RubyVM::AST::Node → String of called method
    def_src = method_def_node.to_source

    View Slide

  33. String of called method → lvar renamed code
    ● Before embed, renaming lvar is needed

    View Slide

  34. Example
    def foo
    x = 1
    bar
    p x * 3 # => 3
    end
    def bar
    x = 21
    p x * 2 # => 42
    end

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  38. lvar renamed code → Replaced code
    def foo
    x = 1
    ((x_XXX = 21
    p x_XXX * 2))
    p x * 3
    end

    View Slide

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

    View Slide

  40. Why braces
    bar * 3 # => 9
    1 + 2 * 3 # => 7
    (1 + 2) * 3 # => 9
    def bar
    1 + 2
    end

    View Slide

  41. Why double braces
    p bar def bar
    1; 2
    end

    View Slide

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

    View Slide

  43. The future of Rinline

    View Slide

  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

    View Slide

  45. Fix many bugs
    ● Rinline generates invalid code
    ○ For example: It breaks on define_method

    View Slide

  46. Support super
    ● Currently, Rinline just ignores `super` node
    ● It can support `super` with
    UnboundMethod#super_method
    ○ https://twitter.com/udzura/status/11303947244
    58565633

    View Slide

  47. Conclusion

    View Slide

  48. Conclusion
    ● RubyVM::AST.of is useful for “Dark Power”
    ● Rinline is a PoC of inline expansion for Ruby

    View Slide

  49. pp self
    ● Masataka “pocke” Kuwabara
    ● Work for Bit Journey, Inc.
    Thank you for listening!

    View Slide