Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

Performance Analysis of Ruby Refinements: Benchmarks & Insights

ค้น 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