电磁炉什么牌子好| 狗狗吃南瓜有什么好处| 疣有什么危害| 咳嗽吃什么食物好得最快最有效| 做梦死人了是什么征兆| 老年人睡眠多是什么原因| 舌根发黄是什么原因造成的| 什么鱼最好养不容易死| 慢性胃炎吃什么药效果好| 大姨妈来了吃什么| 儿童正常体温在什么范围| 孕妇现在吃什么水果好| 嗓子咽口水疼吃什么药| 活性印染是什么意思| 人中长痘痘什么原因| 男人吃香菜有什么好处| 肝不好看什么科| 长痘是什么原因| 牙龈疼是什么问题| 水保是什么| 肝囊肿吃什么食物好| 来月经吃什么排得最干净| 鹦鹉吃什么东西| 什么食物含硒| 给老人过生日送什么礼物好| 言过其实是什么意思| 糖尿病的人可以吃什么水果| 小雪是什么意思| 冰心原名叫什么名字| 欺凌是什么意思| 颈椎退行性病变是什么意思| 茹毛饮血什么意思| 瓶颈期是什么意思| 1.22是什么星座| 晚上睡觉脚抽筋是什么原因引起的| 云南白药里面的保险子有什么用| 拔草是什么意思| led什么意思| 鸽子拉水便是什么原因| 胸长什么样子| 误会是什么意思| 眼睛痛是什么原因| 什么是足金| ny是什么牌子| 中筋面粉是什么粉| 什么草药能治肿瘤| 暗代表什么生肖| 豆蔻年华是什么意思| 脂肪是什么| 铜是什么颜色的| 月经血块是什么原因| 美国为什么打伊拉克| 布灵布灵是什么意思| 成人发烧吃什么退烧药| 尿频去药店买什么药| 排骨炖什么最好吃| 甲鱼喜欢吃什么食物| 涮菜都有什么菜| 等闲识得东风面什么意思| 君子兰叶子发黄是什么原因| 什么是转基因| 外阴萎缩是什么症状| bj是什么| 楷字五行属什么| 今年农历什么年| 球麻痹是什么病| 臭虫是什么| 千什么百什么| 依托考昔片是什么药| 什么时候可以领退休金| 一什么新闻| 市检察长是什么级别| 葳是什么意思| 金风送爽是什么意思| 吃什么补维生素| 父母都是o型血孩子是什么血型| 性激素六项检查是什么| 脓毒血症是什么原因引起的| 捋是什么意思| 粗茶淡饭下一句是什么| 省委巡视组组长什么级别| 望穿秋水的意思是什么| 什么花最大| 家里为什么突然有床虱| 腋下淋巴结肿大挂什么科| 酒精对皮肤有什么伤害| 生蚝是什么东西| 贫血是什么原因引起的| 做梦梦到怀孕了是什么意思| 胸闷气短咳嗽是什么原因引起的| 女人的秘密是什么| 硼砂是干什么用的| 日出东方下一句是什么| 2008年出生的属什么| 寒咳吃什么药| 肠系膜淋巴结炎吃什么药最有效| 卫生纸属于什么垃圾| 晚上9点半是什么时辰| 天使什么意思| 棉纱是什么面料| 育婴员是做什么的| 浛是什么意思| 来曲唑片什么时候吃最好| 色令智昏是什么意思| 省内流量是什么意思| 夜间咳嗽是什么原因| 激光点痣后需要注意什么| 铜绿假单胞菌用什么抗生素| 喝莓茶对身体有什么好处| 紫得什么| 湘女多情是什么意思| 看病人送什么| 黑乎乎的什么| 5月16是什么星座| 部队政委是什么级别| 发烧吃什么水果好| 回应是什么意思| circles是什么意思| 心慌是什么意思| 回族人为什么不吃猪肉| 秫米是什么米| 半夜脚抽筋是什么原因| 什么情况下安装心脏起搏器| 什么高什么下| 晚上十点多是什么时辰| 血压高有什么危害| 师团长是什么级别| 男性经常手淫有什么危害| 什么样的牙齿需要矫正| 路冲是什么意思| 20至30元什么烟最好抽| 囊壁钙化是什么意思| 上海特产是什么| 侏罗纪是什么意思| prr是什么意思| 146是什么意思| 说你什么好| 大胯疼是什么原因引起| 菁字五行属什么| 中性粒细胞低说明什么| 胆囊充盈欠佳什么意思| 讽刺是什么意思| 韩信属什么生肖| 红绿色盲是什么遗传病| vane是什么意思| 肠胃炎不能吃什么| 花生不能和什么一起吃| 消防支队长是什么级别| 上上签什么意思| hpv是什么| 7月6日是什么星座| 偏旁和部首有什么区别| 椰子水是什么颜色| 丫丫的老公叫什么| 奇葩什么意思| 去湿气吃什么中药| 脸红是什么大病的前兆| 鸡内金有什么作用| 什么是反式脂肪酸| 为什么萤火虫会发光| 三宝是什么意思| 数字3代表什么意思| 白酒是什么时候出现的| 感恩节为什么要吃火鸡| 央企董事长什么级别| 仪仗队是什么意思| 变节是什么意思| 1971年属什么| 精囊炎吃什么药最有效| 什么叫小微企业| 肝不好看什么科| 腰痛宁为什么晚上吃| eb病毒iga抗体阳性是什么意思| 肺结核复发有什么症状| 牛肉烧什么菜好吃| 睡意是什么意思| 英特纳雄耐尔是什么意思| 什么地眨眼| 伽马刀是什么意思| 肌酐为什么会升高| 角膜塑形镜什么牌子好| 肝阳上亢是什么意思| 花椰菜是什么菜| 面色潮红是什么原因| 路亚什么意思| 打包是什么意思| 老虎菜是什么菜| 什么夺目| 什么茶不影响睡眠| 社会保险是什么意思| 枫树的叶子像什么| 一什么绿毯| 情趣什么意思| 惊什么万什么| 纳肛是什么意思| 饮鸩止渴什么意思| 脸部出油多是什么原因| 呼吸困难吃什么药| 玉佛寺求什么最灵验| 吃完紧急避孕药不能吃什么| 姜子牙姓什么| 黄发指什么| 大生化能查出什么病来| 补刀什么意思| 宫颈管是什么| 密度是什么意思| 为什么月经每个月提前| 麸子是什么东西| 什么终于什么造句| 血小板计数偏高是什么意思| 什么是冰种翡翠| 怂恿是什么意思| 罢黜百家独尊儒术是什么意思| 经期有血块是什么原因| 电风扇不转是什么原因| 心衰吃什么药好| 百草枯什么味道| 血管堵塞吃什么药好| 小便有点红是什么原因| 明天是什么节日| 心脏有早搏吃什么药好| 人老是犯困想睡觉是什么原因| 夜尿次数多是什么原因| 藿香正气水什么人不能喝| 新型冠状病毒有什么症状| 45岁属什么| 疱疹长什么样子图片| 头疼是因为什么| 尿尿疼吃什么药| 胃反酸是什么原因造成的| 胆囊息肉有什么症状| 甲功是什么| 蜘蛛侠叫什么名字| 送朋友什么礼物好| 小猫踩奶是什么意思| 炒什么菜好吃又简单| 骨质疏松用什么药好| 白凉粉是什么| 我国计划生育什么时候开始| 镶牙和种牙有什么区别| 缺金的人戴什么最旺| 非淋菌性尿道炎吃什么药最好| 高血压可以吃什么| h表示什么| 客厅沙发后面墙上挂什么画好| 标准员是干什么的| 姑息性化疗什么意思| 拔了牙吃什么消炎药| 夏天白鸽煲什么汤最好| 新生儿便秘吃什么好| 不经意间是什么意思| 男性内分泌失调吃什么药| 脱发厉害是什么原因引起的| 2002年属什么生肖| 三和大神是什么意思| 归脾丸的功效与作用治什么病| 豆浆什么人不能喝| 六个坚持是什么| 梦见死人是什么| 椰子煲鸡汤放什么材料| 1996年五行属什么| 劫富济贫是什么意思| 潜叶蝇打什么药效果好| 尿糖一个加号是什么意思| 百度
Upgrade to Pro — share decks privately, control downloads, hide ads and more …

