Spec-Zone.ru › Ruby 3.4

модуль Benchmark

Модуль Benchmark предоставляет методы для измерения и вывода времени, затраченного на выполнение кода Ruby.

  • Измерение времени создания строки, заданной выражением "a"*1_000_000_000:

    require 'benchmark'
    
    puts Benchmark.measure { "a"*1_000_000_000 }
    

    На моей машине (OSX 10.8.3, i5 1.7 GHz) это даёт:

    0.350000   0.400000   0.750000 (  0.835234)

    В этом отчёте показаны процессорное время пользователя, процессорное время системы, сумма процессорного времени пользователя и системы, а также фактическое прошедшее время. Единица измерения времени — секунды.

  • Проведение нескольких экспериментов последовательно с помощью метода bm:

    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
    

    Результат:

        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)
  • Продолжая предыдущий пример, добавим метку к каждому отчёту:

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

Результат:

              user     system      total        real
for:      1.010000   0.000000   1.010000 (  1.015688)
times:    1.000000   0.000000   1.000000 (  1.003611)
upto:     1.030000   0.000000   1.030000 (  1.028098)
  • Результаты некоторых тестов производительности зависят от порядка выполнения элементов. Эти различия обусловлены затратами на выделение памяти и сборку мусора. Чтобы избежать таких расхождений, предусмотрен метод bmbm. Например, для сравнения способов сортировки массива чисел с плавающей точкой:

    require 'benchmark'
    
    array = (1..1000000).map { rand }
    
    Benchmark.bmbm do |x|
      x.report("sort!") { array.dup.sort! }
      x.report("sort")  { array.dup.sort  }
    end
    

    Результат:

    Rehearsal -----------------------------------------
    sort!   1.490000   0.010000   1.500000 (  1.490520)
    sort    1.460000   0.000000   1.460000 (  1.463025)
    -------------------------------- total: 2.960000sec
    
                user     system      total        real
    sort!   1.460000   0.000000   1.460000 (  1.460465)
    sort    1.450000   0.010000   1.460000 (  1.448327)
  • Вывод статистики последовательных экспериментов с уникальными метками с помощью метода benchmark:

    require 'benchmark'
    include Benchmark         # we need the CAPTION and FORMAT constants
    
    n = 5000000
    Benchmark.benchmark(CAPTION, 7, FORMAT, ">total:", ">avg:") do |x|
      tf = x.report("for:")   { for i in 1..n; a = "1"; end }
      tt = x.report("times:") { n.times do   ; a = "1"; end }
      tu = x.report("upto:")  { 1.upto(n) do ; a = "1"; end }
      [tf+tt+tu, (tf+tt+tu)/3]
    end
    

    Результат:

                 user     system      total        real
    for:      0.950000   0.000000   0.950000 (  0.952039)
    times:    0.980000   0.000000   0.980000 (  0.984938)
    upto:     0.950000   0.000000   0.950000 (  0.946787)
    >total:   2.880000   0.000000   2.880000 (  2.883764)
    >avg:     0.960000   0.000000   0.960000 (  0.961255)

Константы

CAPTION

Строка заголовка по умолчанию (заголовок над выводимым временем).

FORMAT

Строка формата по умолчанию для отображения времени. См. также Benchmark::Tms#format.

VERSION

Открытые методы класса

benchmark (caption = "", label_width = nil, format = nil, *labels) { |report| ... } Показать исходный код
# File lib/benchmark.rb, line 170
def benchmark(caption = "", label_width = nil, format = nil, *labels) # :yield: report
  sync = $stdout.sync
  $stdout.sync = true
  label_width ||= 0
  label_width += 1
  format ||= FORMAT
  report = Report.new(label_width, format)
  results = yield(report)

  print " " * report.width + caption unless caption.empty?
  report.list.each { |i|
    print i.label.to_s.ljust(report.width)
    print i.format(report.format, *format)
  }

  Array === results and results.grep(Tms).each {|t|
    print((labels.shift || t.label || "").ljust(label_width), t.format(format))
  }
  report.list
ensure
  $stdout.sync = sync unless sync.nil?
end

