Ruby Racing:
Challenging
Ruby Methods
Danielle Adams / RubyHACK 2017
adamzdanielle
Ruby Racing:
Challenging
Ruby Methods
code at
runtime
static VALUE
rb_str_strip(VALUE str)
{
char *start;
long olen, loffset, roffset;
rb_encoding *enc = STR_ENC_GET(str);
RSTRING_GETMEM(str, start, olen);
loffset = lstrip_offset(str, start, start+olen, enc);
roffset = rstrip_offset(str, start+loffset, start+olen, enc);
if (loffset <= 0 && roffset <= 0) return rb_str_dup(str);
return rb_str_subseq(str, loffset, olen-loffset-roffset);
}
#strip
How do Ruby methods work?
code
# hello_ruby_hack.rb
def say_hello(name)
puts "Hello, #{name}!"
end
say_hello("Ruby HACK")
tokens
require 'ripper'
file = File.read('hello_ruby_hack.rb')
# tokens are chunks of code
# and don't need to be executable
Ripper.tokenize(file)
=> ["def",
" ",
"say_hello",
"(",
"name",
")",
"\n",
" ",
"puts",
" ",
"\"",
"Hello, ",
"\#{",
"name",
"}",
"!",
"\"",
"\n",
"end",
"\n",
"\n",
"say_hello",
"(",
"\"",
"Ruby HACK",
"\"",
")",
"\n"]
AST
abstract syntax trees
require 'ripper'
file = File.read('hello_ruby_hack.rb')
# sexp is short for symbolic expression
Ripper.sexp(file)
=> [:program,
[[:def,
[:@ident, "say_hello", [1, 4]],
[:paren, [:params, [[:@ident, "name", [1, 14]]], nil, nil, nil, nil, nil, nil]],
[:bodystmt,
[[:void_stmt],
[:command,
[:@ident, "puts", [1, 21]],
[:args_add_block,
[[:string_literal,
[:string_content,
[:@tstring_content, "Hello, ", [1, 27]],
[:string_embexpr, [[:var_ref, [:@ident, "name", [1, 36]]]]],
[:@tstring_content, "!", [1, 41]]]]],
false]]],
nil,
nil,
nil]],
[:method_add_arg,
[:fcall, [:@ident, "say_hello", [1, 50]]],
[:arg_paren,
[:args_add_block,
[[:string_literal, [:string_content, [:@tstring_content, "Ruby HACK", [1, 61]]]]],
false]]]]]
bytecode
file = File.read('hello_ruby_hack.rb')
instructions = RubyVM::InstructionSequence.compile(file)
# since Ruby 1.9, bytecode is read by VM
# instead of the traversing the AST
puts instructions.disassemble
== disasm: #<ISeq:<compiled>@<compiled>>================================
0000 trace 1 ( 1)
0002 putspecialobject 1
0004 putobject :say_hello
0006 putiseq say_hello
0008 opt_send_without_block <callinfo!mid:core#define_method, argc:2, ARGS_SIMPLE>, <callcache>
0011 pop
0012 trace 1 ( 5)
0014 putself
0015 putstring "Ruby HACK"
0017 opt_send_without_block <callinfo!mid:say_hello, argc:1, FCALL|ARGS_SIMPLE>, <callcache>
0020 leave
== disasm: #<ISeq:say_hello@<compiled>>=================================
local table (size: 2, argc: 1 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 2] name<Arg>
0000 trace 8 ( 1)
0002 trace 1 ( 2)
0004 putself
0005 putobject "Hello, "
0007 getlocal_OP__WC__0 2
0009 tostring
0010 putobject "!"
0012 concatstrings 3
0014 opt_send_without_block <callinfo!mid:puts, argc:1, FCALL|ARGS_SIMPLE>, <callcache>
0017 trace 16 ( 3)
0019 leave ( 2)
bytecode
+
YARV
(This is only true for CRuby.)
speeding up
code
story time
What is Ruby Racing?
$ gem install ruby-racer
$
#flatten_race.rb
require 'ruby_racer'
require_relative 'flatten'
input = [ 1, [ 2, 3 ] ]
proc = Proc.new { |input| flatten(input) }
RubyRacer.go(:flatten, input, &proc)
demo
What makes code faster?
primitive
operations
are fast
# max with each enumerable
def max(array)
# (...)
array.each do |el|
max = el if el > max
# (...)
end
# max with while loop
def max(array)
# (...)
while i < arr.length
if array[i] > max
max = array[i]
# (...)
end
# benchmark
iterations = 1000000
input = [ 1, 2, 3 ]
operation = el + el
user total real
with each: 0.2500 0.2500 (0.251002)
with while: 0.2200 0.2200 (0.226902)
array mutation
is costly
# array appending vs assignment
[] << 1
[nil][0] = 1
# => [1]
# benchmark
iterations = 1000000
user total real
with append: 0.1000 0.1000 (0.096824)
with assign: 0.0900 0.0900 (0.095280)
complex algorithms
are slow
dive into
source code
# ruby/array.c:4631
static VALUE
rb_ary_flatten(int argc, VALUE *argv, VALUE ary)
{
int mod = 0, level = -1;
VALUE result, lv;
# (...)
result = flatten(ary, level, &mod);
OBJ_INFECT(result, ary);
return result;
}
# ruby/array.c:4631
static VALUE
rb_ary_flatten(int argc, VALUE *argv, VALUE ary)
{
int mod = 0, level = -1;
VALUE result, lv;
# (...)
result = flatten(ary, level, &mod);
OBJ_INFECT(result, ary);
return result;
}
result = flatten(ary, level, &mod);
# ruby/array.c:4508
static VALUE
flatten(VALUE ary, int level, int *modified)
{
# (...)
while (i < RARRAY_LEN(ary)) {
if (level >= 0 &&
RARRAY_LEN(stack) / 2 >= level) {
rb_ary_push(result, elt);
continue;
# (...)
else {
rb_ary_push(stack, ary);
# ruby/array.c:4508
static VALUE
flatten(VALUE ary, int level, int *modified)
{
# (...)
while (i < RARRAY_LEN(ary)) {
if (level >= 0 &&
RARRAY_LEN(stack) / 2 >= level) {
rb_ary_push(result, elt);
continue;
# (...)
else {
rb_ary_push(stack, ary);
if (level >= 0 &&
RARRAY_LEN(stack) / 2 >= level) {
rb_ary_push(result, elt);
rb_ary_push(stack, ary);
# ruby/array.c:4395
static VALUE
rb_ary_uniq(VALUE ary)
{
VALUE hash, uniq;
# (...)
if (RARRAY_LEN(ary) <= 1)
return rb_ary_dup(ary);
# (...)
else {
hash = ary_make_hash(ary);
uniq = rb_hash_values(hash);
}
# ruby/array.c:4395
static VALUE
rb_ary_uniq(VALUE ary)
{
VALUE hash, uniq;
# (...)
if (RARRAY_LEN(ary) <= 1)
return rb_ary_dup(ary);
# (...)
else {
hash = ary_make_hash(ary);
uniq = rb_hash_values(hash);
}
if (RARRAY_LEN(ary) <= 1)
return rb_ary_dup(ary);
hash = ary_make_hash(ary);
uniq = rb_hash_values(hash);
# ruby/array.c:2530
VALUE
rb_ary_sort(VALUE ary)
{
ary = rb_ary_dup(ary);
rb_ary_sort_bang(ary);
return ary;
}
# ruby/array.c:2530
VALUE
rb_ary_sort(VALUE ary)
{
ary = rb_ary_dup(ary);
rb_ary_sort_bang(ary);
return ary;
}
ary = rb_ary_dup(ary);
rb_ary_sort_bang(ary);
There are a few factors that will affect performance if we are willing to sacrifice other methodologies.
performance != code quality
How do we define code quality?
What is Ruby best at?
I didn't work hard to make Ruby perfect for everyone, because you feel differently from me. No language is perfect for everyone. [...] The perfect language for Guido van Rossum is probably Python.
- Yukihiro "Matz" Matsumoto
(Guido van Rossum wrote Python.)
resources
PyPy
pypy.org
If you want your code to run faster, you should probably just use PyPy.
- Guido van Rossum
Ruby Under the Microscope
Pat Shaughnessy
Why?
Ruby is designed to make programmers happy.
- Matz
code: github.com/danielleadams/ruby-racer
slides: slides.com/danielleadams/ruby-racing-rh17
adamzdanielle
thank you