Ruby 2.4 Internals
Koichi Sasada
ko1@cookpad.com
Note:
Ruby 2.4.0 has
several bugs.
Note2:
No x.y.0 doesn’t
have several bugs.
Ruby 2.4.0
New features
written in a release announcement
• Introduce hash table improvement (by Vladimir Makarov)
• Binding#irb: Start a REPL session similar to binding.pry
• Unify Fixnum and Bignum into Integer
• String supports Unicode case mappings
• Performance improvements
• Array#max, Array#min
• Regexp#match?
• speed up instance variable access
• Debugging
• Thread#report_on_exception and Thread.report_on_exception
• Thread deadlock detection now shows threads with their backtrace and dependency
• Other notable changes since 2.3
• Support OpenSSL 1.1.0 (drop support for 0.9.7 or prior)
• ext/tk is now removed from stdlib Feature #8539
• XMLRPC is now removed from stdlib Feature #12160
Ruby 2.4 Internals
Koichi Sasada
ko1@cookpad.com
https://blog.heroku.com/r
uby-2-4-features-hashes-
integers-rounding
Any other
topics?
benchmark/bm_app_lc_fizzbuzz.rb
Bug #10212 MRI is not for lambda calculus
JRuby 26 sec
mruby 27 sec
MRI 114 sec
Feature #12628
change block/env structs
Ruby 2.4 Internals
Change block/env structs
Koichi Sasada
ko1@cookpad.com
Issues
1. we need to clear
rb_control_frame_t::block_iseq for
every frame setup. It consumes space (a
VALUE for each frame) and initializing
time.
2. There are several block passing ways by
ISeq (iter{...}), Proc(iter(&pr)),
Symbol(iter(:sym)). However, they are
not optimized (for Symbol blocks, there
is only ad-hoc check code).
3. Env (and Proc, Binding) objects are not
WB-protected ([Bug #10212]).
Method dispatch (etc)
improvements
Cleanup src code
Improve GC perf.
Patch
•https://github.com/ruby/ruby/compare/tru
nk...ko1:block_code
•“Showing with 1,863 additions and 1,070
deletions.”
Approaches
•For (1), (2)
•Introduce Block Handler (BH)
•Using BH
•For (3)
•Introduce Write Barriers (WB) for Env objects
WB protected or unprotected?
•Ruby 2.1.0 introduced Generational GC
•Only newer objects
•GenGC requires “Write barriers” (WB), but
MRI allows WB unprotected objects
(See my past presentations for details)
•WB protected objects: GenGC → Fast
•WB unprotected objects: Not GenGC → Slow
RubyVM::Env objects
•Env objects represent
captured local variables
•Each Proc or Binding has
at least one Env object
•Proc object “$pr”
consists of 3 Env objects
a = 1
1.times{|b|
1.times{|c|
$pr = Proc.new{
# you can access a, b, c
}
}
}
Proc
$pr
Env
c=0
Env
b=0
Env
a=1
RubyVM::Env objects were
WB-unprotected
•They were WB unprotected because:
•Difficulty of implementation
•Performance issue
Performance issue
Assignment performance
• Ruby 2.3 or before
*(ep - idx) = val;
• Naïve implementation
#define VM_EP_IN_HEAP_P(th, ep) ¥
(!((th)->stack <= (ep) && ¥
(ep) < ((th)->stack + (th)->stack_size)))
if (VM_EP_IN_HEAP_P(ep)) {
RB_OBJ_WRITE(VM_ENV_EP_ENVVAL(ep),
ep-idx, val);
}
else *(ep - idx) = val;
Ideas
1. Lightweight escape detection
2. Skip WB except really required timing
Idea
Lightweight escape detection
•Move cfp->flags to ep[0]
•Introduce a VM_ENV_FLAG_ESCAPED flag to
represent escaped Env.
• // Before
#define VM_EP_IN_HEAP_P(th, ep) (!((th)->stack <= (ep) && (ep) < ((th)->stack + (th)->stack_size)))
• // After
#define VM_EP_IN_HEAP_P(ep) (ep[0] & VM_ENV_FLAG_ESCAPED)
Idea
Skip WB except really required timing
1. At initializing Env objects, VM_ENV_FLAG_WB_REQUIRED is true.
2. At first local variable assignment, VM_ENV_FLAG_WB_REQUIRED
is true, we remember this Env object forcibly. And turn off this
flag.
3. At next local variable assignment, VM_ENV_FLAG_WB_REQUIRED
is false, so we can ignore WB protection.
4. At GC marking for this Env object, we turn on
VM_ENV_FLAG_WB_REQUIRED and goto (2).
Very danger technique because it depends on GC implementation
Naïve code
#define VM_EP_IN_HEAP_P(th, ep) (!((th)->stack <= (ep)
&& (ep) < ((th)->stack + (th)->stack_size)))
vm_env_write(const VALUE *ep, int index, VALUE v) {
if (VM_EP_IN_HEAP_P(ep)) {
RB_OBJ_WRITE(VM_ENV_EP_ENVVAL(ep), ep-idx, val);
}
else {
*(ep - idx) = val;
}
}
Final code
vm_env_write(const VALUE *ep, int index, VALUE v) {
VALUE flags = ep[VM_ENV_DATA_INDEX_FLAGS];
if (LIKELY((flags & VM_ENV_FLAG_WB_REQUIRED) == 0)) {
*(ep - idx) = val; /* mostly used */
}
else {
/* remember env value forcibly */
vm_env_write_slowpath(ep, index, v);
}
}
Benchmark result
Bug #10212 MRI is not for lambda calculus
lc_fizzbuzz with MRI versions
0
50
100
150
ruby_2_0 ruby_2_1 ruby_2_2 ruby_2_3 trunk
Executiontime(sec)
app_lc_fizzbuzz
Bug #10212 MRI is not for lambda calculus
lc_fizzbuzz with MRI, JRuby, mruby
36.976
18.706
40.185
0
10
20
30
40
50
trunk jruby mruby
Executiontime(sec)
app_lc_fizzbuzz
Summary
•Ruby 2.4.0 has many improvements
•Now Proc (Env) objects are WB protected
and we have more faster GC (marking)
•My ideas allow to protect Env objects
without big performance impact
Thank you for your attention
Koichi Sasada
<ko1@cookpad.com>