Вызывает блок с объектом Benchmark::Report, который можно использовать для сбора результатов отдельных тестов производительности и вывода отчёта по ним. Для меток в каждой строке резервируется label_width начальных пробелов. В начале отчёта выводится caption, а для форматирования каждой строки используется format. (Примечание: caption должна оканчиваться символом новой строки; пример см. в значении Benchmark::Tms::CAPTION по умолчанию.)

Возвращает массив объектов Benchmark::Tms.

Если блок возвращает массив объектов Benchmark::Tms, они будут использованы для форматирования дополнительных строк вывода. Если указаны параметры labels, они будут использованы для присвоения меток этим дополнительным строкам.

Примечание: Другие методы предоставляют более простой интерфейс и подходят почти для всех задач тестирования производительности. См. примеры в Benchmark, а также методы bm и bmbm.

Пример:

require 'benchmark'
include Benchmark          # we need the CAPTION and FORMAT constants

n = 5000000
Benchmark.benchmark(CAPTION, 7, FORMAT, ">total:", ">avg:") do |x|
  tf = x.report("for:")   { for i in 1..n; a = "1"; end }
  tt = x.report("times:") { n.times do   ; a = "1"; end }
  tu = x.report("upto:")  { 1.upto(n) do ; a = "1"; end }
  [tf+tt+tu, (tf+tt+tu)/3]
end

Выводит:

              user     system      total        real
for:      0.970000   0.000000   0.970000 (  0.970493)
times:    0.990000   0.000000   0.990000 (  0.989542)
upto:     0.970000   0.000000   0.970000 (  0.972854)
>total:   2.930000   0.000000   2.930000 (  2.932889)
>avg:     0.976667   0.000000   0.976667 (  0.977630)
bm (label_width = 0, *labels) { |report| ... } Показать исходный код
# File lib/benchmark.rb, line 215
def bm(label_width = 0, *labels, &blk) # :yield: report
  benchmark(CAPTION, label_width, FORMAT, *labels, &blk)
end

Простой интерфейс к методу benchmark. Метод bm формирует последовательные отчёты с метками. Параметры label_width и labels имеют то же значение, что и для benchmark.

require 'benchmark'

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

Выводит:

              user     system      total        real
for:      0.960000   0.000000   0.960000 (  0.957966)
times:    0.960000   0.000000   0.960000 (  0.960423)
upto:     0.950000   0.000000   0.950000 (  0.954864)
bmbm (width = 0) { |job| ... } Показать исходный код
# File lib/benchmark.rb, line 257
def bmbm(width = 0) # :yield: job
  job = Job.new(width)
  yield(job)
  width = job.width + 1
  sync = $stdout.sync
  $stdout.sync = true

  # rehearsal
  puts 'Rehearsal '.ljust(width+CAPTION.length,'-')
  ets = job.list.inject(Tms.new) { |sum,(label,item)|
    print label.ljust(width)
    res = Benchmark.measure(&item)
    print res.format
    sum + res
  }.format("total: %tsec")
  print " #{ets}\n\n".rjust(width+CAPTION.length+2,'-')

  # take
  print ' '*width + CAPTION
  job.list.map { |label,item|
    GC.start
    print label.ljust(width)
    Benchmark.measure(label, &item).tap { |res| print res }
  }
ensure
  $stdout.sync = sync unless sync.nil?
end

Иногда результаты тестов производительности искажаются, поскольку код, выполненный раньше, сталкивается с другими накладными расходами на сборку мусора, чем код, выполненный позже. Метод bmbm пытается свести этот эффект к минимуму, выполняя тесты дважды: сначала в качестве репетиции, чтобы стабилизировать среду выполнения, а затем по-настоящему. Перед началом каждого реального замера выполняется GC.start; затраты на это не включаются в замеры. Однако возможности метода bmbm ограничены, и результаты не гарантированно защищены от влияния сборки мусора и других факторов.

Поскольку метод bmbm выполняет тесты в два прохода, он может вычислить необходимую ширину метки.

require 'benchmark'

array = (1..1000000).map { rand }

Benchmark.bmbm do |x|
  x.report("sort!") { array.dup.sort! }
  x.report("sort")  { array.dup.sort  }
end

Выводит:

Rehearsal -----------------------------------------
sort!   1.440000   0.010000   1.450000 (  1.446833)
sort    1.440000   0.000000   1.440000 (  1.448257)
-------------------------------- total: 2.890000sec

            user     system      total        real