RubyKaigi 2025: Class New, A New Approach

Avatar for Aaron Patterson Aaron Patterson
May 15, 2025
78

退役将军们都去了哪?11人在专委会任职刘源专委会将军

百度 ”中国航天三江集团设计所型号总设计师、科技委主任胡胜云代表说,过去科研领域存在一些不良现象,个别科研工作者为了多揽项目、快出成绩,丢掉了好传统,拼凑材料政绩,甚至炮制“空心”成果。

This talk is about optimizing Class#new. I gave the talk at RubyKaigi 2025

Avatar for Aaron Patterson

Aaron Patterson

May 15, 2025
Tweet

Transcript

  1. class Class def new(...) allocate.initialize(...) end end class Foo def

    initialize(a, b) :hello end end Foo.new(1, 2) # => ??? :hello Possible Implementation First, buggy implementation
  2. Possible Implementation Second, less buggy implementation class Class def new(...)

    obj = allocate obj.initialize(...) obj end end class Foo def initialize(a, b) :hello end end Foo.new(1, 2) # => #<Foo ...>
  3. Possible Implementation Things we need to know about class Class

    def new(...) obj = allocate obj.initialize(...) obj end end class Foo def initialize(a, b) :hello end end Foo.new(1, 2) # => #<Foo ...> method call method call 2 inline caches Calling conventions
  4. Actual Implementation Class#new implementation in C VALUE rb_class_new_instance_pass_kw(int argc, const

    VALUE *argv, VALUE klass) { VALUE obj; obj = rb_class_alloc(klass); rb_obj_call_init_kw(obj, argc, argv, RB_PASS_CALLED_KEYWORDS); return obj; } class Foo def initialize(a, b) :hello end end Foo.new(1, 2) # => #<Foo ...> VALUE rb_class_new_instance_pass_kw(int argc, const VALUE *argv, VALUE klass) { VALUE obj; obj = rb_class_alloc(klass); rb_obj_call_init_kw(obj, argc, argv, RB_PASS_CALLED_KEYWORDS); return obj; } class Foo def initialize(a, b) :hello end end Foo.new(1, 2) # => #<Foo ...> Allocation happens here
  5. VALUE rb_class_new_instance_pass_kw(int argc, const VALUE *argv, VALUE klass) { VALUE

    obj; obj = rb_class_alloc(klass); rb_obj_call_init_kw(obj, argc, argv, RB_PASS_CALLED_KEYWORDS); return obj; } class Foo def initialize(a, b) :hello end end Foo.new(1, 2) # => #<Foo ...> Actual Implementation Class#new implementation in C
  6. VALUE rb_class_new_instance_pass_kw(int argc, const VALUE *argv, VALUE klass) { VALUE

    obj; obj = rb_class_alloc(klass); rb_obj_call_init_kw(obj, argc, argv, RB_PASS_CALLED_KEYWORDS); return obj; } class Foo def initialize(a, b) :hello end end Foo.new(1, 2) # => #<Foo ...> Actual Implementation Class#new implementation in C
  7. VALUE rb_class_new_instance_pass_kw(int argc, const VALUE *argv, VALUE klass) { VALUE

    obj; obj = rb_class_alloc(klass); rb_obj_call_init_kw(obj, argc, argv, RB_PASS_CALLED_KEYWORDS); return obj; } class Foo def initialize(a, b) :hello end end Foo.new(1, 2) # => #<Foo ...> Actual Implementation Class#new implementation in C
  8. VALUE rb_class_new_instance_pass_kw(int argc, const VALUE *argv, VALUE klass) { VALUE

    obj; obj = rb_class_alloc(klass); rb_obj_call_init_kw(obj, argc, argv, RB_PASS_CALLED_KEYWORDS); return obj; } class Foo def initialize(a, b) :hello end end Foo.new(1, 2) # => #<Foo ...> Actual Implementation Class#new implementation in C C Ruby Ruby
  9. Different Calling Conventions Crossing language border requires translation C Ruby

    Ruby Translate to C calling convention Translate back to Ruby calling convention
  10. Where is time spent? Arrows are impacted by calling convention

    C Ruby Ruby Ruby Ruby Ruby Time Savings
  11. Where is time spent? Methods are impacted by VM speed

    / method lookup (inline caches) C Ruby Ruby Ruby Ruby Ruby Time Savings
  12. Where is “baz”? Methods must be located, and we can

    cache the location class Foo def bar self.baz end def baz end end Where is the baz method?
  13. Where is “baz”? Method lookup routine def find_method(receiver, method_name) #

    Loop through ancestors until we find the method while !receiver.method_defined?(method_name) receiver = receiver.superclass end # Return the method receiver.method(method_name) end def call_method(receiver, method_name) # Call the method find_method(receiver, method_name).call end
  14. Method lookup can be linear We have to scan ancestors

    looking for the method class A; def baz; end end class B < A; end class C < B; end class D < C; end class E < D; end class F < E; end # .... class Foo < Z def bar self.baz end end ??
  15. Receiver type is our cache key Create a cache entry

    where the key is the class, and the value is the method class A; def baz; end end class B < A; end class C < B; end class D < C; end class E < D; end class F < E; end # .... class Foo < Z def bar self.baz end end Cache key is the class of self: Foo Cache value is the method entry
  16. Cache: Stored inline with bytecode That’s why it’s called “inline”

    cache == disasm: #<ISeq:[email protected]:11 (11,2)-(13,5)> 0000 putself ( 12)[LiCa] 0001 opt_send_without_block <calldata!mid:baz, argc:0, FCALL|ARGS_SIMPLE> 0003 leave ( 13)[Re]
  17. Cache: Stored inline with bytecode That’s why it’s called “inline”

    cache == disasm: #<ISeq:[email protected]:11 (11,2)-(13,5)> 0000 putself ( 12)[LiCa] 0001 opt_send_without_block <calldata!mid:baz, argc:0, FCALL|ARGS_SIMPLE> 0003 leave ( 13)[Re]
  18. Inline Cache Object Graph bar method points at cache, cache

    points at method entry class A; def baz; end end class B < A; end class C < B; end class D < C; end class E < D; end class F < E; end # .... class Foo < Z def bar self.baz end end bar method A#baz inline cache A#baz method Weak Reference Ruby Objects class A
  19. Measure In line Cache Allocations Objects can be allocated on

    the fi rst call, but not subsequent def baz; end def bar # First time this is called, an object gets allocated self.baz end def measure x = GC.stat(:total_allocated_objects) yield GC.stat(:total_allocated_objects) - x end measure { } # heat p measure { bar } # => 2 p measure { bar } # => 0 First call allocates
  20. Inline Cache: Only holds one object Inline caches are “monomorphic”,

    they only cache one item class A; def baz; end end class B < A; end class C < B; end class D < C; end class E < D; end class F < E; end # .... class Foo < Z def bar self.baz end end bar method A#baz inline cache A#baz method Weak Reference class A
  21. Inline Cache: Only holds one object Inline caches are “monomorphic”,

    they only cache one item class A def bar; end end class B def bar; end end def run_it(obj); obj.bar; end run_it(A.new) run_it(B.new) run_it method A#bar inline cache A#bar method B#bar inline cache B#bar method
  22. Cache hit / miss examples Cache size is one, so

    we only hit when repeating types def run_it(obj); obj.bar; end run_it(A.new) # cache miss run_it(A.new) # cache hit run_it(A.new) # cache hit run_it(A.new) # cache hit run_it(B.new) # cache miss run_it(B.new) # cache hit run_it(B.new) # cache hit run_it(B.new) # cache hit run_it(A.new) # cache miss run_it(B.new) # cache miss run_it(A.new) # cache miss run_it(B.new) # cache miss run_it(A.new) # cache miss run_it(B.new) # cache miss
  23. Cache hit / miss comparison Compare always hitting to never

    hitting class A; def bar; end; end class B; def bar; end; end def run_it(obj); obj.bar; end a = A.new b = B.new Benchmark.ips { |x| x.report("always hit") { run_it(a); run_it(a); run_it(a); run_it(a) run_it(a); run_it(a); run_it(a); run_it(a) run_it(a); run_it(a); run_it(a); run_it(a) run_it(a); run_it(a); run_it(a); run_it(a) } x.report("never hit") { run_it(a); run_it(b); run_it(a); run_it(b) run_it(a); run_it(b); run_it(a); run_it(b) run_it(a); run_it(b); run_it(a); run_it(b) run_it(a); run_it(b); run_it(a); run_it(b) } x.compare! } always call with a alternate between a and b
  24. Benchmark Results Never hitting is about 40% slower $ ruby

    test.rb ruby 3.4.1 (2025-08-06 revision 48d4efcb85) +PRISM [arm64-darwin24] Warming up -------------------------------------- always hit 319.277k i/100ms never hit 224.288k i/100ms Calculating ------------------------------------- always hit 3.189M (± 2.4%) i/s (313.58 ns/i) - 15.964M in 5.008714s never hit 2.280M (± 1.8%) i/s (438.55 ns/i) - 11.439M in 5.018054s Comparison: always hit: 3188993.5 i/s never hit: 2280264.0 i/s - 1.40x slower $ ruby test.rb ruby 3.4.1 (2025-08-06 revision 48d4efcb85) +PRISM [arm64-darwin24] Warming up -------------------------------------- always hit 319.277k i/100ms never hit 224.288k i/100ms Calculating ------------------------------------- always hit 3.189M (± 2.4%) i/s (313.58 ns/i) - 15.964M in 5.008714s never hit 2.280M (± 1.8%) i/s (438.55 ns/i) - 11.439M in 5.018054s Comparison: always hit: 3188993.5 i/s never hit: 2280264.0 i/s - 1.40x slower
  25. VALUE rb_class_new_instance_pass_kw(int argc, const VALUE *argv, VALUE klass) { VALUE

    obj; obj = rb_class_alloc(klass); rb_obj_call_init_kw(obj, argc, argv, RB_PASS_CALLED_KEYWORDS); return obj; } class Foo def initialize(a, b) :hello end end Foo.new(1, 2) # => #<Foo ...> Initialize is called from C Class#new implementation in C
  26. rb_obj_call_init_kw Calls your initialize method void rb_obj_call_init_kw(VALUE obj, int argc,

    const VALUE *argv, int kw_splat) { PASS_PASSED_BLOCK_HANDLER(); rb_funcallv_kw(obj, idInitialize, argc, argv, kw_splat); } Call a m ethod O n this object M ethod nam e Param eters
  27. rb_funcallv_kw Calls any method VALUE rb_funcallv_kw(VALUE recv, ID mid, int

    argc, const VALUE *argv, int kw_splat) { VM_ASSERT(ruby_thread_has_gvl_p()); return rb_call(recv, mid, argc, argv, kw_splat ? CALL_FCALL_KW : CALL_FCALL); } VALUE rb_funcallv_kw(VALUE recv, ID mid, int argc, const VALUE *argv, int kw_splat) { VM_ASSERT(ruby_thread_has_gvl_p()); return rb_call(recv, mid, argc, argv, kw_splat ? CALL_FCALL_KW : CALL_FCALL); }
  28. rb_call Just passes everything to rb_call0 static inline VALUE rb_call(VALUE

    recv, ID mid, int argc, const VALUE *argv, call_type scope) { rb_execution_context_t *ec = GET_EC(); return rb_call0(ec, recv, mid, argc, argv, scope, ec->cfp->self); } static inline VALUE rb_call(VALUE recv, ID mid, int argc, const VALUE *argv, call_type scope) { rb_execution_context_t *ec = GET_EC(); return rb_call0(ec, recv, mid, argc, argv, scope, ec->cfp->self); } Why is it named rb_call0????? ??
  29. rb_call0 Is actually complicated, and I’ve omitted some of it

    static inline VALUE rb_call0(rb_execution_context_t *ec, VALUE recv, ID mid, int argc, const VALUE *argv, call_type call_scope, VALUE self) { /* Snip */ struct rb_callinfo ci; scope_to_ci(scope, mid, argc, &ci); const struct rb_callcache *cc = gccct_method_search(ec, recv, mid, &ci); /* Snip */ return vm_call0_cc(ec, recv, mid, argc, argv, cc, kw_splat); } static inline VALUE rb_call0(rb_execution_context_t *ec, VALUE recv, ID mid, int argc, const VALUE *argv, call_type call_scope, VALUE self) { /* Snip */ struct rb_callinfo ci; scope_to_ci(scope, mid, argc, &ci); const struct rb_callcache *cc = gccct_method_search(ec, recv, mid, &ci); /* Snip */ return vm_call0_cc(ec, recv, mid, argc, argv, cc, kw_splat); }
  30. gccct_method_search gccct: Global call cache cache table static inline const

    struct rb_callcache * gccct_method_search(rb_execution_context_t *ec, VALUE recv, ID mid, const struct rb_callinfo *ci) { /* snip */ // search global method cache unsigned int index = (unsigned int)(gccct_hash(klass, mid) % VM_GLOBAL_CC_CACHE_TABLE_SIZE); rb_vm_t *vm = rb_ec_vm_ptr(ec); const struct rb_callcache *cc = vm->global_cc_cache_table[index]; if (LIKELY(cc)) { if (LIKELY(vm_cc_class_check(cc, klass))) { const rb_callable_method_entry_t *cme = vm_cc_cme(cc); if (LIKELY(!METHOD_ENTRY_INVALIDATED(cme) && cme->called_id == mid)) { VM_ASSERT(vm_cc_check_cme(cc, rb_callable_method_entry(klass, mid))); return cc; } } } return gccct_method_search_slowpath(vm, klass, index, ci); } static inline const struct rb_callcache * gccct_method_search(rb_execution_context_t *ec, VALUE recv, ID mid, const struct rb_callinfo *ci) { /* snip */ // search global method cache unsigned int index = (unsigned int)(gccct_hash(klass, mid) % VM_GLOBAL_CC_CACHE_TABLE_SIZE); rb_vm_t *vm = rb_ec_vm_ptr(ec); const struct rb_callcache *cc = vm->global_cc_cache_table[index]; if (LIKELY(cc)) { if (LIKELY(vm_cc_class_check(cc, klass))) { const rb_callable_method_entry_t *cme = vm_cc_cme(cc); if (LIKELY(!METHOD_ENTRY_INVALIDATED(cme) && cme->called_id == mid)) { VM_ASSERT(vm_cc_check_cme(cc, rb_callable_method_entry(klass, mid))); return cc; } } } return gccct_method_search_slowpath(vm, klass, index, ci); } static inline const struct rb_callcache * gccct_method_search(rb_execution_context_t *ec, VALUE recv, ID mid, const struct rb_callinfo *ci) { /* snip */ // search global method cache unsigned int index = (unsigned int)(gccct_hash(klass, mid) % VM_GLOBAL_CC_CACHE_TABLE_SIZE); rb_vm_t *vm = rb_ec_vm_ptr(ec); const struct rb_callcache *cc = vm->global_cc_cache_table[index]; if (LIKELY(cc)) { if (LIKELY(vm_cc_class_check(cc, klass))) { const rb_callable_method_entry_t *cme = vm_cc_cme(cc); if (LIKELY(!METHOD_ENTRY_INVALIDATED(cme) && cme->called_id == mid)) { VM_ASSERT(vm_cc_check_cme(cc, rb_callable_method_entry(klass, mid))); return cc; } } } return gccct_method_search_slowpath(vm, klass, index, ci); } static inline const struct rb_callcache * gccct_method_search(rb_execution_context_t *ec, VALUE recv, ID mid, const struct rb_callinfo *ci) { /* snip */ // search global method cache unsigned int index = (unsigned int)(gccct_hash(klass, mid) % VM_GLOBAL_CC_CACHE_TABLE_SIZE); rb_vm_t *vm = rb_ec_vm_ptr(ec); const struct rb_callcache *cc = vm->global_cc_cache_table[index]; if (LIKELY(cc)) { if (LIKELY(vm_cc_class_check(cc, klass))) { const rb_callable_method_entry_t *cme = vm_cc_cme(cc); if (LIKELY(!METHOD_ENTRY_INVALIDATED(cme) && cme->called_id == mid)) { VM_ASSERT(vm_cc_check_cme(cc, rb_callable_method_entry(klass, mid))); return cc; } } } return gccct_method_search_slowpath(vm, klass, index, ci); } Lookup cache in global table Is the cache entry good? Slow path
  31. C methods calling Ruby methods It has “method caches” Stored

    in a global table Cache entries limited by table size
  32. A “convention” for connect methods Calling convention de fi nes

    where parameters and return values will be def bar(x, y, z) x + y + z end def foo bar(1, 2, 3) end Arguments are stored in a certain place Parameters are read from a certain place Return value is stored in a certain place Return value is read from a certain place
  33. def bar(a, b, c) a + b + c end

    def foo bar(5, 7, 9) end Ruby Code Calling Convention: Caller’s Side Stack values become method parameters def bar(a, b, c) a + b + c end def foo bar(5, 7, 9) end Ruby Code Push 5 Instructions Stack 5 Push 7 Push 9 7 9 Call bar
  34. def bar(a, b, c) a + b + c end

    def foo bar(5, 7, 9) end Ruby Code Calling Convention: Callee’s side Stack values become method parameters Instructions Stack 5 Getlocal -3 Getlocal -2 Add Getlocal -1 Add 7 9 5 12 21 Return 7 9
  35. Calling Convention: Keyword Arguments Stack values become method parameters def

    bar(a:, b:, c:) a + b + c end def foo bar(a: 5, b: 7, c: 9) end Ruby Code Instructions Stack 5 Getlocal -3 Getlocal -2 Add Getlocal -1 Add 7 9 5 12 21 Return
  36. Calling Convention: Keyword Arguments Stack values become method parameters def

    bar(a:, b:, c:) a + b + c end def foo bar(c: 9, a: 5, b: 7) end Ruby Code Instructions Stack 9 Push 9 Push 5 Push 7 Call bar 5 7
  37. Keyword Arguments Normally don’t require allocations def bar(a:, b:, c:)

    a + b + c end def foo bar(c: 9, a: 5, b: 7) end count_allocs { foo } # heat p count_allocs { foo } # => 0 Ruby Ruby
  38. Keyword Arguments + Initialize We expect one allocation, Foo, but

    there are two allocations class Foo def initialize(a:, b:, c:) end end def foo Foo.new(c: 9, a: 5, b: 7) end count_allocs { foo } # heat p count_allocs { foo } # => 2 Ruby C Ruby
  39. Hash is allocated across language barrier We have to convert

    kwargs to a hash, then back to stack positions 5 7 9 { a: 5, b: 7, c: 9 } 5 7 9
  40. Class#new First implementation class Class def new(...) instance = allocate

    instance.initialize(...) instance end end Initialize is private!
  41. Trying to call Initialize won’t work Because it’s a private

    method ?? class Foo def initialize end private def bar; end end Foo.allocate.initialize $ ruby thing.rb thing.rb:10:in '<main>': private method 'initialize' called for an instance of Foo (NoMethodError) Foo.allocate.initialize ^^^^^^^^^^^
  42. Class#new, second attempt Also doesn’t work class Class def new(...)

    instance = allocate instance.send(:initialize, ...) instance end end No “send” on BasicObject
  43. BasicObject is very Basic It doesn’t support send! obj =

    BasicObject.new obj.send :initialize $ ruby thing.rb thing.rb:2:in '<main>': undefined method 'send' for an instance of BasicObject (NoMethodError) obj.send :initialize ^^^^^
  44. Special flags on send instruction FCALL means “ignore method visibility”

    class Foo def initialize bar end private def bar; end end Ruby Code == disasm: #<ISeq:[email protected]:2 (2,2)-(4,5)> 0000 putself ( 3)[LiCa] 0001 send <calldata!mid:bar, argc:0, FCALL|VCALL|ARGS_SIMPLE>, nil 0004 leave ( 4)[Re] VM Instructions == disasm: #<ISeq:[email protected]:2 (2,2)-(4,5)> 0000 putself ( 3)[LiCa] 0001 send <calldata!mid:bar, argc:0, FCALL|VCALL|ARGS_SIMPLE>, nil 0004 leave ( 4)[Re] VM Instructions
  45. Class#new Instructions We need an FCALL fl ag == disasm:

    #<ISeq:[email protected]:2 (2,2)-(6,5)> local table (size: 2, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1]) [ 2] "..."@0 [ 1] instance@1 0000 putself ( 3)[LiCa] 0001 send <calldata!mid:allocate, argc:0, FCALL|VCALL|ARGS_SIMPLE>, nil 0004 setlocal instance@1, 0 0007 getlocal instance@1, 0 ( 4)[Li] 0010 getlocal "..."@0, 0 0013 sendforward <calldata!mid:initialize, argc:0, FORWARDING>, nil 0016 pop 0017 getlocal instance@1, 0 ( 5)[Li] 0020 leave ( 6)[Re] == disasm: #<ISeq:[email protected]:2 (2,2)-(6,5)> local table (size: 2, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1]) [ 2] "..."@0 [ 1] instance@1 0000 putself ( 3)[LiCa] 0001 send <calldata!mid:allocate, argc:0, FCALL|VCALL|ARGS_SIMPLE>, nil 0004 setlocal instance@1, 0 0007 getlocal instance@1, 0 ( 4)[Li] 0010 getlocal "..."@0, 0 0013 sendforward <calldata!mid:initialize, argc:0, FORWARDING>, nil 0016 pop 0017 getlocal instance@1, 0 ( 5)[Li] 0020 leave ( 6)[Re] Add FCALL here
  46. Class#new take three With a primitive class Class def new(...)

    obj = allocate Primitive.send_delegate!( obj, :initialize, ...) obj end end
  47. == disasm: #<ISeq:new@<internal:class>:2 (2,2)-(8,5)> local table (size: 2, argc: 0

    [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1]) [ 2] "..."@0 [ 1] obj@1 0000 putself ( 3)[LiCa] 0001 opt_send_without_block <calldata!mid:allocate, argc:0, FCALL|VCALL|ARGS_SIMPLE> 0003 setlocal_WC_0 obj@1 0005 getlocal_WC_0 obj@1 ( 5)[Li] 0007 getlocal_WC_0 "..."@0 ( 4) 0009 sendforward <calldata!mid:initialize, argc:0, FCALL|FORWARDING>, nil 0012 pop 0013 getlocal_WC_0 obj@1 ( 7)[Li] 0015 leave ( 8)[Re] Class#new take 3 Instructions == disasm: #<ISeq:new@<internal:class>:2 (2,2)-(8,5)> local table (size: 2, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1]) [ 2] "..."@0 [ 1] obj@1 0000 putself ( 3)[LiCa] 0001 opt_send_without_block <calldata!mid:allocate, argc:0, FCALL|VCALL|ARGS_SIMPLE> 0003 setlocal_WC_0 obj@1 0005 getlocal_WC_0 obj@1 ( 5)[Li] 0007 getlocal_WC_0 "..."@0 ( 4) 0009 sendforward <calldata!mid:initialize, argc:0, FCALL|FORWARDING>, nil 0012 pop 0013 getlocal_WC_0 obj@1 ( 7)[Li] 0015 leave ( 8)[Re] FCALL
  48. Class#new take three Doesn’t pass tests class Class def new(...)

    obj = allocate Primitive.send_delegate!( obj, :initialize, ...) obj end end People monkey patch allocate
  49. Monkeypatch to allocate is ignored This code works fi ne

    class Class def allocate raise "hahahaha" end end class Foo end Foo.new # works fine
  50. Allocate an object without calling “allocate” Primitive is not impacted

    by monkey patches class Class def new(...) obj = Primitive.rb_class_alloc2 Primitive.send_delegate!( obj, :initialize, ...) obj end end class Class def new(...) obj = Primitive.rb_class_alloc2 Primitive.send_delegate!( obj, :initialize, ...) obj end end
  51. Class#new instruction sequence Allocating a new object: 8 instructions obj

    = Primitive.rb_class_alloc2 Primitive.send_delegate!( obj, :initialize, ...) obj Ruby Code allocate Instructions Stack setlocal getlocal getlocal send pop getlocal leave new instance ...
  52. Ruby Code is YARV Our Ruby code is converted to

    YARV instructions class Class def new(...) obj = Primitive.rb_class_alloc2 Primitive.send_delegate!( obj, :initialize, ...) obj end end Ruby Code YARV Instructions allocate send pop leave dup Foo.new Ruby Code putobject Foo send
  53. YARV Instructions Paste “Class#new” code at the call site allocate

    send pop dup Foo.new Ruby Code putobject Foo
  54. Compiler Modifications - PUSH_SEND_R(ret, location, method_id, INT2FIX(orig_argc), block_iseq, INT2FIX(flags), kw_arg);

    + LABEL *not_basic_new = NEW_LABEL(location.line); + LABEL *not_basic_new_finish = NEW_LABEL(location.line); + + bool inline_new = ISEQ_COMPILE_DATA(iseq)->option->specialized_instruction && + method_id == rb_intern("new") && + call_node->block == NULL && + !(flags & VM_CALL_ARGS_BLOCKARG); + + if (inline_new) { + if (LAST_ELEMENT(ret) == opt_new_prelude) { + PUSH_INSN(ret, location, putnil); + PUSH_INSN(ret, location, swap); + } + else { + ELEM_INSERT_NEXT(opt_new_prelude, &new_insn_body(iseq, location.line, location.node_id, BIN(swap), 0)->link); + ELEM_INSERT_NEXT(opt_new_prelude, &new_insn_body(iseq, location.line, location.node_id, BIN(putnil), 0)->link); + } + + // Jump unless the receiver uses the "basic" implementation of "new" + VALUE ci; + if (flags & VM_CALL_FORWARDING) { + ci = (VALUE)new_callinfo(iseq, method_id, orig_argc + 1, flags, kw_arg, 0); + } + else { + ci = (VALUE)new_callinfo(iseq, method_id, orig_argc, flags, kw_arg, 0); + } + + PUSH_INSN2(ret, location, opt_new, ci, not_basic_new); + LABEL_REF(not_basic_new); + // optimized path + PUSH_SEND_R(ret, location, rb_intern("initialize"), INT2FIX(orig_argc), block_iseq, INT2FIX(flags | VM_CALL_FCALL), kw_arg); + PUSH_INSNL(ret, location, jump, not_basic_new_finish); + Is this a call to “new”? Insert special instructions If not new, jump to slow path
  55. “Object.new” Before and after inlining Instructions for “new” are inserted

    at the call site > ruby --dump=insns -e'Object.new' == disasm: #<ISeq:<main>@-e:1 (1,0)-(1,10)> 0000 opt_getconstant_path <ic:0 Object> ( 1)[Li] 0002 opt_send_without_block <calldata!mid:new, argc:0, ARGS_SIMPLE> 0004 leave Before > ./ruby --dump=insns -e'Object.new' == disasm: #<ISeq:<main>@-e:1 (1,0)-(1,10)> 0000 opt_getconstant_path <ic:0 Object> ( 1)[Li] 0002 putnil 0003 swap 0004 opt_new <calldata!mid:new, argc:0, ARGS_SIMPLE>, 11 0007 opt_send_without_block <calldata!mid:initialize, argc:0, FCALL|ARGS_SIMPLE> 0009 jump 14 0011 opt_send_without_block <calldata!mid:new, argc:0, ARGS_SIMPLE> 0013 swap 0014 pop 0015 leave After
  56. Keyword Arguments Only one object allocated class Foo def initialize(a:,

    b:, c:) end end def foo Foo.new(c: 9, a: 5, b: 7) end count_allocs { foo } # heat p count_allocs { foo } # => 1 Only One Allocation!!!
  57. Positional Parameters Allocations per Second by Ruby version Allocations Per

    Second 0 9500000 19000000 28500000 38000000 Number of Parameters 0 1 2 3 4 5 6 7 8 9 10 Ruby 3.5 + inlining Ruby 3.4
  58. Keyword Parameters Allocations per second by Ruby version Allocations Per

    Second 0 10000000 20000000 30000000 40000000 Number of Parameters 0 1 2 3 4 5 6 7 8 9 10 Ruby 3.5+inlining Ruby 3.4
  59. Positional Parameters + Varied Classes Allocations per second by Ruby

    version? (varying allocated class) Allocations per Second 0 9500000 19000000 28500000 38000000 Number of Parameters 0 1 2 3 4 5 6 7 8 9 10 Ruby 3.5+Inlining Ruby 3.4
  60. Keyword Parameters + Varied Classes Allocations per second by Ruby

    version? (varying allocated class) Allocations Per Second 0 10000000 20000000 30000000 40000000 Number of Parameters 0 1 2 3 4 5 6 7 8 9 10 Ruby 3.5+Inlining Ruby 3.4
  61. Measure ISeq size How many bytes does the “alloc” method

    use? require "objspace" def alloc Object.new end m = method(:alloc) insn = RubyVM::InstructionSequence.of(insn) puts ObjectSpace.memsize_of(insn) Ruby 3.5 + inlining: 656 bytes Ruby 3.4: 544 bytes +122 Bytes
  62. Stack Trace is Different Class#new is missing class Foo def

    initialize puts caller end end def hello Foo.new end hello > ruby test.rb test.rb:8:in 'Class#new' test.rb:8:in 'Object#hello' test.rb:11:in '<main>' Ruby 3.4 > ./ruby test.rb test.rb:8:in 'Object#hello' test.rb:11:in '<main>' Ruby 3.5 + inlining
无痕是什么意思 女性检查甲功是什么病 耿耿于怀什么意思 豆蔻年华什么意思 失眠吃什么中药
adh医学上是什么意思 葫芦是什么生肖 什么是抗性淀粉 牙周炎吃什么药最有效 希五行属什么
西游记是什么时候写的 子夜是指什么时间 gin什么意思 做梦车丢了有什么预兆 烦躁是什么意思
什么叫菩提心 共情是什么意思 枸杞和什么搭配壮阳 畈是什么意思 川芎有什么功效
呃逆是什么意思hcv9jop1ns8r.cn 拟物是什么意思hcv7jop4ns6r.cn 嘉兴有什么大学hcv9jop5ns2r.cn 薄熙来犯了什么罪hcv8jop3ns8r.cn 早上适合做什么运动zsyouku.com
葛根粉有什么作用hcv7jop9ns7r.cn 回族女人为什么戴头巾hcv7jop6ns2r.cn 乙酰氨基葡萄糖苷酶阳性什么意思hcv8jop0ns5r.cn 一什么天安门hcv8jop8ns6r.cn 囊肿和肿瘤有什么区别hcv8jop1ns1r.cn
b型血和ab型血的孩子是什么血型hcv8jop4ns0r.cn 亚麻籽是什么hcv7jop9ns0r.cn 情感障碍是什么意思tiangongnft.com 什么是红颜知己hcv9jop6ns7r.cn 做完人流需要注意什么hcv8jop2ns4r.cn
鼻窦炎用什么药好hcv7jop9ns3r.cn 寡淡是什么意思hcv8jop6ns1r.cn 冰淇淋是什么做的hcv8jop6ns3r.cn 尿尿泡沫多是什么原因hcv8jop6ns3r.cn 助听器什么牌子好用ff14chat.com
百度