240 lines
8.0 KiB
Ruby
240 lines
8.0 KiB
Ruby
|
#!/usr/bin/env ruby
|
||
|
$LOAD_PATH[0, 0] = File.join(File.dirname(__FILE__), '..', 'lib')
|
||
|
#
|
||
|
# review load path
|
||
|
|
||
|
# Exploratory/Experimental/Exponential tests for performance tuning
|
||
|
|
||
|
require 'rubygems'
|
||
|
require 'test-unit'
|
||
|
require 'json'
|
||
|
require 'mongo'
|
||
|
require 'benchmark'
|
||
|
|
||
|
$calibration_runtime = 0.1
|
||
|
$target_runtime = 5.0
|
||
|
$db_name = "benchmark"
|
||
|
$collection_name = "exp_series"
|
||
|
|
||
|
class TestExpPerformance < Test::Unit::TestCase
|
||
|
|
||
|
def array_nest(base, level, obj)
|
||
|
return obj if level == 0
|
||
|
return Array.new(base, array_nest(base, level - 1, obj))
|
||
|
end
|
||
|
|
||
|
def hash_nest(base, level, obj)
|
||
|
return obj if level == 0
|
||
|
h = Hash.new
|
||
|
(0...base).each{|i| h[i.to_s] = hash_nest(base, level - 1, obj)}
|
||
|
return h
|
||
|
end
|
||
|
|
||
|
def estimate_iterations(db, coll, setup, teardown)
|
||
|
start_time = Time.now
|
||
|
iterations = 1
|
||
|
utime = 0.0
|
||
|
while utime <= $calibration_runtime do
|
||
|
setup.call(db, coll)
|
||
|
btms = Benchmark.measure do
|
||
|
(0...iterations).each do
|
||
|
yield
|
||
|
end
|
||
|
end
|
||
|
utime = btms.utime
|
||
|
teardown.call(db, coll)
|
||
|
iterations *= 2
|
||
|
end
|
||
|
etime = (Time.now - start_time)
|
||
|
return [(iterations.to_f * $target_runtime / utime).to_i, etime]
|
||
|
end
|
||
|
|
||
|
def measure_iterations(db, coll, setup, teardown, iterations)
|
||
|
setup.call(db, coll)
|
||
|
btms = Benchmark.measure { iterations.times { yield } }
|
||
|
teardown.call(db, coll)
|
||
|
return [btms.utime, btms.real]
|
||
|
end
|
||
|
|
||
|
def valuate(db, coll, setup, teardown)
|
||
|
iterations, etime = estimate_iterations(db, coll, setup, teardown) { yield }
|
||
|
utime, rtime = measure_iterations(db, coll, setup, teardown, iterations) { yield }
|
||
|
return [iterations, utime, rtime, etime]
|
||
|
end
|
||
|
|
||
|
def power_test(base, max_power, db, coll, generator, setup, operation, teardown)
|
||
|
return (0..max_power).collect do |power|
|
||
|
size, doc = generator.call(base, power)
|
||
|
iterations, utime, rtime, etime = valuate(db, coll, setup, teardown) { operation.call(coll, doc) }
|
||
|
result = {
|
||
|
"base" => base,
|
||
|
"power" => power,
|
||
|
"size" => size,
|
||
|
"exp2" => Math.log2(size).to_i,
|
||
|
"generator" => generator.name.to_s,
|
||
|
"operation" => operation.name.to_s,
|
||
|
"iterations" => iterations,
|
||
|
"utime" => utime.round(2),
|
||
|
"etime" => etime.round(2),
|
||
|
"rtime" => rtime.round(2),
|
||
|
"ops" => (iterations.to_f / utime.to_f).round(1),
|
||
|
"usec" => (1000000.0 * utime.to_f / iterations.to_f).round(1),
|
||
|
# "git" => git, # thinking
|
||
|
# "datetime" +> Time.now, # thinking
|
||
|
# "hostname" => hostname, # thinking
|
||
|
# "nbench-int" => nbench.int, # thinking
|
||
|
}
|
||
|
STDERR.puts result.inspect
|
||
|
STDERR.flush
|
||
|
result
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def value_string_size(base, power)
|
||
|
n = base ** power
|
||
|
return [n, {n.to_s => ("*" * n)}]
|
||
|
end
|
||
|
|
||
|
def key_string_size(base, power)
|
||
|
n = base ** power
|
||
|
return [n, {("*" * n) => n}]
|
||
|
end
|
||
|
|
||
|
def hash_size_fixnum(base, power)
|
||
|
n = base ** power
|
||
|
h = Hash.new
|
||
|
(0...n).each { |i| h[i.to_s] = i }
|
||
|
return [n, h]
|
||
|
end
|
||
|
|
||
|
def array_size_fixnum(base, power)
|
||
|
n = base ** power
|
||
|
return [n, {n.to_s => Array.new(n, n)}]
|
||
|
end
|
||
|
|
||
|
def array_nest_fixnum(base, power)
|
||
|
n = base ** power
|
||
|
return [n, {n.to_s => array_nest(base, power, n)}]
|
||
|
end
|
||
|
|
||
|
def hash_nest_fixnum(base, power)
|
||
|
n = base ** power
|
||
|
return [n, {n.to_s => hash_nest(base, power, n)}]
|
||
|
end
|
||
|
|
||
|
def null_setup(db, coll)
|
||
|
|
||
|
end
|
||
|
|
||
|
def insert(coll, h)
|
||
|
h.delete(:_id) # delete :_id to insert
|
||
|
coll.insert(h) # note that insert stores :_id in h and subsequent inserts are updates
|
||
|
end
|
||
|
|
||
|
def default_teardown(db, coll)
|
||
|
coll.remove
|
||
|
#cmd = Hash.new.store('compact', $collection_name)
|
||
|
#db.command(cmd)
|
||
|
end
|
||
|
|
||
|
def test_array_nest
|
||
|
assert_equal(1, array_nest(2,0,1))
|
||
|
assert_equal([1, 1], array_nest(2,1,1))
|
||
|
assert_equal([[1, 1], [1, 1]], array_nest(2,2,1))
|
||
|
assert_equal([[[1, 1], [1, 1]], [[1, 1], [1, 1]]], array_nest(2,3,1))
|
||
|
assert_equal(1, array_nest(4,0,1))
|
||
|
assert_equal([1, 1, 1, 1], array_nest(4,1,1))
|
||
|
assert_equal([[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]], array_nest(4,2,1))
|
||
|
assert_equal(1, array_nest(8,0,1))
|
||
|
assert_equal([1, 1, 1, 1, 1, 1, 1, 1], array_nest(8,1,1))
|
||
|
end
|
||
|
|
||
|
def test_hash_nest # incomplete
|
||
|
assert_equal(1, hash_nest(2, 0, 1))
|
||
|
assert_equal({"0"=>1, "1"=>1}, hash_nest(2, 1, 1))
|
||
|
assert_equal({"0"=>{"0"=>1, "1"=>1}, "1"=>{"0"=>1, "1"=>1}}, hash_nest(2, 2, 1))
|
||
|
assert_equal({"0"=>{"0"=>{"0"=>1, "1"=>1}, "1"=>{"0"=>1, "1"=>1}},
|
||
|
"1"=>{"0"=>{"0"=>1, "1"=>1}, "1"=>{"0"=>1, "1"=>1}}}, hash_nest(2, 3, 1))
|
||
|
assert_equal(1, hash_nest(4,0,1))
|
||
|
assert_equal({"0"=>1, "1"=>1, "2"=>1, "3"=>1}, hash_nest(4,1,1))
|
||
|
assert_equal({"0"=>{"0"=>1, "1"=>1, "2"=>1, "3"=>1},
|
||
|
"1"=>{"0"=>1, "1"=>1, "2"=>1, "3"=>1},
|
||
|
"2"=>{"0"=>1, "1"=>1, "2"=>1, "3"=>1},
|
||
|
"3"=>{"0"=>1, "1"=>1, "2"=>1, "3"=>1}}, hash_nest(4,2,1))
|
||
|
assert_equal(1, hash_nest(8,0,1))
|
||
|
assert_equal({"0"=>1, "1"=>1, "2"=>1, "3"=>1, "4"=>1, "5"=>1, "6"=>1, "7"=>1}, hash_nest(8,1,1))
|
||
|
end
|
||
|
|
||
|
# Performance Tuning Engineering
|
||
|
## Overall Strategy
|
||
|
### Prioritize/Review Ruby 1.9.3, Ruby 1.8.7, JRuby 1.6.7
|
||
|
### Run spectrum of exploratory performance tests
|
||
|
### Graph results, probably with gnuplot, with HTML wrapper
|
||
|
### Select test for profiling
|
||
|
### Find where time is being spent
|
||
|
### Construct specific performance test
|
||
|
### Iteratively tune specific performance test
|
||
|
### Iterate selection of test for profiling
|
||
|
## Notes
|
||
|
### Start with Create/insert, writing comes first
|
||
|
### Then Read/find, reading comes next. both findOne and find-cursor
|
||
|
### Update is primarily server load with minimal driver load for conditions
|
||
|
### Delete/remove is primarily server load with minimal driver load for conditions
|
||
|
## Benefits
|
||
|
### Performance Improvements
|
||
|
### Knowledge of Ruby driver and techniques
|
||
|
### Perhaps architecture and design improvements
|
||
|
### Lessons transferable to other drivers
|
||
|
|
||
|
def test_zzz_exp_blanket
|
||
|
puts
|
||
|
conn = Mongo::Connection.new
|
||
|
conn.drop_database($db_name)
|
||
|
db = conn.db($db_name)
|
||
|
coll = db.collection($collection_name)
|
||
|
coll.remove
|
||
|
|
||
|
tests = [
|
||
|
# Create/insert
|
||
|
[2, 15, :value_string_size, :null_setup, :insert, :default_teardown],
|
||
|
[2, 15, :key_string_size, :null_setup, :insert, :default_teardown],
|
||
|
[2, 14, :array_size_fixnum, :null_setup, :insert, :default_teardown],
|
||
|
[2, 17, :hash_size_fixnum, :null_setup, :insert, :default_teardown],
|
||
|
[2, 12, :array_nest_fixnum, :null_setup, :insert, :default_teardown],
|
||
|
[4, 6, :array_nest_fixnum, :null_setup, :insert, :default_teardown],
|
||
|
[8, 4, :array_nest_fixnum, :null_setup, :insert, :default_teardown],
|
||
|
[16, 3, :array_nest_fixnum, :null_setup, :insert, :default_teardown],
|
||
|
[32, 2, :array_nest_fixnum, :null_setup, :insert, :default_teardown],
|
||
|
[2, 15, :hash_nest_fixnum, :null_setup, :insert, :default_teardown ],
|
||
|
[4, 8, :hash_nest_fixnum, :null_setup, :insert, :default_teardown ],
|
||
|
[8, 4, :hash_nest_fixnum, :null_setup, :insert, :default_teardown ],
|
||
|
[16, 4, :hash_nest_fixnum, :null_setup, :insert, :default_teardown ],
|
||
|
[32, 3, :hash_nest_fixnum, :null_setup, :insert, :default_teardown ],
|
||
|
|
||
|
# synthesized mix, real-world data pending
|
||
|
|
||
|
# Read/findOne/find pending
|
||
|
|
||
|
# Update pending
|
||
|
|
||
|
# Delete/remove pending
|
||
|
|
||
|
]
|
||
|
results = []
|
||
|
tests.each do |base, max_power, generator, setup, operation, teardown|
|
||
|
# consider moving "method" as permitted by scope
|
||
|
results += power_test(base, max_power, db, coll, method(generator), method(setup), method(operation), method(teardown))
|
||
|
end
|
||
|
# consider inserting the results into a database collection
|
||
|
# Test::Unit::TestCase pollutes STDOUT, so write to a file
|
||
|
File.open("exp_series.js", "w"){|f|
|
||
|
f.puts("expSeries = #{results.to_json.gsub(/(\[|},)/, "\\1\n")};")
|
||
|
}
|
||
|
|
||
|
conn.drop_database($db_name)
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
|