Lock in $30 Savings on PRO—Offer Ends Soon! ⏳

Turbo Rails with Rust

Turbo Rails with Rust

Ruby is not the fastest language in the world, there is no doubt about it. This doesn't turn out to matter all that much – Ruby and its ecosystem has so much more to offer, making it a worthwhile tradeoff a lot of the times.

However, you might occasionally encounter workloads that are simply not suitable for Ruby. This is especially true for frameworks like Rails, where the overhead wants to be as little as possible.

In this talk, we will explore building a native Ruby extension with Rust to speed up parts of Rails. What does Rust have to offer here over plain-old C? Let's find out!

Godfrey Chan

May 06, 2016
Tweet

More Decks by Godfrey Chan

Other Decks in Programming

Transcript

  1. PHP

  2. C

  3. "Here’s a general value for Rails development: We will jump

    through hoops on the implementation to make the user-facing API nicer." David Heinemeier Hansson
  4. "Here’s a general value for Rails development: We will jump

    through hoops on the implementation to make the user-facing API nicer." David Heinemeier Hansson
  5. "Here’s a general value for Rails development: We will jump

    through hoops on the implementation to make the user-facing API nicer." David Heinemeier Hansson developer experience
  6. Rust is a systems programming language that runs blazingly fast,

    prevents segfaults, and guarantees thread safety.
  7. This is Rust. Rust has a compiler. Rust's compiler finds

    errors at compile- time. Rust's compiler guarantees the compiled program cannot crash at runtime. Be like Rust.
  8. static VALUE rb_str_blank(VALUE str) { rb_encoding *enc; char *s, *e;

    enc = STR_ENC_GET(str); s = RSTRING_PTR(str); if (!s || RSTRING_LEN(str) == 0) return Qtrue; e = RSTRING_END(str); while (s < e) { int n; unsigned int cc = rb_enc_codepoint_len(s, e, &n, enc); switch (cc) { case 9: case 0xa: case 0xb: case 0xc: case 0xd: case 0x20: case 0x85: case 0xa0: case 0x1680: case 0x2000: case 0x2001: case 0x2002: case 0x2003: case 0x2004: case 0x2005: case 0x2006: case 0x2007: case 0x2008: case 0x2009: case 0x200a: case 0x2028: case 0x2029: case 0x202f: case 0x205f: case 0x3000: /* found */ break; default: return Qfalse; } s += n; } return Qtrue; }
  9. static VALUE rb_str_blank(VALUE str) { rb_encoding *enc; char *s, *e;

    enc = STR_ENC_GET(str); s = RSTRING_PTR(str); if (!s || RSTRING_LEN(str) == 0) return Qtrue; e = RSTRING_END(str); while (s < e) { int n; unsigned int cc = rb_enc_codepoint_len(s, e, &n, enc); switch (cc) { case 9: case 0xa: case 0xb: case 0xc: case 0xd: case 0x20: case 0x85: case 0xa0: case 0x1680: case 0x2000: case 0x2001: case 0x2002: case 0x2003: case 0x2004: case 0x2005: case 0x2006: case 0x2007: case 0x2008: case 0x2009: case 0x200a: case 0x2028: case 0x2029: case 0x202f: case 0x205f: case 0x3000: /* found */ break; default: return Qfalse; } s += n; } return Qtrue; } static VALUE rb_str_blank(VALUE str) { }
  10. static VALUE rb_str_blank(VALUE str) { rb_encoding *enc; char *s, *e;

    enc = STR_ENC_GET(str); s = RSTRING_PTR(str); if (!s || RSTRING_LEN(str) == 0) return Qtrue; e = RSTRING_END(str); while (s < e) { int n; unsigned int cc = rb_enc_codepoint_len(s, e, &n, enc); switch (cc) { case 9: case 0xa: case 0xb: case 0xc: case 0xd: case 0x20: case 0x85: case 0xa0: case 0x1680: case 0x2000: case 0x2001: case 0x2002: case 0x2003: case 0x2004: case 0x2005: case 0x2006: case 0x2007: case 0x2008: case 0x2009: case 0x200a: case 0x2028: case 0x2029: case 0x202f: case 0x205f: case 0x3000: /* found */ break; default: return Qfalse; } s += n; } return Qtrue; } static VALUE rb_str_blank(VALUE str) { // …boilerplate… }
  11. static VALUE rb_str_blank(VALUE str) { rb_encoding *enc; char *s, *e;

    enc = STR_ENC_GET(str); s = RSTRING_PTR(str); if (!s || RSTRING_LEN(str) == 0) return Qtrue; e = RSTRING_END(str); while (s < e) { int n; unsigned int cc = rb_enc_codepoint_len(s, e, &n, enc); switch (cc) { case 9: case 0xa: case 0xb: case 0xc: case 0xd: case 0x20: case 0x85: case 0xa0: case 0x1680: case 0x2000: case 0x2001: case 0x2002: case 0x2003: case 0x2004: case 0x2005: case 0x2006: case 0x2007: case 0x2008: case 0x2009: case 0x200a: case 0x2028: case 0x2029: case 0x202f: case 0x205f: case 0x3000: /* found */ break; default: return Qfalse; } s += n; } return Qtrue; } static VALUE rb_str_blank(VALUE str) { // …boilerplate… if (!s || RSTRING_LEN(str) == 0) return Qtrue; }
  12. static VALUE rb_str_blank(VALUE str) { rb_encoding *enc; char *s, *e;

    enc = STR_ENC_GET(str); s = RSTRING_PTR(str); if (!s || RSTRING_LEN(str) == 0) return Qtrue; e = RSTRING_END(str); while (s < e) { int n; unsigned int cc = rb_enc_codepoint_len(s, e, &n, enc); switch (cc) { case 9: case 0xa: case 0xb: case 0xc: case 0xd: case 0x20: case 0x85: case 0xa0: case 0x1680: case 0x2000: case 0x2001: case 0x2002: case 0x2003: case 0x2004: case 0x2005: case 0x2006: case 0x2007: case 0x2008: case 0x2009: case 0x200a: case 0x2028: case 0x2029: case 0x202f: case 0x205f: case 0x3000: /* found */ break; default: return Qfalse; } s += n; } return Qtrue; } static VALUE rb_str_blank(VALUE str) { // …boilerplate… if (!s || RSTRING_LEN(str) == 0) return Qtrue; while(s < RSTRING_END(str)) { s++; } }
  13. static VALUE rb_str_blank(VALUE str) { rb_encoding *enc; char *s, *e;

    enc = STR_ENC_GET(str); s = RSTRING_PTR(str); if (!s || RSTRING_LEN(str) == 0) return Qtrue; e = RSTRING_END(str); while (s < e) { int n; unsigned int cc = rb_enc_codepoint_len(s, e, &n, enc); switch (cc) { case 9: case 0xa: case 0xb: case 0xc: case 0xd: case 0x20: case 0x85: case 0xa0: case 0x1680: case 0x2000: case 0x2001: case 0x2002: case 0x2003: case 0x2004: case 0x2005: case 0x2006: case 0x2007: case 0x2008: case 0x2009: case 0x200a: case 0x2028: case 0x2029: case 0x202f: case 0x205f: case 0x3000: /* found */ break; default: return Qfalse; } s += n; } return Qtrue; } static VALUE rb_str_blank(VALUE str) { // …boilerplate… if (!s || RSTRING_LEN(str) == 0) return Qtrue; while(s < RSTRING_END(str)) { // …boilerplate… s++; } }
  14. static VALUE rb_str_blank(VALUE str) { rb_encoding *enc; char *s, *e;

    enc = STR_ENC_GET(str); s = RSTRING_PTR(str); if (!s || RSTRING_LEN(str) == 0) return Qtrue; e = RSTRING_END(str); while (s < e) { int n; unsigned int cc = rb_enc_codepoint_len(s, e, &n, enc); switch (cc) { case 9: case 0xa: case 0xb: case 0xc: case 0xd: case 0x20: case 0x85: case 0xa0: case 0x1680: case 0x2000: case 0x2001: case 0x2002: case 0x2003: case 0x2004: case 0x2005: case 0x2006: case 0x2007: case 0x2008: case 0x2009: case 0x200a: case 0x2028: case 0x2029: case 0x202f: case 0x205f: case 0x3000: /* found */ break; default: return Qfalse; } s += n; } return Qtrue; } static VALUE rb_str_blank(VALUE str) { // …boilerplate… if (!s || RSTRING_LEN(str) == 0) return Qtrue; while(s < RSTRING_END(str)) { // …boilerplate… switch (s) { } s++; } }
  15. static VALUE rb_str_blank(VALUE str) { rb_encoding *enc; char *s, *e;

    enc = STR_ENC_GET(str); s = RSTRING_PTR(str); if (!s || RSTRING_LEN(str) == 0) return Qtrue; e = RSTRING_END(str); while (s < e) { int n; unsigned int cc = rb_enc_codepoint_len(s, e, &n, enc); switch (cc) { case 9: case 0xa: case 0xb: case 0xc: case 0xd: case 0x20: case 0x85: case 0xa0: case 0x1680: case 0x2000: case 0x2001: case 0x2002: case 0x2003: case 0x2004: case 0x2005: case 0x2006: case 0x2007: case 0x2008: case 0x2009: case 0x200a: case 0x2028: case 0x2029: case 0x202f: case 0x205f: case 0x3000: /* found */ break; default: return Qfalse; } s += n; } return Qtrue; } static VALUE rb_str_blank(VALUE str) { // …boilerplate… if (!s || RSTRING_LEN(str) == 0) return Qtrue; while(s < RSTRING_END(str)) { // …boilerplate… switch (cc) { case 9: case 0xa: … case 0x205f: case 0x3000: // whitespace break; } s++; } }
  16. static VALUE rb_str_blank(VALUE str) { rb_encoding *enc; char *s, *e;

    enc = STR_ENC_GET(str); s = RSTRING_PTR(str); if (!s || RSTRING_LEN(str) == 0) return Qtrue; e = RSTRING_END(str); while (s < e) { int n; unsigned int cc = rb_enc_codepoint_len(s, e, &n, enc); switch (cc) { case 9: case 0xa: case 0xb: case 0xc: case 0xd: case 0x20: case 0x85: case 0xa0: case 0x1680: case 0x2000: case 0x2001: case 0x2002: case 0x2003: case 0x2004: case 0x2005: case 0x2006: case 0x2007: case 0x2008: case 0x2009: case 0x200a: case 0x2028: case 0x2029: case 0x202f: case 0x205f: case 0x3000: /* found */ break; default: return Qfalse; } s += n; } return Qtrue; } static VALUE rb_str_blank(VALUE str) { // …boilerplate… if (!s || RSTRING_LEN(str) == 0) return Qtrue; while(s < RSTRING_END(str)) { // …boilerplate… switch (cc) { case 9: case 0xa: … case 0x205f: case 0x3000: // whitespace break; default: return Qfalse; } s++; } }
  17. static VALUE rb_str_blank(VALUE str) { rb_encoding *enc; char *s, *e;

    enc = STR_ENC_GET(str); s = RSTRING_PTR(str); if (!s || RSTRING_LEN(str) == 0) return Qtrue; e = RSTRING_END(str); while (s < e) { int n; unsigned int cc = rb_enc_codepoint_len(s, e, &n, enc); switch (cc) { case 9: case 0xa: case 0xb: case 0xc: case 0xd: case 0x20: case 0x85: case 0xa0: case 0x1680: case 0x2000: case 0x2001: case 0x2002: case 0x2003: case 0x2004: case 0x2005: case 0x2006: case 0x2007: case 0x2008: case 0x2009: case 0x200a: case 0x2028: case 0x2029: case 0x202f: case 0x205f: case 0x3000: /* found */ break; default: return Qfalse; } s += n; } return Qtrue; } static VALUE rb_str_blank(VALUE str) { // …boilerplate… if (!s || RSTRING_LEN(str) == 0) return Qtrue; while(s < RSTRING_END(str)) { // …boilerplate… switch (cc) { case 9: case 0xa: … case 0x205f: case 0x3000: // whitespace break; default: return Qfalse; } s++; } return Qtrue; }
  18. =========== Test String Length: 6 =========== Rust 11.043M (± 3.5%)

    i/s - 54.744M C 10.583M (± 8.5%) i/s - 52.464M Ruby 964.469k (±27.6%) i/s - 4.390M
  19. =========== Test String Length: 6 =========== Rust 11.043M (± 3.5%)

    i/s - 54.744M C 10.583M (± 8.5%) i/s - 52.464M Ruby 964.469k (±27.6%) i/s - 4.390M
  20. =========== Test String Length: 6 =========== Rust 11.043M (± 3.5%)

    i/s - 54.744M C 10.583M (± 8.5%) i/s - 52.464M Ruby 964.469k (±27.6%) i/s - 4.390M
  21. RUST C // Rust Code extern crate libc; use std::{ptr,

    slice}; use std::marker::PhantomData; #[repr(C)] #[allow(raw_pointer_derive)] #[derive(Copy, Clone)] pub struct Buf<'a> { ptr: *mut u8, len: usize, marker: PhantomData<&'a ()>, } impl<'a> Buf<'a> { pub fn as_slice(self) -> &'a str { unsafe { let s = slice::from_raw_parts(self.ptr as *const u8, self.len); std::str::from_utf8_unchecked(s) } } } #[no_mangle] pub extern "C" fn fast_blank(buf: Buf) -> bool { buf.as_slice().unwrap().chars().all(|c| c.is_whitespace()) } // C Code #include <ruby.h> #include <ruby/encoding.h> typedef struct { void* data; size_t len; } fast_blank_buf_t; static inline fast_blank_buf_t STR2BUF(VALUE str) { return (fast_blank_buf_t) { .data = RSTRING_PTR(str), .len = RSTRING_LEN(str), }; } int fast_blank(fast_blank_buf_t); static VALUE rb_string_blank_p(VALUE str) { return fast_blank(STR2BUF(str)) ? Qtrue : Qfalse; } void Init_fast_blank() { rb_define_method(rb_cString, "blank?", rb_string_blank_p, 0); } #include <stdio.h> #include <ruby.h> #include <ruby/encoding.h> #include <ruby/re.h> #define STR_ENC_GET(str) rb_enc_from_index(ENCODING_GET(str)) static VALUE rb_string_blank(VALUE str) { rb_encoding *enc; char *s, *e; enc = STR_ENC_GET(str); s = RSTRING_PTR(str); if (!s || RSTRING_LEN(str) == 0) return Qtrue; e = RSTRING_END(str); while (s < e) { int n; unsigned int cc = rb_enc_codepoint_len(s, e, &n, enc); switch (cc) { case 9: case 0xa: case 0xb: case 0xc: case 0xd: case 0x20: case 0x85: case 0xa0: case 0x1680: case 0x2000: case 0x2001: case 0x2002: case 0x2003: case 0x2004: case 0x2005: case 0x2006: case 0x2007: case 0x2008: case 0x2009: case 0x200a: case 0x2028: case 0x2029: case 0x202f: case 0x205f: case 0x3000: break; default: return Qfalse; } s += n; } return Qtrue; } void Init_fast_blank() { rb_define_method(rb_cString, "blank?", rb_string_blank, 0); }
  22. RUST C // Rust Code extern crate libc; use std::{ptr,

    slice}; use std::marker::PhantomData; #[repr(C)] #[allow(raw_pointer_derive)] #[derive(Copy, Clone)] pub struct Buf<'a> { ptr: *mut u8, len: usize, marker: PhantomData<&'a ()>, } impl<'a> Buf<'a> { pub fn as_slice(self) -> &'a str { unsafe { let s = slice::from_raw_parts(self.ptr as *const u8, self.len); std::str::from_utf8_unchecked(s) } } } #[no_mangle] pub extern "C" fn fast_blank(buf: Buf) -> bool { buf.as_slice().unwrap().chars().all(|c| c.is_whitespace()) } // C Code #include <ruby.h> #include <ruby/encoding.h> typedef struct { void* data; size_t len; } fast_blank_buf_t; static inline fast_blank_buf_t STR2BUF(VALUE str) { return (fast_blank_buf_t) { .data = RSTRING_PTR(str), .len = RSTRING_LEN(str), }; } int fast_blank(fast_blank_buf_t); static VALUE rb_string_blank_p(VALUE str) { return fast_blank(STR2BUF(str)) ? Qtrue : Qfalse; } void Init_fast_blank() { rb_define_method(rb_cString, "blank?", rb_string_blank_p, 0); } #include <stdio.h> #include <ruby.h> #include <ruby/encoding.h> #include <ruby/re.h> #define STR_ENC_GET(str) rb_enc_from_index(ENCODING_GET(str)) static VALUE rb_string_blank(VALUE str) { rb_encoding *enc; char *s, *e; enc = STR_ENC_GET(str); s = RSTRING_PTR(str); if (!s || RSTRING_LEN(str) == 0) return Qtrue; e = RSTRING_END(str); while (s < e) { int n; unsigned int cc = rb_enc_codepoint_len(s, e, &n, enc); switch (cc) { case 9: case 0xa: case 0xb: case 0xc: case 0xd: case 0x20: case 0x85: case 0xa0: case 0x1680: case 0x2000: case 0x2001: case 0x2002: case 0x2003: case 0x2004: case 0x2005: case 0x2006: case 0x2007: case 0x2008: case 0x2009: case 0x200a: case 0x2028: case 0x2029: case 0x202f: case 0x205f: case 0x3000: break; default: return Qfalse; } s += n; } return Qtrue; } void Init_fast_blank() { rb_define_method(rb_cString, "blank?", rb_string_blank, 0); }
  23. #[macro_use] extern crate libcruby; declare_types! { reopen class RubyString {

    def is_blank(self) -> bool { self.chars().all(|c| c.is_whitespace()) } } }
  24. #[macro_use] extern crate libcruby; declare_types! { reopen class RubyString {

    def is_blank(self) -> bool { self.chars().all(|c| c.is_whitespace()) } } }
  25. #[macro_use] extern crate libcruby; declare_types! { reopen class RubyString {

    def is_blank(self) -> bool { self.chars().all(|c| c.is_whitespace()) } } }
  26. #[macro_use] extern crate libcruby; declare_types! { reopen class RubyString {

    def is_blank(self) -> bool { self.chars().all(|c| c.is_whitespace()) } } }
  27. #[macro_use] extern crate libcruby; declare_types! { reopen class RubyString {

    def is_blank(self) -> bool { self.chars().all(|c| c.is_whitespace()) } } }
  28. meal_tags = [3, 6, 18, 34, 50] preference_tags = [6,

    18, 42] meal_tags.fully_contain?(preference_tags)
  29. meal_tags = [3, 6, 18, 34, 50] preference_tags = [6,

    18, 42] meal_tags.fully_contain?(preference_tags)
  30. meal_tags = [3, 6, 18, 34, 50] preference_tags = [6,

    18, 42] meal_tags.fully_contain?(preference_tags)
  31. meal_tags = [3, 6, 18, 34, 50] preference_tags = [6,

    18, 42] meal_tags.fully_contain?(preference_tags)
  32. (

  33. class Array def fully_contain?(needle) return true if needle.empty? return false

    if self.empty? needle_length = needle.length return false if needle_length > self.length needle_position = 0 needle_item = needle[needle_position] self.each do |item| if item == needle_item needle_position += 1 if needle_position >= needle_length return true else needle_item = needle[needle_position] end end end false end end
  34. class Array def fully_contain?(needle) return true if needle.empty? return false

    if self.empty? needle_length = needle.length return false if needle_length > self.length needle_position = 0 needle_item = needle[needle_position] self.each do |item| if item == needle_item needle_position += 1 if needle_position >= needle_length return true else needle_item = needle[needle_position] end end end false end end
  35. class Array def fully_contain?(needle) return true if needle.empty? return false

    if self.empty? needle_length = needle.length return false if needle_length > self.length needle_position = 0 needle_item = needle[needle_position] self.each do |item| if item == needle_item needle_position += 1 if needle_position >= needle_length return true else needle_item = needle[needle_position] end end end false end end
  36. class Array def fully_contain?(needle) return true if needle.empty? return false

    if self.empty? needle_length = needle.length return false if needle_length > self.length needle_position = 0 needle_item = needle[needle_position] self.each do |item| if item == needle_item needle_position += 1 if needle_position >= needle_length return true else needle_item = needle[needle_position] end end end false end end
  37. class Array def fully_contain?(needle) return true if needle.empty? return false

    if self.empty? needle_length = needle.length return false if needle_length > self.length needle_position = 0 needle_item = needle[needle_position] self.each do |item| if item == needle_item needle_position += 1 if needle_position >= needle_length return true else needle_item = needle[needle_position] end end end false end end
  38. #[macro_use] extern crate libcruby; declare_types! { reopen class Array {

    def fully_contain(self, needle: &[usize]) -> bool { if needle.is_empty() { return true } let haystack = self.as_ref(); if haystack.is_empty() { return false } let mut needle = needle.iter(); let mut needle_item = needle.next().unwrap(); for item in haystack { if item == needle_item { match needle.next() { None => return true, Some(next_item) => needle_item = next_item } } } false } } }
  39. #[macro_use] extern crate libcruby; declare_types! { reopen class Array {

    def fully_contain(self, needle: &[usize]) -> bool { if needle.is_empty() { return true } let haystack = self.as_ref(); if haystack.is_empty() { return false } let mut needle = needle.iter(); let mut needle_item = needle.next().unwrap(); for item in haystack { if item == needle_item { match needle.next() { None => return true, Some(next_item) => needle_item = next_item } } } false } } } \
  40. #[macro_use] extern crate libcruby; declare_types! { reopen class Array {

    def fully_contain(self, needle: &[usize]) -> bool { if needle.is_empty() { return true } let haystack = self.as_ref(); if haystack.is_empty() { return false } let mut needle = needle.iter(); let mut needle_item = needle.next().unwrap(); for item in haystack { if item == needle_item { match needle.next() { None => return true, Some(next_item) => needle_item = next_item } } } false } } }
  41. #[macro_use] extern crate libcruby; declare_types! { reopen class Array {

    def fully_contain(self, needle: &[usize]) -> bool { if needle.is_empty() { return true } let haystack = self.as_ref(); if haystack.is_empty() { return false } let mut needle = needle.iter(); let mut needle_item = needle.next().unwrap(); for item in haystack { if item == needle_item { match needle.next() { None => return true, Some(next_item) => needle_item = next_item } } } false } } }
  42. #[macro_use] extern crate libcruby; declare_types! { reopen class Array {

    def fully_contain(self, needle: &[usize]) -> bool { if needle.is_empty() { return true } let haystack = self.as_ref(); if haystack.is_empty() { return false } let mut needle = needle.iter(); let mut needle_item = needle.next().unwrap(); for item in haystack { if item == needle_item { match needle.next() { None => return true, Some(next_item) => needle_item = next_item } } } false } } }
  43. #[macro_use] extern crate libcruby; declare_types! { reopen class Array {

    def fully_contain(self, needle: &[usize]) -> bool { if needle.is_empty() { return true } let haystack = self.as_ref(); if haystack.is_empty() { return false } let mut needle = needle.iter(); let mut needle_item = needle.next().unwrap(); for item in haystack { if item == needle_item { match needle.next() { None => return true, Some(next_item) => needle_item = next_item } } } false } } }
  44. #[macro_use] extern crate libcruby; declare_types! { reopen class Array {

    def fully_contain(self, needle: &[usize]) -> bool { if needle.is_empty() { return true } let haystack = self.as_ref(); if haystack.is_empty() { return false } let mut needle = needle.iter(); let mut needle_item = needle.next().unwrap(); for item in haystack { if item == needle_item { match needle.next() { None => return true, Some(next_item) => needle_item = next_item } } } false } } }
  45. #[macro_use] extern crate libcruby; declare_types! { reopen class Array {

    def fully_contain(self, needle: &[usize]) -> bool { if needle.is_empty() { return true } let haystack = self.as_ref(); if haystack.is_empty() { return false } let mut needle = needle.iter(); let mut needle_item = needle.next().unwrap(); for item in haystack { if item == needle_item { match needle.next() { None => return true, Some(next_item) => needle_item = next_item } } } false } } }