sort!   1.460000   0.000000   1.460000 (  1.458065)
sort    1.450000   0.000000   1.450000 (  1.455963)

Метод bmbm передаёт блоку объект Benchmark::Job и возвращает массив объектов Benchmark::Tms.

measure (label = "") { || ... } Показать исходный код
# File lib/benchmark.rb, line 302
def measure(label = "") # :yield:
  t0, r0 = Process.times, Process.clock_gettime(Process::CLOCK_MONOTONIC)
  yield
  t1, r1 = Process.times, Process.clock_gettime(Process::CLOCK_MONOTONIC)
  Benchmark::Tms.new(t1.utime  - t0.utime,
                     t1.stime  - t0.stime,
                     t1.cutime - t0.cutime,
                     t1.cstime - t0.cstime,
                     r1 - r0,
                     label)
end

Возвращает время, затраченное на выполнение заданного блока, в виде объекта Benchmark::Tms. Принимает параметр label.

require 'benchmark'

n = 1000000

time = Benchmark.measure do
  n.times { a = "1" }
end
puts time

Выводит:

0.220000   0.000000   0.220000 (  0.227313)
realtime () { || ... } Показать исходный код
# File lib/benchmark.rb, line 321
def realtime # :yield:
  r0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
  yield
  Process.clock_gettime(Process::CLOCK_MONOTONIC) - r0
end

Возвращает фактическое прошедшее время, затраченное на выполнение заданного блока. Единица измерения времени — секунды.

Benchmark.realtime { "a" * 1_000_000_000 }
#=> 0.5098029999935534

Закрытые методы экземпляра

benchmark (caption = "", label_width = nil, format = nil, *labels) { |report| ... } Показать исходный код
# File lib/benchmark.rb, line 170
def benchmark(caption = "", label_width = nil, format = nil, *labels) # :yield: report
  sync = $stdout.sync
  $stdout.sync = true
  label_width ||= 0
  label_width += 1
  format ||= FORMAT
  report = Report.new(label_width, format)
  results = yield(report)

  print " " * report.width + caption unless caption.empty?
  report.list.each { |i|
    print i.label.to_s.ljust(report.width)
    print i.format(report.format, *format)
  }

  Array === results and results.grep(Tms).each {|t|
    print((labels.shift || t.label || "").ljust(label_width), t.format(format))
  }
  report.list
ensure
  $stdout.sync = sync unless sync.nil?
end

Вызывает блок с объектом Benchmark::Report, который можно использовать для сбора результатов отдельных тестов производительности и вывода отчёта по ним. Для меток в каждой строке резервируется label_width начальных пробелов. В начале отчёта выводится caption, а для форматирования каждой строки используется format. (Примечание: caption должна оканчиваться символом новой строки; пример см. в значении Benchmark::Tms::CAPTION по умолчанию.)

Возвращает массив объектов Benchmark::Tms.

Если блок возвращает массив объектов Benchmark::Tms, они будут использованы для форматирования дополнительных строк вывода. Если указаны параметры labels, они будут использованы для присвоения меток этим дополнительным строкам.

Примечание: Другие методы предоставляют более простой интерфейс и подходят почти для всех задач тестирования производительности. См. примеры в Benchmark, а также методы bm и bmbm.

Пример:

require 'benchmark'
include Benchmark          # we need the CAPTION and FORMAT constants

n = 5000000
Benchmark.benchmark(CAPTION, 7, FORMAT, ">total:", ">avg:") do |x|
  tf = x.report("for:")   { for i in 1..n; a = "1"; end }
  tt = x.report("times:") { n.times do   ; a = "1"; end }
  tu = x.report("upto:")  { 1.upto(n) do ; a = "1"; end }
  [tf+tt+tu, (tf+tt+tu)/3]
end

Выводит:

              user     system      total        real
for:      0.970000   0.000000   0.970000 (  0.970493)
times:    0.990000   0.000000   0.990000 (  0.989542)
upto:     0.970000   0.000000   0.970000 (  0.972854)
>total:   2.930000   0.000000   2.930000 (  2.932889)
>avg:     0.976667   0.000000   0.976667 (  0.977630)
bm (label_width = 0, *labels) { |report| ... } Показать исходный код
# File lib/benchmark.rb, line 215
def bm(label_width = 0, *labels, &blk) # :yield: report
  benchmark(CAPTION, label_width, FORMAT, *labels, &blk)
