Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Helix: Native Extensions for Everyone - RubyCon...

hone
September 08, 2017

Helix: Native Extensions for Everyone - RubyConf Colombia 2017

You can write a native extension for Ruby using Rust through Helix. With Helix your code will be screaming fast, while ensuring thread and memory safety and preventing segfaults. Fast, reliable, and productive. Come learn how we can have our cake and eat it too!

Writing Faster Ruby: https://speakerdeck.com/sferik/writing-fast-ruby

hone

September 08, 2017
Tweet

More Decks by hone

Other Decks in Programming

Transcript

  1. Ruby is Slow • Usually it doesn't matter • Most

    workloads are I/O bound • But occasionally it does...
  2. "Best of both worlds" • Native extensions • JSON gem,

    nokogiri gem, etc…. • stdlib too: Date, Pathname, etc... • Very fast • Transparent to the user
  3. Case Study: Discourse • String#blank? Is a hotspot for Discourse

    • Sam Saffron's fast_blank • 50 LoC in C • 20x speedup of String#blank?
  4. But C... • Unsafe • Risky - segfaults! • Maintenance

    burden • Contribution barrier • Cross Platform Support
  5. Case Study #2: New Relic RPM • Monitoring agent built

    in Ruby • Hard to minimize overhead blog.scoutapp.com/articles/2016/02/07/overhead-benchmarks-new-relic-vs-scout
  6. Case Study #3: Skylight Agent • Started with Ruby •

    Too much overhead • Native extension! • But C… • AppSignal has written parts of their agent in Rust too
  7. Meet Rust • Like C: compiled, statically typed, very fast

    • Unlike C: enjoyable to use, guarantees safety • "If it compiles, it doesn't crash" • Same guarantee as Ruby with memory safety • Multiplatform Support
  8. Cargo.toml [package] name = "hello_world" version = "0.1.0" authors =

    ["Your Name <[email protected]>"] [dependencies] time = "0.1.12" regex = "0.1.41"
  9. cargo build $ cargo build Updating registry `https://github.com/rust-lang/crates.io-index` Downloading memchr

    v0.1.5 Downloading libc v0.1.10 Downloading regex-syntax v0.2.1 Downloading memchr v0.1.5 Downloading aho-corasick v0.3.0 Downloading regex v0.1.41 Compiling memchr v0.1.5 Compiling libc v0.1.10 Compiling regex-syntax v0.2.1 Compiling memchr v0.1.5 Compiling aho-corasick v0.3.0 Compiling regex v0.1.41 Compiling hello_world v0.1.0 (file:///path/to/project/hello_world)
  10. Cargo.lock [root] name = "hello_world" version = "0.1.0" dependencies =

    [ "regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "aho-corasick" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", ]
  11. Rust Multiplatform Support https://blog.rust-lang.org/2016/05/13/rustup.html rustup - a toolchain manager for

    Rust. "rustup works the same on Windows as it does on Unix" - rustup README Targets: $ rustup target list | wc -l 36 Including: • x86_64-apple-darwin (installed) • x86_64-pc-windows-gnu (installed) • x86_64-unknown-linux-gnu (default) • x86_64-unknown-linux-musl (installed)
  12. Shipping Rust in Firefox https://hacks.mozilla.org/2016/07/shipping-rust-in-firefox/ "Starting with Firefox 48, Mozilla

    is shipping its first production Rust code, with more to come!" -Dave Herman, Director of Strategy at Mozilla Research
  13. Zero-cost abstractions™ • In Ruby: tension between abstractions and performance

    • hash.keys.each vs hash.each_key • array.shuffle.first vs array.sample • In Rust: no such tradeoff • Compiler is magic
  14. Zero-cost abstractions™ pub fn main() { let array = [1,2,3];

    let mut sum = 0; for i in 0..3 { sum += array[i] } println!("{:?}", sum); }
  15. Zero-cost abstractions™ pub fn main() { let array = [1,2,3];

    let sum = array.iter().fold(0, |sum, &val| sum + val); println!("{:?}", sum); }
  16. fast_blank in Rust* pub extern "C" in fast_blank(buf: Buf) ->

    bool { buf.as_slice().chars().all(|c| c.is_whitespace()) } * without boilerplate code
  17. fast_blank in Helix #[macro_use] extern crate helix; declare_types! { reopen

    class RubyString { def is_blank(self) -> bool { self.chars().all(|c| c.is_whitespace()) } } }
  18. The Vision • Keep writing the Ruby you love… •

    ...without the fear of eventually hitting a wall • Start with Ruby • Move to Helix when appropriate
  19. 3. Rails Generator $ bin/rails generate helix:crate text_transform Running via

    Spring preloader in process 24825 create crates/text_transform/Cargo.toml create crates/text_transform/src/lib.rs create crates/text_transform/text_transform.gemspec create crates/text_transform/Gemfile create crates/text_transform/lib/tasks/helix_runtime.rake create crates/text_transform/lib/text_transform.rb create crates/text_transform/.gitignore create crates/text_transform/Rakefile append crates/text_transform/Rakefile run bundle from "./crates/text_transform" append Gemfile
  20. 3. Rails Generator $ bin/rails generate helix:crate text_transform Running via

    Spring preloader in process 24825 create crates/text_transform/Cargo.toml create crates/text_transform/src/lib.rs create crates/text_transform/text_transform.gemspec create crates/text_transform/Gemfile create crates/text_transform/lib/tasks/helix_runtime.rake create crates/text_transform/lib/text_transform.rb create crates/text_transform/.gitignore create crates/text_transform/Rakefile append crates/text_transform/Rakefile run bundle from "./crates/text_transform" append Gemfile
  21. $ bundle exec rake build $ bundle exec irb >

    require 'text_transform' => true > TextTransform.hello Hello from text_transform! => nil
  22. 4. Implement flip method def flip(text: String) -> String {

    text.chars().rev().map(|char| { match char { '!' => '¡', '"' => '„', '&' => '⅋', '\'' => '‚', '(' => ')', ')' => '(', ',' => '‘', '.' => '˙', ... // Flip back ... _ => char, } }).collect() end
  23. 4. Implement flip method def flip(text: String) -> String {

    text.chars().rev().map(|char| { match char { '!' => '¡', '"' => '„', '&' => '⅋', '\'' => '‚', '(' => ')', ')' => '(', ',' => '‘', '.' => '˙', ... // Flip back ... _ => char, } }).collect() end
  24. 4. Implement flip method def flip(text: String) -> String {

    text.chars().rev().map(|char| { match char { '!' => '¡', '"' => '„', '&' => '⅋', '\'' => '‚', '(' => ')', ')' => '(', ',' => '‘', '.' => '˙', ... // Flip back ... _ => char, } }).collect() end
  25. crates/text_transform/spec/text_transform.rb require "text_transform" describe "TextTransform" do it "can flip text"

    do expect(TextTransform.flip("Hello Aaron (@tenderlove)!")).to eq("¡(ǝʌolɹǝpuǝʇ@) uoɹɐ∀ ollǝH") end it "can flip the text back" do expect(TextTransform.flip("¡(ǝʌolɹǝpuǝʇ@) uoɹɐ∀ ollǝH")).to eq("Hello Aaron (@tenderlove)!") end end
  26. 5. Flipping Tables! def flip(text: String) -> String { if

    text == "┬──┬ ノ( ゜-゜ノ)" { return "(╯°□°)╯︵ ┻━┻".to_string(); } else if text == "(╯°□°)╯︵ ┻━┻" { return "┬──┬ ノ( ゜-゜ノ)".to_string(); } // rest of method … end
  27. 6. Using in Rails $ bin/rails generate helix:crate text_transform Running

    via Spring preloader in process 24825 create crates/text_transform/Cargo.toml create crates/text_transform/src/lib.rs create crates/text_transform/text_transform.gemspec create crates/text_transform/Gemfile create crates/text_transform/lib/tasks/helix_runtime.rake create crates/text_transform/lib/text_transform.rb create crates/text_transform/.gitignore create crates/text_transform/Rakefile append crates/text_transform/Rakefile run bundle from "./crates/text_transform" append Gemfile
  28. 6. Using in Rails (Controller) app/controllers/flips_controller.rb: class FlipsController < ApplicationController

    def index @text = params[:text] || "Hello world!" end def create @text = TextTransform.flip(params[:text]) render :index end end
  29. 6. Using in Rails (View) app/views/flips/index.html.erb: <h1>Flipper</h1> <%= form_tag do

    %> <%= text_field_tag :text, @text %> <%= submit_tag "Flip!" %> <% end %>
  30. 7. Deploy to Heroku Setup rustc on production machine $

    heroku create $ heroku buildpacks:add https://github.com/emk/heroku-buildpack-rust $ heroku buildpacks:add heroku/ruby $ git push heroku master
  31. 7. Deploy to Heroku Setup rustc on production machine $

    heroku create $ heroku buildpacks:add https://github.com/emk/heroku-buildpack-rust $ heroku buildpacks:add heroku/ruby $ git push heroku master
  32. Distribution (Precompile Binaries) • Use CI to build binaries and

    publish to rubygems ◦ Travis/CircleCI: Linux + OS X ◦ Appveyor: Windows • Don't need rust compiler on host machine • Next version of helix CLI will generate CI config ◦ helix bootstrap hello_helix ◦ Add env var RUBYGEMS_AUTH_TOKEN to CI services to publish
  33. 8. Deploy to Heroku $ heroku create $ git push

    heroku master -----> Ruby app detected -----> Compiling Ruby/Rails -----> Using Ruby version: ruby-2.3.4 -----> Installing dependencies using bundler 1.15.2 … Installing text_transform 0.1.0 (x86_64-linux)
  34. Good use cases • CPU-bound • Simple inputs ◦ JSON

    ◦ Filenames ◦ data tables • Avoid chatty APIs (boundary cost)
  35. Good use cases ruby! { class WordCount { def search(path:

    String, search: String) -> i32 { let mut file = File::open(path).expect("could not open file."); // … lines.into_par_iter(). .map(|line| wc_line(line, search)) .sum() } } }
  36. Good use cases • Use Rust libraries • Leverage Rust

    web browser tech • Mailer, Background job, Action Cable
  37. ruby! { class InlineCSS { def inline(html: String, css: String)

    -> String { let doc = kuchiki::parse_html().one(html); let mut parser = parse_css::CSSParser::new(css); let rules = ...; for rule in rules { let matching_elements = ...; for matching_element in matching_elements { // insert style attribute } } // serialize } } } Good use cases
  38. Roadmap • Greenfield project • Drop-in replacement • Reopen class

    • Ship to production • Non-traditional use-cases • Performance parity with C • Misc features and QoL improvements