CompilingRuby scripts
Koichi Sasada
ko1@heroku.com

Today’s talk
• Making ruby script serializer and deserializer• Well known technique and tools such as JVM class file
• No special technique is needed. Just implemented it.

• Introduction of how to use• Evaluation result

Koichi Sasada
A programmer living in Japan

Koichi is a Programmer• MRI committer since 2007/01• Original YARV developer since 2004/01• YARV: Yet Another RubyVM• Introduced into Ruby (MRI) 1.9.0 and later• Generational/incremental GC for 2.x

Koichi is an EmployeeAyumu Aizawa
Solutions Architect, Heroku

Satoshi Nagano
Heroku sales manager

Koichi is a member of Heroku
Matz team
MissionDesign Ruby language
and improve quality of MRI
Heroku employs three full time Ruby coredevelopers in Japan named “Matz team”

Upcoming Ruby 2.3
Today, no time to introduce new features…
Please ask me later.

Appreciation

CI server sponsored by YassLab.

A CI server for EL Capitann
Setup by Shibata-san

Many CI servers

1

So many contributions
Development

Environment

Deliver• All MRI developers• Committers• Code and documents
contributors• Issue reporters• Heroku• Employs full-time
MRI committers(Matz, Nobu, Ko1)
• Ruby Association• Maintain Ruby 2.1
and 2.0 (security)• Ruby Association• Sponsored CI servers on
Cloud services• YassLab and N.R.K• Sponsored a new mac
mini machines for CIserver• Prof. Sugaya, Shibaura
Institute of Technology• Allows us to locate
physical machines• Travis-CI• Provides CI services• CloudCore, DTI• Provides CI servers• Fastly.com• Delivers Ruby binaries
by their CDN• NaCl, IIJ, Heroku• Host web servers• GlobalSign• Sponsored SSLcertification

We need more supports• Examples• Nobu’s development machine• Development/benchmark machines• CI machines (VPS)• Hackathon travel fee• See “Misc #11783: Do you have any idea if youhave a budgets?”• https://bugs.ruby-lang.org/issues/11783

How about to utilize this opportunity
for your promotion?

Compiling Ruby scripts

Compilers for interpreters• JIT compilers• Program to native machine code
• Runtime statistics information areavailable• AOT compilers• Program to native machine code
• Program to other languages code
• Translate to C, Java, etc…

• Program to persistent byte code
• RubyVM::InstructionSequence in Ruby’s case

Store serialized program and load
Ruby
Rubyprocess
Ruby
Processprocess
ISeq
ISeq
ISeq

Ruby script

Read, parseand compile

Ruby process

ISeq

Ruby process

Serialize
and store

Pre-compilation

CompiledbinaryLoad anddeserialize

ISeq

ISeq: Instruction sequence, a representation of program (bytecode) in
RubyVM. An instance of RubyVM::InstructionSequence in Ruby world

Purpose of ISeq (de)serializer• Fast boot• Reduce memory consumption• Migrate compiled code to other nodes

Purpose of ISeq (de)serializer
Goal of this time
• Fast boot• Reduce memory consumption• Migrate compiled code to other nodes
Out of scope

Not support portable binary
Not verify at loading time
→ Do not believe binaries by othersFast bootRuby
scriptParse

Libraries

Embeddedclasses and methods

CompileEvaluator
Ruby
Bytecode (ISeq)

Interpret on RubyVM

Fast bootPre-compilation

RubyscriptParse
Compiled binary

Libraries

Embeddedclasses and methods

CompileEvaluator
Load
Extended part

Ruby
Bytecode (ISeq)

Interpret on RubyVM

Memory consumption
Current issue
ISeq consumes 15% (20MB) on simple Rails app

10%

10%

20%

30%

40%

50%

60%

iseq_setup@compile.c

1
15,595,764

rb_iseq_new_with_opt@iseq.c5,231,136

heap_assign_page@gc.c

40,518,400

st_init_table_with_size@st.c

18,994,480

rb_str_buf_new@string.c4,817,252

st_update@st.c

6,578,736

onig_region_resize@regexec.c

4,891,968

others37,676,810

70%

80%

90%

100%

Memory consumption
Current issue on multi-processes
ActualProcess

Bytecodes
Bytecodes
Bytecodes
Independent BCs

Expected

SharedBytecode
Data

Process

BC(Partialy) Shared BCs

Design andimplementation

ISeq tree
Ruby script• ISeq consists as tree
• Basically, each scope has own ISeq
• A top-level has class expressions• Class expression has method definitions• Method definition has blocks• Block has blocks, …
• Other bytecode blocks• ensure, rescue, …

toplevelclass C1

class C2

• And other exceptional cases
def m1

def m2

Binary data format(not so matured)
Header•
•
•
•

BC
1

BC
2

Iseq list

ID ID
1 2

ID listobj
1

obj
2

Objectlist

Iseq (BC), ID, Objects are pointed by index of each lists in each data
Objects are serialized (manually)
Dump machine dependent data (can’t migrate compiled code)
No verifier (because this file is not for migrations)Technique
Lazy loading• Do not load every bytecode at once
• Load bytecode if neededTechnique
Lazy loading(1)Load and make an empty toplevel Iseq
Toplevel(empty)

Ruby script
class C1
def m1; end
def m2; end
end
C1.new.m2
class C2; end

Compiledbinarytoplevel
C1,C2,m1,m2

Technique
Lazy loading(2) Load toplevel ISeq and makeempty C1, C2 empty ISeq andevaluate toplevel ISeq
Ruby script
class C1
def m1; end
def m2; end
end
C1.new.m2
class C2; end