Ruby 2.4 Internals

  • 1.
  • 4.
    Note: Ruby 2.4.0 has severalbugs. Note2: No x.y.0 doesn’t have several bugs.
  • 5.
  • 6.
    New features written ina release announcement • Introduce hash table improvement (by Vladimir Makarov) • Binding#irb: Start a REPL session similar to binding.pry • Unify Fixnum and Bignum into Integer • String supports Unicode case mappings • Performance improvements • Array#max, Array#min • Regexp#match? • speed up instance variable access • Debugging • Thread#report_on_exception and Thread.report_on_exception • Thread deadlock detection now shows threads with their backtrace and dependency • Other notable changes since 2.3 • Support OpenSSL 1.1.0 (drop support for 0.9.7 or prior) • ext/tk is now removed from stdlib Feature #8539 • XMLRPC is now removed from stdlib Feature #12160
  • 7.
  • 8.
  • 9.
  • 13.
  • 15.
    Bug #10212 MRIis not for lambda calculus JRuby 26 sec mruby 27 sec MRI 114 sec
  • 17.
  • 18.
    Ruby 2.4 Internals Changeblock/env structs Koichi Sasada [email protected]
  • 19.
    Issues 1. we needto clear rb_control_frame_t::block_iseq for every frame setup. It consumes space (a VALUE for each frame) and initializing time. 2. There are several block passing ways by ISeq (iter{...}), Proc(iter(&pr)), Symbol(iter(:sym)). However, they are not optimized (for Symbol blocks, there is only ad-hoc check code). 3. Env (and Proc, Binding) objects are not WB-protected ([Bug #10212]). Method dispatch (etc) improvements Cleanup src code Improve GC perf.
  • 20.
  • 21.
    Approaches •For (1), (2) •IntroduceBlock Handler (BH) •Using BH •For (3) •Introduce Write Barriers (WB) for Env objects
  • 22.
    WB protected orunprotected? •Ruby 2.1.0 introduced Generational GC •Only newer objects •GenGC requires “Write barriers” (WB), but MRI allows WB unprotected objects (See my past presentations for details) •WB protected objects: GenGC → Fast •WB unprotected objects: Not GenGC → Slow
  • 23.
    RubyVM::Env objects •Env objectsrepresent captured local variables •Each Proc or Binding has at least one Env object •Proc object “$pr” consists of 3 Env objects a = 1 1.times{|b| 1.times{|c| $pr = Proc.new{ # you can access a, b, c } } } Proc $pr Env c=0 Env b=0 Env a=1
  • 24.
    RubyVM::Env objects were WB-unprotected •Theywere WB unprotected because: •Difficulty of implementation •Performance issue
  • 25.
    Performance issue Assignment performance •Ruby 2.3 or before *(ep - idx) = val; • Naïve implementation #define VM_EP_IN_HEAP_P(th, ep) ¥ (!((th)->stack <= (ep) && ¥ (ep) < ((th)->stack + (th)->stack_size))) if (VM_EP_IN_HEAP_P(ep)) { RB_OBJ_WRITE(VM_ENV_EP_ENVVAL(ep), ep-idx, val); } else *(ep - idx) = val;
  • 26.
    Ideas 1. Lightweight escapedetection 2. Skip WB except really required timing
  • 27.
    Idea Lightweight escape detection •Movecfp->flags to ep[0] •Introduce a VM_ENV_FLAG_ESCAPED flag to represent escaped Env. • // Before #define VM_EP_IN_HEAP_P(th, ep) (!((th)->stack <= (ep) && (ep) < ((th)->stack + (th)->stack_size))) • // After #define VM_EP_IN_HEAP_P(ep) (ep[0] & VM_ENV_FLAG_ESCAPED)
  • 28.
    Idea Skip WB exceptreally required timing 1. At initializing Env objects, VM_ENV_FLAG_WB_REQUIRED is true. 2. At first local variable assignment, VM_ENV_FLAG_WB_REQUIRED is true, we remember this Env object forcibly. And turn off this flag. 3. At next local variable assignment, VM_ENV_FLAG_WB_REQUIRED is false, so we can ignore WB protection. 4. At GC marking for this Env object, we turn on VM_ENV_FLAG_WB_REQUIRED and goto (2). Very danger technique because it depends on GC implementation
  • 29.
    Naïve code #define VM_EP_IN_HEAP_P(th,ep) (!((th)->stack <= (ep) && (ep) < ((th)->stack + (th)->stack_size))) vm_env_write(const VALUE *ep, int index, VALUE v) { if (VM_EP_IN_HEAP_P(ep)) { RB_OBJ_WRITE(VM_ENV_EP_ENVVAL(ep), ep-idx, val); } else { *(ep - idx) = val; } }
  • 30.
    Final code vm_env_write(const VALUE*ep, int index, VALUE v) { VALUE flags = ep[VM_ENV_DATA_INDEX_FLAGS]; if (LIKELY((flags & VM_ENV_FLAG_WB_REQUIRED) == 0)) { *(ep - idx) = val; /* mostly used */ } else { /* remember env value forcibly */ vm_env_write_slowpath(ep, index, v); } }
  • 31.
  • 32.
    Bug #10212 MRIis not for lambda calculus lc_fizzbuzz with MRI versions 0 50 100 150 ruby_2_0 ruby_2_1 ruby_2_2 ruby_2_3 trunk Executiontime(sec) app_lc_fizzbuzz
  • 33.
    Bug #10212 MRIis not for lambda calculus lc_fizzbuzz with MRI, JRuby, mruby 36.976 18.706 40.185 0 10 20 30 40 50 trunk jruby mruby Executiontime(sec) app_lc_fizzbuzz
  • 34.
    Summary •Ruby 2.4.0 hasmany improvements •Now Proc (Env) objects are WB protected and we have more faster GC (marking) •My ideas allow to protect Env objects without big performance impact
  • 35.
    Thank you foryour attention Koichi Sasada <[email protected]>