ruby-mine

exploring the mine

Bench - Ein Frühsommer-Hack

von janfri am 11.06.2008 (11 Uhr)

Wann hast Du das letzte mal die Bibliothek benchmark aus der Ruby Standardbibliothek benutzt? Kannst Du Dich noch erinnern, wie die Methoden heißen, die man braucht, um einen Benchmarkbericht zu erzeugen?

Ich muss jedes mal neu in der API-Dokumentation nachschauen, weil die Bibliothek für mich nicht gerade intuitiv ist, so dass ich die Benutzung immer wieder vergesse. Aber dem kann man ja Abhilfe schaffen…

So ist Bench entstanden, eine kleine DSL um die benchmark-Bibliothek.

Installiert wird das ganze wie gewohnt mit: gem install bench bzw. sudo gem install bench.

So, wie funktioniert das nun? Es gibt nur zwei neue Kommandos: benchmark und run. Mit dem ersten definiert man ein neues Benchmark-Sample und mit run werden die Benchmarks dann durchgeführt und der bekannte Report ausgegeben.

Beispiel (adaptiert aus dem Beispiel aus der Pickaxe 2. Auflage S. 657):
require 'bench'

string = 'Stormy Weather'
m = string.method(:length)

benchmark 'code' do
  m.call
end

benchmark 'send' do
  string.send(:length)
end

benchmark 'eval' do
  eval "string.length"
end

run 10_000
ergibt bei mir
          user     system      total        real
code  0.020000   0.000000   0.020000 (  0.015588)
send  0.010000   0.000000   0.010000 (  0.015256)
eval  0.030000   0.010000   0.040000 (  0.055029)

Ich finde den Code wesentlich einfacher als das Original, aber das ist sicherlich Geschmackssache. ;-)

Mit Bench sind auch interaktive Benchmarks in irb kein Problem:
>> require 'bench'

>> benchmark 'simple' do
>>   /ll/ =~ 'hello world'
>> end

>> benchmark 'freezed' do
>>   /ll/.freeze =~ 'hello world'
>> end

>> run 1000
             user     system      total        real
simple   0.000000   0.000000   0.000000 (  0.003960)
freezed  0.010000   0.000000   0.010000 (  0.004870)

>> run 1000
             user     system      total        real
simple   0.010000   0.000000   0.010000 (  0.003969)
freezed  0.000000   0.000000   0.000000 (  0.004624)

>> # Machen wir mehr Iterationen
>> run 10000
             user     system      total        real
simple   0.060000   0.000000   0.060000 (  0.058049)
freezed  0.060000   0.000000   0.060000 (  0.058636)

>> run 100000
             user     system      total        real
simple   0.500000   0.000000   0.500000 (  0.502427)
freezed  0.540000   0.000000   0.540000 (  0.533421)

>> # Fügen wir ein weiteres Benchmark-Sample hinzu
>> RE = /ll/

>> benchmark 'constant' do
>>   RE =~ 'hello world'
>> end

>> run 100000
              user     system      total        real
simple    0.510000   0.000000   0.510000 (  0.504704)
freezed   0.530000   0.000000   0.530000 (  0.536948)
constant  0.560000   0.000000   0.560000 (  0.554470)

Man kann also run auch mehrfach hintereinander aufrufen, um statistische Ausreißer in den ermittelten Zeiten zu indentifizieren. Außerdem kann man run mit unterschiedlichen Werten für die Iterationen aufrufen, um aussagekräftigere Ergebnisse zu erzielen.

Der eigentliche Code für die DSL ist recht simpel und kurz:
require 'benchmark'

module Bench

  # Create a new benchmark sample <tt>name</tt>. 
  # Within the block you specify the code to execute.
  def benchmark name, &block
    bm = { 'name' => name, 'code' => block }
    Bench.queue << bm
    "#{Bench.queue.size} benchmark samples" # nice return for use with irb ;)
  end

  # Runs the benchmarks. Parameter <tt>count</tt> is the number of iterations
  # each sample code should be repeated.
  def run count=1
    label_width = Bench.queue.inject(0) {|max, bm| len = bm['name'].length; len > max ? len : max}
    Benchmark.bm(label_width) do |x|
      Bench.queue.each do |bm|
        name, code = bm['name'], bm['code'] 
        x.report(name) { count.times(&code) }
      end
    end
  end

  module_function :benchmark, :run

  @queue = []

  class << self
    attr_reader :queue
  end

end

include Bench

Gehostet wird die jeweils aktuelle Version des Codes auf Gitorious. Außerdem ist Bench auf Rubyforge zu finden.

EDIT: Code aktualisiert.


Kommentar schreiben

Name (notwendig)

Mail (wird nicht veröffentlicht)

Webseite


Kommentare

  1. Kai schrieb am 11.06.2008 (15 Uhr)

    Schöne Sache. Ich find Benchmark jetzt nicht unbedingt soo kompliziert, dass ich da dringend auf so ein Programm gewartet habe. Aber die Vereinfachung ist sehr praktisch. Mich hats immer aufgeregt wenn ich in der irb ein Benchmark-Block mit etlichen Reports geschrieben habe und im letzten Report dann durch Verklicken ein Syntax-Error entstand ^^°

  2. Johannes schrieb am 12.06.2008 (09 Uhr)

    Ui, das ist nett. Zeigt doch nochmal wie einfach es (anscheinend) ist mit Ruby sich seine Welt/Arbeitsumgebung einfach anzupassen...

  3. murphy schrieb am 13.06.2008 (16 Uhr)

    Seltsame...warum hat er nicht einfach alles unter class << self gestellt?

  4. janfri schrieb am 16.06.2008 (21 Uhr)

    "murphy: "Seltsame...warum hat er nicht einfach alles unter class << self gestellt?"

    Was meinst Du?
    • Toplevel? Habe ich nicht gemacht, um Namenskollisionen zu vermeiden: run ist nicht ganz ungewöhnlich, so kann ich es immer noch explizit mittels Bench.run aufrufen.
    • Im Modul? Ich wollte sowohl Mixin-Funktionalität als auch Modulfunktionalität haben, um es für den einfachen Zugriff in Object zu inkludieren und im Falle eines Namenskonfliktes als Mudulfunktion zugreifen zu können (s.o.).
    • Noch was anderes?"