Ruby中的基准方法Benchmarking

Benchmarking methods in Ruby

我正在尝试对一组这样的计算进行基准测试-

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def benchmark(func, index, array)
    start = Time.now
    func(index, array)
    start - Time.now #returns time taken to perform func
end

def func1(index, array)
    #perform computations based on index and array
end

def func2(index, array)
    #more computations....
end

benchmark(func1, index1, array1)
benchmark(func1, index2, array2)

现在我想知道我怎样才能做到这一点。我试过这个例子,但被吐出来了

1
`func1': wrong number of arguments (0 for 2) (ArgumentError)

如果我尝试-

1
benchmark(func1(index1, array1), index1, array1)

它吐出…

1
undefined method `func' for main:Object (NoMethodError)

我看到一个关于它的类似问题,但它是针对Python的。将带有参数的函数传递给Python中的另一个函数?有人能帮忙吗?谢谢。


在Ruby中,可以在方法名后面不包含空括号的情况下调用方法,如下所示:

1
2
3
4
5
def func1
  puts"Hello!"
end

func1 # Calls func1 and prints"Hello!"

因此,在编写benchmark(func1, index1, array1)时,实际上是在无参数地调用func1并将结果传递给benchmark,而不是像您预期的那样将func1传递给基准函数。为了将func1作为对象传递,可以使用Method方法为函数获取包装对象,如下所示:

1
2
3
4
5
6
def func1
  puts"Hello!"
end

m = method(:func1) # Returns a Method object for func1
m.call(param1, param2)

但大多数时候,这不是你真正想做的事情。Ruby支持一个名为blocks的结构,它更适合于此目的。您可能已经熟悉了each迭代器ruby用于循环数组的块。下面是将块用于您的用例的情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def benchmark
  start = Time.now
  yield
  Time.now - start # Returns time taken to perform func
end

# Or alternately:
# def benchmark(&block)
#   start = Time.now
#   block.call
#   Time.now - start # Returns time taken to perform func
# end

def func1(index, array)
    # Perform computations based on index and array
end

def func2(index, array)
    # More computations....
end

benchmark { func1(index1, array1) }
benchmark { func1(index1, array2) }

实际上,Ruby有一个标准的基准测试库,称为Benchmark,它使用块,可能已经完全满足了您的需要。

用途:

1
2
3
4
5
6
7
8
require 'benchmark'

n = 5000000
Benchmark.bm do |x|
  x.report { for i in 1..n; a ="1"; end }
  x.report { n.times do   ; a ="1"; end }
  x.report { 1.upto(n) do ; a ="1"; end }
end

The result:

1
2
3
4
    user     system      total        real
1.010000   0.000000   1.010000 (  1.014479)
1.000000   0.000000   1.000000 (  0.998261)
0.980000   0.000000   0.980000 (  0.981335)

似乎您正试图将Ruby方法用作函数。这很少见,但完全有可能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def benchmark(func, index, array)
    start = Time.now
    func.call(index, array) # <= (C)
    start - Time.now
end

def func1(index, array)
    #perform computations based on index and array
end

def func2(index, array)
    #more computations....
end

benchmark(method(:func1), index1, array1) # <= (A)
benchmark(method(:func1), index2, array2) # <= (B)

对代码的更改如下:

a,b)使用以前定义的方法创建一个Method对象。Method对象类似于Proc对象,因为它有一个call方法,允许您稍后调用它。在您的代码中,当您只是使用func1而不是method(:func1)时,发生的情况是您立即调用该方法并将其结果传递给benchmark,而不是将函数本身传递给benchmark以供以后调用。

c)采用call法。Ruby不允许像其他语言那样使用括号随意调用变量作为函数,但是如果它是一个使用call方法的对象类型,例如MethodProc方法,则可以在准备好时使用call来调用函数,而不是在尝试将它传递给另一个方法之前立即调用它,就像你原来的代码一样。


我就是这样做的。我首先创建一个包含所有要测试的方法的模块:

1
2
3
4
5
6
7
8
9
10
11
12
13
module Methods
  def bob(array)
    ...
  end

  def gretta(array
    ...
  end

  def wilma(array)
    ...
  end
end

然后我将该模块包括在内,并将这些方法放入一个数组中:

1
2
3
include Methods
@methods = Methods.public_instance_methods(false)
  #=> [:bob, :gretta, :wilma]

这样我就可以对@methods中的每个方法meth执行send(meth, *args)

下面是这个方法的一个例子。您将看到我还有代码来检查所有方法是否返回相同的结果并格式化输出。

主要程序可能如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
test_sizes.each do |n|
  puts"
n = #{n}"

  arr = test_array(n)
  Benchmark.bm(@indent) do |bm|
    @methods.each do |m|
      bm.report m.to_s do
        send(m, arr)
     end
    end
  end
end

我使用一个模块,这样就可以添加、删除或重命名要进行基准测试的方法,而无需接触模块外部的任何代码。


请检查我的新gem,它可以分析您的Ruby方法(实例或类)-https://github.com/igorkasyanchuk/benchmark_方法。

不再有这样的代码:

1
2
3
t = Time.now
user.calculate_report
puts Time.now - t

现在你可以做到:

1
benchmark :calculate_report # in class

然后调用你的方法

1
user.calculate_report

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class SimpleBenchmarker
    def self.go(how_many=1, &block)
        $sorted_time = Array.new
        puts"
--------------Benchmarking started----------------"

        start_time = Time.now
        puts"Start Time:\t#{start_time}

"

        how_many.times do |a|
            print"."
            block.call
        end
        print"

"

        end_time = Time.now
        puts"End Time:\t#{end_time}
"

        puts"-------------Benchmarking finished----------------

"

        result_time = end_time - start_time
        puts"Total time:\t\t#{result_time.round(3)} seconds

"

        puts"The run times for the iterations from shortest to longest was: #{$sorted_time.sort.join("s,")}s

"

    end
end
    print"How many times?"
    t = gets.to_i
SimpleBenchmarker.go t do
    time = rand(0.1..1.0).round(3)
    $sorted_time.push(time)
    sleep time
end