ToplevelCompiledbinarytoplevel
C1,C2,m1,m2

class C1(empty)

class C2(empty)

Technique
Lazy loading(3) Load C1 and evaluate C1
Define m1 and m2 with empty
ISeqs
Ruby script
class C1
def m1; end
def m2; end
end
C1.new.m2
class C2; end

ToplevelCompiledbinaryclass C2(empty)

class C1

toplevel
C1,C2,m1,m2
def m1(empty)

def m2(empty)Technique
Lazy loading(4) Load m2 and invoke m2

Ruby script
class C1
def m1; end
def m2; end
end
C1.new.m2
class C2; end

ToplevelCompiledbinaryclass C2(empty)

class C1

toplevel
C1,C2,m1,m2
def m1(empty)

def m2

Technique
Lazy loading(4) Load C2 and evaluate C2

Ruby script
class C1
def m1; end
def m2; end
end
C1.new.m2
class C2; end

ToplevelCompiledbinaryclass C2(empty)

class C1

toplevel
C1,C2,m1,m2
def m1(empty)

def m2

Interface
API and Tools

How to store compiled binary?
• Compile timing• Use compiler explicitly• C/Java/… compilers• Loading time

Notfixed!
• Location of compiled binary• Rubinius, Python, …

• A file in a same directory of *.rb files• A file in a special directory• DB

Current implementation
Provide loading API• RubyVM::InstructionSequence.load_iseq• Call this method at every loading time (if defined)• This method should return or loaded ISeq object or nil• You can write your own loader• From File
• From any DB• From network, and so on• RubyVM::InstructionSequence#to_binary• RubyVM::InstructionSequnece.load_from_binary(binary)• Serialize and deserialize (load) methods

Store serialized program and load
Ruby
Rubyprocess
Ruby
Process
process
ISeq
ISeq
ISeq

Ruby script

Read, parseand compile

Ruby process

Ruby process
ISeq#to_binary

ISeq

Serialize
and store

Pre-compilation

ISeq#load_from_binaryCompiledbinary

Load anddeserialize

ISeq

Using ISeq.load_iseq
require/load process

New require/load process

File name (“x.rb”)

File name (“x.rb”)

Load_internal(fname)Load_internal(fname)Read, parse and compile
script named fname (x.rb)

Call ISeq.load_iseq(fname)Read, parse and compile
script named fname (x.rb)

ISeq of x.rb

ISeq of x.rb

ISeq.load_iseq(fname)

Can’t load

Load and return iseq

When should we compile?
• Compile timing• Use compiler explicitly• C/Java/… compilers• Gem install timing is good idea to kick it• Loading time (if not available, compile automatically)• Python (.pyc), Rubinius (.rbc)• Matz doesn’t like itWhere to store?
• “sample/iseq_load.rb” provides 3 type of repository

Store compiled binary in thesame directory/a/b/x.rb, x.rb.yarb
y.rb, y.rb.yarb
c/z.rb, z.rb.yarb

Store compiled binary in thespecified directory/a/b/x.rb, y.rb
c/z.rb/repos/a_b_x.rb.yarb
a_b_y.rb.yarb
a_c_z.rb.yarb

Using dbm
/a/b/x.rb

Binary of x.rb
/a/b/y.rb

Binary of y.rb

/a/c/z.rb

Binary of z.rb

Usage of iseq_load.rb• $ ruby iseq_load.rb [file or dir]
• Compile, serialize and store specified file or files indirectories (dir/**/*.rb)• $ ruby -r iseq_load [script]• Enable loader• Load stored files if possible• Setting by environment variables• See iseq_load.rb for details

NOTE: Experimental feature• All of features are introduced as “Experimental”• We don’t guarantee to keep these interface (methods)• We don’t guarantee to keep binary format• No verifier so that loading modified/broken binary causes
critical problem→ Do not load any binary data provided by others

Enjoy hacking your great Ruby program cache!

Evaluation

Evaluation• Measure loading time of same script 1,000 times• Use remove_const to cleanup each loading• Choose from lib/*.rb

Target script Lines
resolv.rb
csv.rbfileutils.rb
forwardable.rb

Size (KB)
2,855
2,346
1,761
290

73
83
48
8

Evaluation
Loading time (x1,000)
Normal (sec)resolve.rb
csv.rbfileutils.rb
forwardable.rb

13.19
7.88
8.55
0.48

Load (sec)Lazy load (sec)

3.92 (x3.36)
4.19 (x1.88)
4.64 (x1.84)
0.18 (x2.67)

2.42 (x5.45)
2.85 (x2.76)
3.61 (x2.37)
0.12 (x4.00)

 5 times faster on resolv.rb seems good Nobody load resolv.rb 1,000 times

Evaluation
Compiled binary size
Target script

resolv.rb
csv.rbfileutils.rb
forwardable.rb

Lines

Script size (KB)

2,855
2,346
1,761
290

Binary size (KB)

73
83
48
8337
170
202
14

Evaluation
Rails launch time
• Loading time of sample simple Rails application• $ rails r “”Normal (sec)w/o bundle

1.89w/ bundle

2.23

Load (sec)1.42(x1.33)
1.85(x1.21)

Lazy load (sec)1.37(x1.38)1.85(x1.21)

Future work
• Reduce memory consumption by memory
sharing with mmap (and so on)• Reduce binary size with some techniques• Smart serialization technique• Compaction technique• And more…

Does anyone have an interest?
They may be worth hack topics.

Summary• Introduced new “script serializer and deserializer”• You can try this feature with Ruby 2.3 preview 2

Thank you for your attention
Koichi Sasada<ko1@heroku.com>