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

Crystal & Ruby - Applying learnings from a compiled language

Crystal & Ruby - Applying learnings from a compiled language

We all enjoy the flexibility and expressiveness that languages like Ruby give us, but such flexibility is also paired with the unknown. Dealing with dynamic data and runtime conditions can lead to unknown scenarios and runtime errors.

Compiled languages, specially today offer a fresh perspective and help us developers better understand and shape our code.

This talk aims to discover certain techniques transferred from compiled languages’ realm and transfer those to dynamic languages like Ruby. Will talk from my experience working on mid-size Ruby projects (8K+ LOC).

Presented at Paris.rb Conference, June 2018

Luis Lavena

June 28, 2018
Tweet

More Decks by Luis Lavena

Other Decks in Programming

Transcript

  1. Crystal & Ruby
    Applying learnings from a compiled language

    View Slide

  2. Luis Lavena
    Twitter: @luislavena
    GitHub: luislavena

    View Slide

  3. View Slide

  4. View Slide

  5. area17.com

    View Slide

  6. WARNING: Technical talk ahead
    Important announcement follows...

    View Slide

  7. vs.

    View Slide

  8. View Slide

  9. Compiled vs. Interpreted

    View Slide

  10. Ruby VM
    require
    Read source
    Tokenize (lexer)
    Build AST (parser)
    Build ISeq
    Execute ISeq
    Ruby

    View Slide

  11. OS
    Read source
    Tokenize (lexer)
    Build AST (parser)
    Codegen
    Execute
    Native Code (obj)
    Link
    Compilers
    Analyze/Infer/Optimize

    View Slide

  12. View Slide

  13. View Slide

  14. View Slide

  15. Crystal Language
    ● Ruby-like syntax
    ● Types and Inference
    ● Generics
    ● Macros
    ● Self-hosted
    ● LLVM-powered
    ● Native, single-executable

    View Slide

  16. Crystal Language
    ● Ruby-like syntax
    ● Types and Inference
    ● Generics
    ● Macros
    ● Self-hosted
    ● LLVM-powered
    ● Native, single-executable

    View Slide

  17. Types and Inference

    View Slide

  18. Types and Inference
    a = [1, 2, 3].map { |x| x + 3 }
    pp a # => a = [4, 5, 6]
    pp typeof(a) # => Array(Int32)
    b = [1, "a", 'b']
    pp typeof(b) # => Array(String | Int32 | Char)
    # b << 12.5 # => Compile error (Float32)

    View Slide

  19. Methods and inference
    def add(one, two)
    one + two
    end
    add 30, 12 # Int32, Int32 => Int32
    add "a", "b" # String, String => String
    add "a", 'b' # Compile error

    View Slide

  20. Methods and inference
    def work(worker)
    worker.something!
    end
    work Worker.new # OK
    work nil # Compile error (no method `something!` for `nil`)

    View Slide

  21. Methods and inference
    def work(worker)
    if worker
    worker.something!
    end
    end
    work Worker.new # OK
    work nil # => nil

    View Slide

  22. Methods & signatures
    def add(one : Int32, two : Int32)
    one + two
    end
    add 30, 12 # Int32, Int32 => Int32
    add "a", "b" # Compile error

    View Slide

  23. Ruby
    ● Types syntax will be hard to implement
    ● Probably method signatures will be added in Ruby 3x3
    ● Type checkers possible (ie. Sorbet by Stripe)
    https://sorbet.run/

    View Slide

  24. View Slide

  25. Code techniques

    View Slide

  26. Code techniques
    Acknowledge that nil exists

    View Slide

  27. def work(option)
    return option + 10
    end
    work 5 # => 15
    work nil # => Runtime Exception
    def work(option)
    if option
    return option + 10
    end
    end
    work 5 # => 15
    work nil # => nil
    Acknowledge that nil exists

    View Slide

  28. Code techniques
    Acknowledge that nil exists ✔
    Accessing variables across threads

    View Slide

  29. class Worker
    def work
    if @condition
    @condition.something!
    end
    end
    end
    class Worker
    def work
    if value = @condition
    value.something!
    end
    end
    end
    Accessing variables across threads

    View Slide

  30. Code techniques
    Acknowledge that nil exists ✔
    Accessing variables across threads ✔

    View Slide

  31. Coding resources
    ● Ruby typechecker: Sorbet
    https://sorbet.run/

    View Slide

  32. Optimization techniques

    View Slide

  33. Optimization techniques
    Load time

    View Slide

  34. require
    Read source
    Tokenize (lexer)
    Build AST (parser)
    Build ISeq
    Execute ISeq Ruby VM
    Memory
    I/O

    View Slide

  35. $ cat example.rb
    a = 30
    puts a + 12
    $ ruby -e 'File.binwrite("example.yarb",
    RubyVM::InstructionSequence.compile_file("example.rb").to_
    binary)'
    $ ls -l
    total 4
    -rw-r--r-- 1 luis luis 19 Jun 28 10:07 example.rb
    -rw-r--r-- 1 luis luis 818 Jun 28 10:07 example.yarb

    View Slide

  36. $ ruby -e
    'RubyVM::InstructionSequence.load_from_binary(File.binread
    ("example.yarb")).eval'
    42

    View Slide

  37. require
    Read source
    Tokenize (lexer)
    Build AST (parser)
    Build ISeq
    Execute ISeq
    Ruby VM
    I/O
    Read bytecode

    View Slide

  38. Bootsnap
    ● Compiles .rb into YARB bytecode
    ● Hooks require and loads instead of parsing
    ● Optimizes lookup of $LOAD_PATH

    View Slide

  39. ~35%
    Load time speed up using Bootsnap
    Mid-size Rails app (~6k LOC)

    View Slide

  40. Optimization techniques
    Load time ✔

    View Slide

  41. Optimization techniques
    Load time ✔
    Installation time and dependencies

    View Slide

  42. View Slide

  43. UPDATING NOKOGIRI!!!

    View Slide

  44. But, what if there is an alternative?

    View Slide

  45. $ time gem install nokogiri-1.8.3.gem

    real 5m34.507s
    $ gem compile nokogiri-1.8.3.gem

    $ time gem install nokogiri-1.8.3-x86_64-linux.gem

    1 gem installed
    real 0m4.947s

    View Slide

  46. gem-compiler
    ● RubyGems plugin (`gem compile ...`)
    ● Pre-compile gems for the current platform
    ● Plays nice with Bundler
    (`bundle package` and `bundle install --local`)
    ● Reduces install time
    (compile nokogiri from ~5 mins to ~4 seconds)
    ● Reduces bandwidth usage
    (nokogiri from ~8.8MB to ~700KB)

    View Slide

  47. Optimization techniques
    Load time ✔
    Installation time and dependencies ✔
    Code-reviews and code style

    View Slide

  48. “This PR is excellent! Event the
    inconsistent style and spaces used for
    your hashes, the lack of parenthesis on
    methods and let’s not forget the usage of
    tabs!”
    - Anonymous Ruby developer on GitHub

    View Slide

  49. Formatting code
    ● Reduces bikeshedding
    ● Consistent readability following official styleguides
    ● Improves contribution
    ● Broad implementation and success on other languages:
    Golang, Rust, Elixir
    ● Proactive vs. Passive

    View Slide

  50. $ cat example.rb
    def hello name
    puts "Hello #{name}!!!"
    end
    $ rufo example.rb
    $ cat example.rb
    def hello(name)
    puts "Hello #{name}!!!"
    end
    rufo: Ruby formatter

    View Slide

  51. Optimization techniques
    Load time ✔
    Installation time and dependencies ✔
    Code-reviews and code style ✔

    View Slide

  52. Optimization resources
    ● Load time: Bootsnap
    http://github.com/Shopify/bootsnap
    ● Pre-compiled extensions: gem-compiler
    https://github.com/luislavena/gem-compiler
    ● Code formatting: rufo
    https://github.com/ruby-formatter/rufo

    View Slide

  53. View Slide

  54. Thank you!

    View Slide