Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Ruby

Phân tích hiệu năng Ruby Refinements: Benchmarks & Insights

Nếu bạn search Google cho Ruby refinements mà không biết back story, bạn có thể nghĩ refinements chậm.

Như đề xuất ban đầu, refinements sẽ chậm. Chúng làm interpreter không thể optimize method lookup.

Nhưng implementation thực tế của refinements hơi bị giới hạn hơn proposal gốc. Vì vậy tôi nghĩ chạy series benchmarks trên refinements như hiện tại trong Ruby sẽ thú vị.

TL;DR

Refinements không chậm. Hoặc ít nhất chúng không hề chậm hơn methods "bình thường".

Dummy load

Chúng ta benchmark method calls. Vì vậy cần vài methods.

Ở đây, tôi tạo hai version của method ngớ ngẩn. Một "bình thường" và một trong refinement:

# As our "dummy load" we're going to create shrugs. # 1 shrug == "¯\_(ツ)_/¯" # 2 shrugs == "¯\_(ツ)_/¯¯\_(ツ)_/¯" # ...etc. SHRUG = "¯\_(ツ)_/¯" # Tạo refinement generate shrugs module Shruggable refine Fixnum do def shrugs SHRUG * self end end end # ...và method bình thường cũng generate shrugs def integer_to_shrugs(n) SHRUG * n end 

Chúng ta không dùng refinement trực tiếp. Nó phải activated qua statement using. Vì vậy tôi tạo hai class hành xử giống nhau. Một dùng refinement, cái kia không.

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 

Benchmarks

Tôi muốn biết có chậm hơn instantiate objects dùng refinements, hay gọi methods added via refinements.

Tất cả benchmarks chạy với MRI 2.2.2 trên OSX El Capitan.

Object creation

Từ khóa "using" làm class chậm hơn khởi tạo? Không.

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 ảnh hưởng "bình thường" method lookup speed? Không.

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 

Dùng method từ refinement chậm hơn dùng "bình thường" method tương đương? Không.

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

Tôi có thể làm gì để refinement benchmark chậm hơn control? ¯\_(ツ)_/¯

 # Có lẽ repeated evaluation của `using` keyword ảnh hưởng performance. Chỉ hơi. # Test không công bằng, nhưng tôi thực sự muốn force refinements chậm hơn # trong MỘT 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 

Full code

Nếu bạn muốn tự chạy benchmark, đây là code.

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