หากคุณค้นหาการปรับแต่ง Ruby ใน Google โดยไม่ทราบเรื่องราวเบื้องหลัง คุณอาจคิดได้ว่าการปรับแต่งนั้นช้า
ตามที่เสนอในตอนแรก การปรับแต่งจะช้า พวกเขาจะทำให้มันเป็นไปไม่ได้สำหรับล่ามเพื่อเพิ่มประสิทธิภาพสิ่งต่าง ๆ เช่นการค้นหาวิธีการ
แต่การนำการปรับแต่งไปใช้จริงนั้นค่อนข้างจำกัดกว่าข้อเสนอเดิมเล็กน้อย ดังนั้นฉันจึงคิดว่ามันน่าสนใจที่จะเรียกใช้ชุดการวัดประสิทธิภาพในการปรับแต่งอย่างที่มีอยู่ใน Ruby ในปัจจุบัน
TL;DR
การปรับแต่งไม่ได้ช้า หรืออย่างน้อยก็ดูเหมือนจะไม่ช้าไปกว่าวิธีการ "ปกติ"
ตัวโหลดจำลอง
เราจะทำการเรียกวิธีการเปรียบเทียบ เราจึงต้องใช้วิธีการสองสามวิธี
ที่นี่ ฉันกำลังสร้างวิธีการเล็กๆ น้อยๆ สองเวอร์ชัน หนึ่งคือ "ปกติ" และอีกอันอยู่ในการปรับแต่ง:
# As our "dummy load" we're going to create shrugs.
# 1 shrug == "¯\_(ツ)_/¯"
# 2 shrugs == "¯\_(ツ)_/¯¯\_(ツ)_/¯"
# ...etc.
SHRUG = "¯\_(ツ)_/¯"
# We'll make a refinement that generates shrugs
module Shruggable
refine Fixnum do
def shrugs
SHRUG * self
end
end
end
# ...and we'll make a normal method that also generates shrugs
def integer_to_shrugs(n)
SHRUG * n
end
เราไม่สามารถใช้การปรับแต่งได้โดยตรง ต้องเปิดใช้งานผ่าน using
คำแถลง. ดังนั้นฉันจะสร้างสองคลาสที่ทำงานเหมือนกัน คนหนึ่งใช้การปรุงแต่ง อีกคนไม่ใช้
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
เกณฑ์มาตรฐาน
ฉันต้องการทราบว่าการสร้างอินสแตนซ์ออบเจ็กต์โดยใช้การปรับแต่งหรือเรียกเมธอดที่เพิ่มผ่านการปรับแต่งจะช้ากว่านี้ไหม
การวัดประสิทธิภาพทั้งหมดดำเนินการด้วย MRI 2.2.2 บน OSX El Capitan
การสร้างวัตถุ
คีย์เวิร์ด "ใช้" ทำให้คลาสเริ่มต้นช้าลงหรือไม่ ไม่นะ
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
เรียกเมธอด
การปรับแต่งส่งผลต่อความเร็วในการค้นหาวิธีการ "ปกติ" หรือไม่ ไม่.
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
การใช้วิธีการจากการปรับแต่งช้ากว่าการใช้วิธีการ "ปกติ" ที่เทียบเท่าหรือไม่ ไม่นะ
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
การซ้อนสำรับ
ฉันสามารถทำอะไรเพื่อทำให้เกณฑ์เปรียบเทียบการปรับแต่งช้ากว่าการควบคุมของฉันได้หรือไม่ ¯\_(ツ)_/¯
# Does repeated evaluation of the `using` keyword affect performance. Only slightly.
# This is an unfair test, but I really wanted to force refinements to be slower
# in 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
รหัสเต็ม
หากคุณต้องการเรียกใช้การวัดประสิทธิภาพด้วยตนเอง นี่คือรหัส
require 'benchmark/ips'
# As our "dummy load" we're going to create shrugs.
# 1 shrug == "¯\_(ツ)_/¯"
# 2 shrugs == "¯\_(ツ)_/¯¯\_(ツ)_/¯"
# ...etc.
SHRUG = "¯\_(ツ)_/¯"
# We'll make a refinement that generates shrugs
module Shruggable
refine Fixnum do
def shrugs
SHRUG * self
end
end
end
# ...and we'll make a normal method that also generates shrugs
def integer_to_shrugs(n)
SHRUG * n
end
# Now we'll define two classes. The first uses refinments. The second doesn't.
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
# Does the "using" keyword make a class slower to initialize? Nope.
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
# Do refinements affect "normal" method lookup speed? Nope.
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
# Is using a method from a refinement slower than using an equivalent "normal" method? Nope.
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
# Does repeated evaluation of the `using` keyword affect performance. Only slightly.
# This is an unfair test, but I really wanted to force refinements to be slower
# in 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