Computer >> 컴퓨터 >  >> 프로그램 작성 >> Ruby

루비 정제 벤치마킹

뒷이야기를 모른 채 Google에서 Ruby 개선을 검색하면 개선이 느리다는 생각이 들 수 있습니다.

원래 제안된 대로 개선이 느렸을 것입니다. 그들은 인터프리터가 메소드 조회와 같은 것들을 최적화하는 것을 불가능하게 만들었을 것입니다.

그러나 개선 사항의 실제 구현은 원래 제안보다 약간 더 제한적입니다. 그래서 나는 오늘날 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

벤치마크

구체화를 사용하여 개체를 인스턴스화하거나 구체화를 통해 추가된 메서드를 호출하는 것이 더 느린지 알고 싶었습니다.

모든 벤치마크는 OSX El Capitan에서 MRI 2.2.2로 실행되었습니다.

객체 생성

"using" 키워드로 인해 클래스 초기화 속도가 느려집니까? 아닙니다.

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