Ruby処理系のコンパイル済みコードの設計
笹田耕一、松本行弘（Heroku, Inc.）
ko1@heroku.com, matz@heroku.com

第106回プログラミング研究発表会

今日の話• Rubyスクリプトをバイトコード列に事前にコンパイル・後でロードする機能を試作• JVMクラスファイルみたいなもので、新規性はない• 実際に観察すると、遅延ロードによって、ロードするバイトコードは、実際には15%程度でいいのかも、という結果• 設計する前に、ちゃんと調べろ、という教訓の話

Background
Ruby as a web application dev language

Background
Sample application

Very simple sample application
http://atdot.net/ab/

Background
Load many libraries
Loaded Gems
(Gem ≒ Library)
Loaded Ruby scripts
Average line number of loaded
Ruby scripts
Maximum line number of loaded
Ruby scripts

91
1,550
140
2,970

Problem
Increasing loading time
1. Specify loaded scripts• Tools such as “Bundler” help.
• Some other ideas (out of scope from our research)

2. Read loaded scripts• Traditional “Disk cache” will help (out of scope)

3. Parse and compile loaded scripts to generate Bytecode• We need to repeat this process for all of ruby interpreter• Loading time is important, especially for application
development phaseProblem
Increasing memory consumption
Bytecode consumes 15% (20MB)

10%

10%

20%

30%

40%

50%

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

60%

70%

80%

90%

100%

Measured by valgrind/massif

Problem
Increasing memory on multi-process• Only small application consume 20MB by bytecodes• N processes can consumes N times 20MB (or more)• CoW can help, but not guaranteed• Shared bytecode data is required

Process
Bytecodes
Bytecodes
Bytecodes
Independent BCs

SharedBytecode
Data

Process

BC(Partialy) Shared BCs

Approach• Goal: Fast load, low memory consumption, non-negative performance
impact loading feature• Our approach• Prepare compiled code beforehand• General idea (so many languages Java, Python, PHP, emacs, … support)• Machine dependent compiled data (word size, endian, etc…)• Related work: Ruby’s case• mruby generates compiled code
• Ikehara’s code compaction• Some native compilers

Approach
Bytecode dump/loadInterpreter process

Ruby script

Dump

Compiledcode

Load/shareBC

Reference by mmap() and so onDesign trade-off• Shareable data reduces loading time and memory consumption• But introduces indirect accesses, slows down performance
Shareable data
Interpreter process
BCBC

Access by address

Access bytable index

Interpreter process
BCTableData formatHeader

•
•
•
•

BC
1

BC
2

Iseq list

ID ID
1 2

ID list

obj
1obj
2

Object list

Iseq (BC), ID, Objects are pointed by index of each lists in each data
Objects are serialized by Marhasl (Ruby’s feature)Dump machine dependent data (can’t migrate compiled code)
No verifier (because this file is not for migrations)Implementation technique
Lazy loading

Ruby script• A Ruby script has several bytecodes• Each scope has own independent bytecode sequence• Bytecodes are tree data structure (like AST)

• Each bytecode consumes memory resource

BC1• Bytecode header• Bytecode sequence
•…

BC2

BC4

BC3

BC5

Implementation technique
Lazy loading• Load bytecodes on demand• Make “unloaded” empty BC

Ruby script
BC1• Points compiled code

• Load bytecode when it is needed• To execute BC1, empty BC2 and BC3
are created, BC4 and BC5 is notcreated completelyCompiledcode
BC1 (loaded)
BC2BC3BC4BC5…

BC2(Unloaded)

BC3(Unloaded)Experiment• Ubuntu 14.04.2 LTS on VirtualBox on Windows 7
on Intel i5-3380M (2.90GHz) CPU• 1,400 lines Ruby script• 100 class definition• Each class has 3 simple methods• 401 bytecodes will be generated• Ruby script and compiled code are already onmemory (not from FS)• Current implementation copy all data fromcompiled codeclass C0def foo
x = y = z = :hellop(x, y, z)
end
def barx = y = z = :hellop(x, y, z)
end
def bazx = y = z = :hellop(x, y, z)
end
end
…

Experiment
Load time

Parse+compile
Compiled code
Compiled code (lazy)

Class/method definitions
are execution statements(Initial) Loadtime
7.05
2.22
0.00

Load +Execution
8.42
3.41
2.06

Execution
1.37
1.19
2.06

(seconds)(result of repeating 2,000 times)# 101 bytecodes (25%) are loaded by lazy load

ExperimentCompiled data
Data
Ruby script lines
Ruby script size
Classes
Methods
Compiled code
Compare with a script

1,400 lines
19,050 bytes
100 classes
300 methods
237,536 bytes
x12.5

Experiment
Lazy load• Run simple sample web application with 10 accesses• Count loaded bytecodes and executed bytecode

Loaded bytecode
Executed bytecodeRatio
30,485
4,698

15.4%

Discussion• Only “15%” of bytecodes are needed …
• Lazy load is good idea
• We can consume loading time for each bytecodes, don’t need touse shared compiled data
→ We need to consider to use “Compaction techniques”まとめ• Rubyスクリプトを事前にコンパイル・後でロードする機
能を試作• JVMクラスファイルみたいなものが欲しい、新規性はない• 実際に観察すると、遅延ロードによって、ロードするバイトコードは、実際には15%程度でいいのかも、という結果• 他プロセスとデータを共有するために、共有可能で性能があまり落ちないデータフォーマットを考えていたけど、単純に（15%だけ）コピーして展開するだけでよさそう

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