end

Простой интерфейс к методу benchmark. Метод bm формирует последовательные отчёты с метками. Параметры label_width и labels имеют то же значение, что и для benchmark.

require 'benchmark'

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

Выводит:

              user     system      total        real
for:      0.960000   0.000000   0.960000 (  0.957966)
times:    0.960000   0.000000   0.960000 (  0.960423)
upto:     0.950000   0.000000   0.950000 (  0.954864)
bmbm (width = 0) { |job| ... } Показать исходный код
# File lib/benchmark.rb, line 257
def bmbm(width = 0) # :yield: job
  job = Job.new(width)
  yield(job)
  width = job.width + 1
  sync = $stdout.sync
  $stdout.sync = true

  # rehearsal
  puts 'Rehearsal '.ljust(width+CAPTION.length,'-')
  ets = job.list.inject(Tms.new) { |sum,(label,item)|
    print label.ljust(width)
    res = Benchmark.measure(&item)
    print res.format
    sum + res
  }.format("total: %tsec")
  print " #{ets}\n\n".rjust(width+CAPTION.length+2,'-')

  # take
  print ' '*width + CAPTION
  job.list.map { |label,item|
    GC.start
    print label.ljust(width)
    Benchmark.measure(label, &item).tap { |res| print res }
  }
ensure
  $stdout.sync = sync unless sync.nil?
end

Иногда результаты тестов производительности искажаются, поскольку код, выполненный раньше, сталкивается с другими накладными расходами на сборку мусора, чем код, выполненный позже. Метод bmbm пытается свести этот эффект к минимуму, выполняя тесты дважды: сначала в качестве репетиции, чтобы стабилизировать среду выполнения, а затем по-настоящему. Перед началом каждого реального замера выполняется GC.start; затраты на это не включаются в замеры. Однако возможности метода bmbm ограничены, и результаты не гарантированно защищены от влияния сборки мусора и других факторов.

Поскольку метод bmbm выполняет тесты в два прохода, он может вычислить необходимую ширину метки.

require 'benchmark'

array = (1..1000000).map { rand }

Benchmark.bmbm do |x|
  x.report("sort!") { array.dup.sort! }
  x.report("sort")  { array.dup.sort  }
end

Выводит:

Rehearsal -----------------------------------------
sort!   1.440000   0.010000   1.450000 (  1.446833)
sort    1.440000   0.000000   1.440000 (  1.448257)
-------------------------------- total: 2.890000sec

            user     system      total        real
sort!   1.460000   0.000000   1.460000 (  1.458065)
sort    1.450000   0.000000   1.450000 (  1.455963)

Метод bmbm передаёт блоку объект Benchmark::Job и возвращает массив объектов Benchmark::Tms.

measure (label = "") { || ... } Показать исходный код
# File lib/benchmark.rb, line 302
def measure(label = "") # :yield:
  t0, r0 = Process.times, Process.clock_gettime(Process::CLOCK_MONOTONIC)
  yield
  t1, r1 = Process.times, Process.clock_gettime(Process::CLOCK_MONOTONIC)
  Benchmark::Tms.new(t1.utime  - t0.utime,
                     t1.stime  - t0.stime,
                     t1.cutime - t0.cutime,
                     t1.cstime - t0.cstime,
                     r1 - r0,
                     label)
end

Возвращает время, затраченное на выполнение заданного блока, в виде объекта Benchmark::Tms. Принимает параметр label.

require 'benchmark'

n = 1000000

time = Benchmark.measure do
  n.times { a = "1" }
end
puts time

Выводит:

0.220000   0.000000   0.220000 (  0.227313)
realtime () { || ... } Показать исходный код
# File lib/benchmark.rb, line 321
def realtime # :yield:
  r0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
  yield
  Process.clock_gettime(Process::CLOCK_MONOTONIC) - r0
end

Возвращает фактическое прошедшее время, затраченное на выполнение заданного блока. Единица измерения времени — секунды.

Benchmark.realtime { "a" * 1_000_000_000 }
#=> 0.5098029999935534

Ruby Core © 1993–2025 Yukihiro Matsumoto
Licensed under the Ruby License.
Ruby Standard Library © contributors
Licensed under their own licenses.

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API