ค้น Google "Ruby refinements" โดยไม่รู้ back story อาจคิดว่า refinements ช้า
Original proposal refinements จะช้า ทำให้ interpreter optimize method lookup ไม่ได้
แต่ actual implementation จำกัดกว่า proposal ผมคิด benchmark refinements ใน Ruby วันนี้น่าสนใจ
TL;DR
Refinements ไม่ช้า หรืออย่างน้อยก็ไม่ช้ากว่า "normal" methods
The Dummy Load
Benchmark method calls ต้องมี methods
สร้างสองเวอร์ชัน silly method หนึ่ง "normal" อีกใน refinement:
# "Dummy load" สร้าง shrugs
# 1 shrug == "¯\_(ツ)_/¯"
# 2 shrugs == "¯\_(ツ)_/¯¯\_(ツ)_/¯"
SHRUG = "¯\_(ツ)_/¯"
# Refinement สร้าง shrugs
module Shruggable
refine Fixnum do
def shrugs
SHRUG * self
end
end
end
# Normal method สร้าง shrugs
def integer_to_shrugs(n)
SHRUG * n
end
ไม่ได้ใช้ refinement ตรง ต้อง activate ผ่าน using สร้างสองคลาส identical หนึ่ง用 refinement อีกไม่:
class TestUsing
using Shruggable
def noop; end
def shrug
10.shrugs
end
end
class TestWithoutUsing
def noop; end
def shrug
integer_to_shrugs(10)
end
end
The Benchmarks
อยากรู้ instantiate objects 用 refinements ช้ากว่าไหม หรือ call methods added via refinements ช้ากว่า
Benchmarks รัน MRI 2.2.2 บน OSX El Capitan
Object Creation
using keyword ทำ class ช้ากว่า initialize ไหม? ไม่
Benchmark.ips do |bm|
bm.report("class initialization") { TestUsing.new }
bm.report("class initialization WITH using") { TestWithoutUsing.new }
bm.compare!
end
# Calculating -------------------------------------
# class initialization 142.929k i/100ms
# class initialization WITH using 145.323k i/100ms
# -------------------------------------------------
# class initialization 5.564M (± 8.3%) i/s - 27.728M
# class initialization WITH using 5.619M (± 7.4%) i/s - 28.047M
# Comparison:
# class initialization WITH using: 5618601.3 i/s
# class initialization: 5564116.5 i/s - 1.01x slower
Method Calls
Refinements affect "normal" method lookup speed ไหม? ไม่
Benchmark.ips do |bm|
bm.report("run method") { TestUsing.new.noop }
bm.report("run method in class WITH using") { TestWithoutUsing.new.noop }
bm.compare!
end
# Calculating -------------------------------------
# run method 141.905k i/100ms
# run method in class WITH using 144.435k i/100ms
# -------------------------------------------------
# run method 5.010M (± 6.4%) i/s - 24.975M
# run method in class WITH using 5.086M (± 5.3%) i/s - 25.421M
# Comparison:
# run method in class WITH using: 5086262.3 i/s
# run method: 5010273.6 i/s - 1.02x slower
用 refinement method ช้ากว่า equivalent "normal" method ไหม? ไม่
Benchmark.ips do |bm|
bm.report("shrug") { TestUsing.new.shrug }
bm.report("shrug via refinement") { TestWithoutUsing.new.shrug }
bm.compare!
end
# Calculating -------------------------------------
# shrug 96.089k i/100ms
# shrug via refinement 95.559k i/100ms
# -------------------------------------------------
# shrug 1.825M (± 9.3%) i/s - 9.128M
# shrug via refinement 1.929M (± 6.2%) i/s - 9.651M
# Comparison:
# shrug via refinement: 1928841.5 i/s
# shrug: 1825069.4 i/s - 1.06x slower
Stacking the Deck
ทำอะไรได้ไหมทำ refinement benchmark ช้ากว่า control? ¯\_(ツ)_/¯
# repeated evaluation ของ `using` keyword affect performance ไหม? สlightly
# unfair test แต่อยาก force refinements ช้าใน SOME use case :)
Benchmark.ips do |bm|
bm.report("inline shrug") { integer_to_shrugs(10) }
bm.report("inline shrug via refinement") do
using Shruggable
10.shrugs
end
bm.compare!
end
# Calculating -------------------------------------
# inline shrug 100.460k i/100ms
# inline shrug via refinement 72.131k i/100ms
# -------------------------------------------------
# inline shrug 2.507M (± 5.2%) i/s - 12.557M
# inline shrug via refinement 1.498M (± 4.3%) i/s - 7.502M
# Comparison:
# inline shrug: 2506663.9 i/s
# inline shrug via refinement: 1497747.6 i/s - 1.67x slower
The Full Code
อยากรัน benchmark เอง โค้ดเต็ม:
require 'benchmark/ips'
SHRUG = "¯\_(ツ)_/¯"
module Shruggable
refine Fixnum do
def shrugs
SHRUG * self
end
end
end
def integer_to_shrugs(n)
SHRUG * n
end
class TestUsing
using Shruggable
def noop; end
def shrug; 10.shrugs; end
end
class TestWithoutUsing
def noop; end
def shrug; integer_to_shrugs(10); end
end
Benchmark.ips do |bm|
bm.report("class initialization") { TestUsing.new }
bm.report("class initialization WITH using") { TestWithoutUsing.new }
bm.compare!
end
Benchmark.ips do |bm|
bm.report("run method") { TestUsing.new.noop }
bm.report("run method in class WITH using") { TestWithoutUsing.new.noop }
bm.compare!
end
Benchmark.ips do |bm|
bm.report("shrug") { TestUsing.new.shrug }
bm.report("shrug via refinement") { TestWithoutUsing.new.shrug }
bm.compare!
end
Benchmark.ips do |bm|
bm.report("inline shrug") { integer_to_shrugs(10) }
bm.report("inline shrug via refinement") do
using Shruggable
10.shrugs
end
bm.compare!
end