Permalink
Browse files

More benchmark tools

* bench_atomic_1 is used to measure 1 combination
* graph_atomic is used to run series, using bench_atomic_1

The idea is to calculate series by varying one parameter
which gives much more meaninful results.
  • Loading branch information...
1 parent 8171865 commit 3477f0794201062661265211fc236df139d1611e @zimbatm zimbatm committed Sep 9, 2011
Showing with 206 additions and 0 deletions.
  1. +138 −0 examples/bench_atomic_1.rb
  2. +68 −0 examples/graph_atomic_bench.rb
View
@@ -0,0 +1,138 @@
+#!/usr/bin/env ruby
+
+$: << File.expand_path('../../lib', __FILE__)
+
+require 'optparse'
+require 'thread'
+require 'benchmark'
+
+require 'atomic'
+
+Thread.abort_on_exception = true
+
+$conf = {
+ :lock => "atomic",
+ :num_threads => 100,
+ :count => 100_000,
+ :count_per_thread => nil,
+ :slow => nil,
+}
+
+OptionParser.new do |opts|
+ opts.on("-c", "--count NUM") do |n|
+ $conf[:count] = n.to_i
+ end
+ opts.on("-p", "--count-per-thread") do |n|
+ $conf[:count_per_thread] = n.to_i
+ end
+ opts.on("-t", "--num-threads NUM") do |n|
+ $conf[:num_threads] = n.to_i
+ end
+ opts.on("-s", "--slow NUM") do |n|
+ $conf[:slow] = n.to_i
+ end
+ opts.on("-l", "--lock atomic|mutex") do |x|
+ $conf[:lock] = x
+ end
+ opts.on("-h", "--help"){ puts opts; exit }
+end.parse!(ARGV)
+
+unless $conf[:count_per_thread]
+ $conf[:count_per_thread] = $conf[:count] / $conf[:num_threads]
+end
+$conf.delete(:count)
+
+if $conf[:slow].to_i > 0
+ require 'digest/md5'
+ def slow_down
+ $conf[:slow].times do |i|
+ Digest::MD5.hexdigest(i.to_s)
+ end
+ end
+
+ ret = []
+ 10.times do
+ m = Benchmark.measure{ slow_down }
+ ret << m.real
+ end
+
+ $conf[:slow_time] = [ret.min, ret.max]
+else
+ def slow_down; end
+end
+
+$stderr.puts $conf.inspect
+
+def para_prepare(&block)
+ num_threads = $conf[:num_threads]
+ count = $conf[:count_per_thread]
+
+ if num_threads % 2 > 0
+ raise ArgumentError, "num_threads must be a multiple of two"
+ end
+
+ # Keep those threads together
+ tg = ThreadGroup.new
+
+ num_threads.times do |i|
+ diff = (i % 2 == 0) ? 1 : -1
+
+ t = Thread.new do
+ nil until $go
+ count.times do
+ yield diff
+ end
+ end
+
+ tg.add(t)
+ end
+
+ # Make sure all threads are started
+ while tg.list.find{|t| t.status != "run"}
+ Thread.pass
+ end
+
+ # For good measure
+ GC.start
+
+ $go = false
+
+ tg
+end
+
+
+
+$tg = nil
+if $conf[:lock] == "atomic"
+ $atom = Atomic.new(0)
+ $tg = para_prepare do |diff|
+ $atom.update do |x|
+ slow_down
+ x + diff
+ end
+ end
+else
+ $lock = Mutex.new
+ $value = 0
+ $tg = para_prepare do |diff|
+ $lock.synchronize do
+ slow_down
+ $value += diff
+ end
+ end
+end
+
+
+# Run !
+#
+# NOTE: It seems to me that this measurement method
+# is sensible to how the system dispatches his resources.
+#
+# More precise caluclation could be done using
+# getrusage's times
+ret = Benchmark.measure do
+ $go = true
+ $tg.list.each{|t| t.join}
+ $go = false
+end
+puts ret.real
@@ -0,0 +1,68 @@
+#!/usr/bin/env ruby
+require 'optparse'
+
+conf = {
+ :vary => "threads",
+ :lock => "atomic"
+}
+
+OptionParser.new do |opts|
+ opts.on("-l", "--lock atomic|mutex") do |l|
+ conf[:lock] = l
+ end
+ opts.on("-v", "--vary threads|speed") do |v|
+ conf[:vary] = v
+ end
+ opts.on("-h", "--help"){ puts opts; exit }
+end.parse!(ARGV)
+
+result = File.open("results_#{conf[:lock]}_#{conf[:vary]}.csv", "w")
+
+
+if conf[:vary] == "threads"
+ # Vary the number of concurrent threads that update the value.
+ #
+ # There is a total count of 1mio updates that is distributed
+ # between the number of threads.
+ #
+ # A pair number of threads is used so that even add and odd substract 1.
+ # This avoid creating instances for Bignum since the number should
+ # stay in the Fixnum range.
+ #
+ (1..100).each do |i|
+ i = i * 2
+
+ ret = []
+ 10.times do
+ ret << `ruby ./bench_atomic_1.rb -l #{conf[:lock]} -t #{i}`.to_f
+ end
+
+ line = ([i] + ret).join(', ')
+
+ puts line
+ result.puts line
+ end
+elsif conf[:vary] == "speed"
+ # Varies the execution time of the update block
+ # by using long calulation (MD5)
+ #
+ # NOTE: Thread.pass and sleep() are not usable by the atomic
+ # lock. It needs to run the whole block without hitting
+ # another atomic update otherwise it has to retry
+ #
+ # The expected result is that the atomic lock's performance
+ # will hit a certain threshold where it will be worse than mutexes.
+ #
+ (1..30).each do |i|
+
+ ret = []
+ 10.times do
+ ret << `ruby ./bench_atomic_1.rb -l #{conf[:lock]} -s #{i}`.to_f
+ end
+
+ line = ([i] + ret).join(', ')
+
+ puts line
+ result.puts line
+ end
+end

0 comments on commit 3477f07

Please sign in to comment.