Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

write_element optimization for T_ARRAY to only allocate the buffer fo…

…r the index key once instead of repeatedly for every index. This results in a 2.5x improvement for array sizes around 32. Also includes improvements to the performance tests and graphs, with plothover to show [x,y] values.
  • Loading branch information...
commit 7384585d98d3a7bdca1b23df0314871822ab896b 1 parent bc59448
@gjmurakami-10gen gjmurakami-10gen authored
View
4 Gemfile
@@ -16,7 +16,6 @@ group :development, :test do
gem "test-unit"
gem "ci_reporter"
gem "ruby-prof" unless RUBY_PLATFORM =~ /java/
- gem "perftools.rb" unless RUBY_PLATFORM =~ /java/
gem "rake-compiler"
# Java
@@ -26,3 +25,6 @@ group :development, :test do
gem "jruby-openssl"
end
end
+
+gem "perftools.rb", :group => :development unless RUBY_PLATFORM =~ /java/
+
View
113 bench/exp_series.html
@@ -41,7 +41,6 @@
}
function flotSeries(expSeries, xMax, labelSpec, plotSpecs) {
return $.map(plotSpecs, function(plotSpec, i){
- var base = plotSpec.base; var gen = plotSpec.generator; var op = plotSpec.operation;
return {
label: labelSpec + ': ' + plotSpec[labelSpec],
data: genOpXY(expSeries, xMax, plotSpec, 'exp2', 'ops'),
@@ -68,70 +67,83 @@
{
xaxis: { ticks: xExpTicks },
yaxes: [ { min: 0 } ],
- legend: { position: 'ne' }
+ legend: { position: 'ne' },
+ grid: { hoverable: true }
});
}
// comment pending
var graph = [
- [ 'value_string_size insert C versus Ruby', 14, 'mongo_driver_mode',
+ [ 'value_string_size insert C versus Ruby', 14, 'mode',
[
- { base:2, generator:'value_string_size', operation:'insert', mongo_driver_mode: 'c' },
- { base:2, generator:'value_string_size', operation:'insert', mongo_driver_mode: 'ruby' }
+ { base:2, generator:'value_string_size', operation:'insert', mode: 'c', tag: 'array_slow' },
+ { base:2, generator:'value_string_size', operation:'insert', mode: 'ruby', tag: 'orig_ruby' }
]
],
- [ 'key_string_size insert C versus Ruby', 14, 'mongo_driver_mode',
+ [ 'key_string_size insert C versus Ruby', 14, 'mode',
[
- { base:2, generator:'key_string_size', operation:'insert', mongo_driver_mode: 'c' },
- { base:2, generator:'key_string_size', operation:'insert', mongo_driver_mode: 'ruby' }
+ { base:2, generator:'key_string_size', operation:'insert', mode: 'c', tag: 'array_slow' },
+ { base:2, generator:'key_string_size', operation:'insert', mode: 'ruby', tag: 'orig_ruby' }
]
],
- [ 'array_size_fixnum insert C versus Ruby', 12, 'mongo_driver_mode',
+ [ 'array_size_fixnum insert C versus Ruby', 12, 'mode',
[
- { base:2, generator:'array_size_fixnum', operation:'insert', mongo_driver_mode: 'c' },
- { base:2, generator:'array_size_fixnum', operation:'insert', mongo_driver_mode: 'ruby' }
+ { base:2, generator:'array_size_fixnum', operation:'insert', mode: 'c', tag: 'array_slow' },
+ { base:2, generator:'array_size_fixnum', operation:'insert', mode: 'ruby', tag: 'orig_ruby' }
]
],
- [ 'hash_size_fixnum insert C versus Ruby', 12, 'mongo_driver_mode',
+ [ 'hash_size_fixnum insert C versus Ruby', 12, 'mode',
[
- { base:2, generator:'hash_size_fixnum', operation:'insert', mongo_driver_mode: 'c' },
- { base:2, generator:'hash_size_fixnum', operation:'insert', mongo_driver_mode: 'ruby' }
+ { base:2, generator:'hash_size_fixnum', operation:'insert', mode: 'c', tag: 'array_slow' },
+ { base:2, generator:'hash_size_fixnum', operation:'insert', mode: 'ruby', tag: 'orig_ruby' }
]
],
- [ 'array_nest_fixnum base 2 insert C versus Ruby', 12, 'mongo_driver_mode',
+ [ 'array_nest_fixnum base 2 insert C versus Ruby', 12, 'mode',
[
- { base:2, generator:'array_nest_fixnum', operation:'insert', mongo_driver_mode:'c' },
- { base:2, generator:'array_nest_fixnum', operation:'insert', mongo_driver_mode:'ruby' }
+ { base:2, generator:'array_nest_fixnum', operation:'insert', mode:'c', tag: 'array_slow' },
+ { base:2, generator:'array_nest_fixnum', operation:'insert', mode:'ruby', tag: 'orig_ruby' }
]
],
- [ 'hash_nest_fixnum base 2 insert C versus Ruby', 12, 'mongo_driver_mode',
+ [ 'hash_nest_fixnum base 2 insert C versus Ruby', 12, 'mode',
[
- { base:2, generator:'hash_nest_fixnum', operation:'insert', mongo_driver_mode: 'c' },
- { base:2, generator:'hash_nest_fixnum', operation:'insert', mongo_driver_mode: 'ruby' }
+ { base:2, generator:'hash_nest_fixnum', operation:'insert', mode: 'c', tag: 'array_slow' },
+ { base:2, generator:'hash_nest_fixnum', operation:'insert', mode: 'ruby', tag: 'orig_ruby' }
]
],
[ 'array_nest_fixnum insert C by base', 12, 'base',
[
- { base:2, generator:'array_nest_fixnum', operation:'insert', mongo_driver_mode: 'c' },
- { base:4, generator:'array_nest_fixnum', operation:'insert', mongo_driver_mode: 'c' },
- { base:8, generator:'array_nest_fixnum', operation:'insert', mongo_driver_mode: 'c' },
- { base:16, generator:'array_nest_fixnum', operation:'insert', mongo_driver_mode: 'c' },
- { base:32, generator:'array_nest_fixnum', operation:'insert', mongo_driver_mode: 'c' }
+ { base:2, generator:'array_nest_fixnum', operation:'insert', mode: 'c', tag: 'array_slow' },
+ { base:4, generator:'array_nest_fixnum', operation:'insert', mode: 'c', tag: 'array_slow' },
+ { base:8, generator:'array_nest_fixnum', operation:'insert', mode: 'c', tag: 'array_slow' },
+ { base:16, generator:'array_nest_fixnum', operation:'insert', mode: 'c', tag: 'array_slow' },
+ { base:32, generator:'array_nest_fixnum', operation:'insert', mode: 'c', tag: 'array_slow' }
]
],
[ 'hash_nest_fixnum insert C by base', 12, 'base',
[
- { base:2, generator:'hash_nest_fixnum', operation:'insert', mongo_driver_mode: 'c' },
- { base:4, generator:'hash_nest_fixnum', operation:'insert', mongo_driver_mode: 'c' },
- { base:8, generator:'hash_nest_fixnum', operation:'insert', mongo_driver_mode: 'c' },
- { base:16, generator:'hash_nest_fixnum', operation:'insert', mongo_driver_mode: 'c' },
- { base:32, generator:'hash_nest_fixnum', operation:'insert', mongo_driver_mode: 'c' }
+ { base:2, generator:'hash_nest_fixnum', operation:'insert', mode: 'c', tag: 'array_slow' },
+ { base:4, generator:'hash_nest_fixnum', operation:'insert', mode: 'c', tag: 'array_slow' },
+ { base:8, generator:'hash_nest_fixnum', operation:'insert', mode: 'c', tag: 'array_slow' },
+ { base:16, generator:'hash_nest_fixnum', operation:'insert', mode: 'c', tag: 'array_slow' },
+ { base:32, generator:'hash_nest_fixnum', operation:'insert', mode: 'c', tag: 'array_slow' }
]
],
- [ 'array_size_fixnum versus hash_size_fixnum insert C', 12, 'generator',
+ [ 'array_size_fixnum slow versus hash_size_fixnum insert C', 12, 'generator',
[
- { base:2, generator:'array_size_fixnum', operation:'insert', mongo_driver_mode: 'c' },
- { base:2, generator:'hash_size_fixnum', operation:'insert', mongo_driver_mode: 'c' }
+ { base:2, generator:'array_size_fixnum', operation:'insert', mode: 'c', tag: 'array_slow' },
+ { base:2, generator:'hash_size_fixnum', operation:'insert', mode: 'c', tag: 'array_slow' }
+ ]
+ ],
+ [ 'array_size_fixnum fast versus hash_size_fixnum insert C', 12, 'generator',
+ [
+ { base:2, generator:'array_size_fixnum', operation:'insert', mode: 'c', tag: 'array_fast' },
+ { base:2, generator:'hash_size_fixnum', operation:'insert', mode: 'c', tag: 'array_slow' }
+ ]
+ ],
+ [ 'array_size_fixnum slow versus fast insert C', 12, 'tag',
+ [
+ { base:2, generator:'array_size_fixnum', operation:'insert', mode: 'c', tag: 'array_slow' },
+ { base:2, generator:'array_size_fixnum', operation:'insert', mode: 'c', tag: 'array_fast' }
]
]
];
@@ -142,6 +154,41 @@
doPlot(title, series);
});
+ function showTooltip(x, y, contents) {
+ $('<div id="tooltip">' + contents + '</div>').css( {
+ position: 'absolute',
+ display: 'none',
+ top: y + 5,
+ left: x + 5,
+ border: '1px solid #fdd',
+ padding: '2px',
+ 'background-color': '#fee',
+ opacity: 0.80
+ }).appendTo("body").fadeIn(200);
+ }
+
+ var previousPoint = null;
+ $('.graph').bind('plothover', function (event, pos, item) {
+ $("#x").text(pos.x.toFixed(2));
+ $("#y").text(pos.y.toFixed(2));
+ if (item) {
+ if (previousPoint != item.dataIndex) {
+ previousPoint = item.dataIndex;
+
+ $("#tooltip").remove();
+ var x = item.datapoint[0].toFixed(2),
+ y = item.datapoint[1].toFixed(2);
+
+ showTooltip(item.pageX, item.pageY,
+ item.series.label + ' [ ' + Math.round(x) + ', ' + Math.round(y) + ' ]');
+ }
+ }
+ else {
+ $("#tooltip").remove();
+ previousPoint = null;
+ }
+ });
+
});
</script>
</body>
View
680 bench/exp_series.js
408 additions, 272 deletions not shown
View
160 bench/exp_series.rb
@@ -1,37 +1,57 @@
#!/usr/bin/env ruby
$LOAD_PATH.unshift(File.expand_path("../../lib", __FILE__))
-def set_mongo_driver_mode(mode)
+require 'rubygems'
+require 'getoptlong'
+require 'json'
+require 'benchmark'
+require 'test-unit'
+
+def set_mode(mode)
case mode
- when :c
+ when 'c'
ENV.delete('TEST_MODE')
ENV['C_EXT'] = 'TRUE'
- when :ruby
+ when 'ruby'
ENV['TEST_MODE'] = 'TRUE'
ENV.delete('C_EXT')
else
- raise 'mode must be :c or :ruby'
+ raise 'mode must be c or ruby'
end
- ENV['MONGO_DRIVER_MODE'] = mode.to_s
+ return mode
end
-$mode = ARGV[0].to_sym if ARGV[0]
-set_mongo_driver_mode($mode || :c)
-ENV['HOSTNAME'] = `uname -n`[/([^.]*)/,1]
-ENV['OSNAME'] = `uname -s`.strip
-
-# Exploratory/Experimental/Exponential tests for performance tuning
-
-require 'rubygems'
-require 'test-unit'
-require 'json'
-require 'mongo'
-require 'benchmark'
-
+$description = 'Exploratory/Experimental/Exponential tests for Ruby-driver performance tuning'
$calibration_runtime = 0.1
$target_runtime = 5.0
$db_name = 'benchmark'
$collection_name = 'exp_series'
+$mode = set_mode('ruby')
+$hostname = `uname -n`[/([^.]*)/,1]
+$osname = `uname -s`.strip
+$tag = `git log -1 --format=oneline`.split[0]
+$date = Time.now.strftime('%Y%m%d-%H%M')
+
+options_with_help = [
+ [ '--help', '-h', GetoptLong::NO_ARGUMENT, '', 'show help' ],
+ [ '--mode', '-m', GetoptLong::OPTIONAL_ARGUMENT, ' mode', 'set mode to "c" or "ruby" (default)' ],
+ [ '--tag', '-t', GetoptLong::OPTIONAL_ARGUMENT, ' tag', 'set tag for run, default is git commit key' ]
+]
+options = options_with_help.collect{|option|option[0...3]}
+GetoptLong.new(*options).each do |opt, arg|
+ case opt
+ when '--help'
+ puts "#{$0} -- #{$description}\nusage: #{$0} [options]\noptions:"
+ options_with_help.each{|option| puts "#{option[0]}#{option[3]}, #{option[1]}#{option[3]}\n\t#{option[4]}"}
+ exit 0
+ when '--mode'
+ $mode = set_mode(arg)
+ when '--tag'
+ $tag = arg
+ end
+end
+
+require 'mongo' # must be after option processing
class Hash
def store_embedded(key, value)
@@ -70,12 +90,12 @@ def hash_nest(base, level, obj)
return h
end
- def estimate_iterations(db, coll, setup, teardown)
+ def estimate_iterations(db, coll, doc, setup, teardown)
start_time = Time.now
iterations = 1
utime = 0.0
while utime <= $calibration_runtime do
- setup.call(db, coll)
+ setup.call(db, coll, doc, iterations)
btms = Benchmark.measure do
(0...iterations).each do
yield
@@ -89,23 +109,23 @@ def estimate_iterations(db, coll, setup, teardown)
return [(iterations.to_f * $target_runtime / utime).to_i, etime]
end
- def measure_iterations(db, coll, setup, teardown, iterations)
- setup.call(db, coll)
+ def measure_iterations(db, coll, doc, setup, teardown, iterations)
+ setup.call(db, coll, doc, iterations)
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 }
+ def valuate(db, coll, doc, setup, teardown)
+ iterations, etime = estimate_iterations(db, coll, doc, setup, teardown) { yield }
+ utime, rtime = measure_iterations(db, coll, doc, 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) }
+ iterations, utime, rtime, etime = valuate(db, coll, doc, setup, teardown) { operation.call(coll, doc) }
result = {
'base' => base,
'power' => power,
@@ -119,11 +139,11 @@ def power_test(base, max_power, db, coll, generator, setup, operation, teardown)
'rtime' => rtime.round(2),
'ops' => (iterations.to_f / utime.to_f).round(1),
'usec' => (1000000.0 * utime.to_f / iterations.to_f).round(1),
- 'mongo_driver_mode' => ENV['MONGO_DRIVER_MODE'],
- 'hostname' => ENV['HOSTNAME'],
- 'osname' => ENV['OSNAME'],
- # 'git' => git, # thinking
- # 'datetime' +> Time.now, # thinking
+ 'mode' => $mode,
+ 'hostname' => $hostname,
+ 'osname' => $osname,
+ 'date' => $date,
+ 'tag' => $tag,
# 'nbench-int' => nbench.int, # thinking
}
STDERR.puts result.inspect
@@ -164,19 +184,46 @@ def hash_nest_fixnum(base, power)
return [n, {n.to_s => hash_nest(base, power, n)}]
end
- def null_setup(db, coll)
+ def null_setup(db, coll, doc, iterations)
+
+ end
+
+ def find_one_setup(db, coll, doc, iterations)
+ insert(coll, doc)
+ end
+
+ def cursor_setup(db, coll, doc, iterations)
+ (0...iterations).each{insert(coll, doc)}
+ @cursor = coll.find
+ @queries = 1
+ end
+ def insert(coll, doc)
+ doc.delete(:_id) # delete :_id to insert
+ coll.insert(doc) # note that insert stores :_id in doc and subsequent inserts are updates
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
+ def find_one(coll, doc)
+ h = coll.find_one
+ raise "find_one failed" unless h
+ end
+
+ def cursor_next(coll, doc)
+ h = @cursor.next
+ unless h
+ @cursor = coll.find
+ @queries += 1
+ @cursor.next
+ end
end
def default_teardown(db, coll)
coll.remove
- #cmd = Hash.new.store('compact', $collection_name)
- #db.command(cmd)
+ end
+
+ def cursor_teardown(db, coll)
+ coll.remove
+ puts "queries: #{@queries}"
end
def test_array_nest
@@ -237,6 +284,7 @@ def test_hash_nest # incomplete
def test_zzz_exp_blanket
puts
+ p ({'mode' => $mode , 'hostname' => $hostname, 'osname' => $osname, 'date' => $date, 'tag' => $tag})
puts sys_info
conn = Mongo::Connection.new
@@ -264,7 +312,41 @@ def test_zzz_exp_blanket
# synthesized mix, real-world data pending
- # Read/findOne/find pending
+ # Read/find_one
+=begin
+ [2, 15, :value_string_size, :find_one_setup, :find_one, :default_teardown],
+ [2, 15, :key_string_size, :find_one_setup, :find_one, :default_teardown],
+ [2, 14, :array_size_fixnum, :find_one_setup, :find_one, :default_teardown],
+ [2, 17, :hash_size_fixnum, :find_one_setup, :find_one, :default_teardown],
+ [2, 12, :array_nest_fixnum, :find_one_setup, :find_one, :default_teardown],
+ [4, 6, :array_nest_fixnum, :find_one_setup, :find_one, :default_teardown],
+ [8, 4, :array_nest_fixnum, :find_one_setup, :find_one, :default_teardown],
+ [16, 3, :array_nest_fixnum, :find_one_setup, :find_one, :default_teardown],
+ [32, 2, :array_nest_fixnum, :find_one_setup, :find_one, :default_teardown],
+ [2, 15, :hash_nest_fixnum, :find_one_setup, :find_one, :default_teardown ],
+ [4, 8, :hash_nest_fixnum, :find_one_setup, :find_one, :default_teardown ],
+ [8, 4, :hash_nest_fixnum, :find_one_setup, :find_one, :default_teardown ],
+ [16, 4, :hash_nest_fixnum, :find_one_setup, :find_one, :default_teardown ],
+ [32, 3, :hash_nest_fixnum, :find_one_setup, :find_one, :default_teardown ],
+=end
+
+ # Read/find/next
+=begin
+ [2, 15, :value_string_size, :cursor_setup, :cursor_next, :cursor_teardown],
+ [2, 15, :key_string_size, :cursor_setup, :cursor_next, :cursor_teardown],
+ [2, 14, :array_size_fixnum, :cursor_setup, :cursor_next, :cursor_teardown],
+ [2, 17, :hash_size_fixnum, :cursor_setup, :cursor_next, :cursor_teardown],
+ [2, 12, :array_nest_fixnum, :cursor_setup, :cursor_next, :cursor_teardown],
+ [4, 6, :array_nest_fixnum, :cursor_setup, :cursor_next, :cursor_teardown],
+ [8, 4, :array_nest_fixnum, :cursor_setup, :cursor_next, :cursor_teardown],
+ [16, 3, :array_nest_fixnum, :cursor_setup, :cursor_next, :cursor_teardown],
+ [32, 2, :array_nest_fixnum, :cursor_setup, :cursor_next, :cursor_teardown],
+ [2, 15, :hash_nest_fixnum, :cursor_setup, :cursor_next, :cursor_teardown ],
+ [4, 8, :hash_nest_fixnum, :cursor_setup, :cursor_next, :cursor_teardown ],
+ [8, 4, :hash_nest_fixnum, :cursor_setup, :cursor_next, :cursor_teardown ],
+ [16, 4, :hash_nest_fixnum, :cursor_setup, :cursor_next, :cursor_teardown ],
+ [32, 3, :hash_nest_fixnum, :cursor_setup, :cursor_next, :cursor_teardown ],
+=end
# Update pending
@@ -278,7 +360,7 @@ def test_zzz_exp_blanket
end
# consider inserting the results into a database collection
# Test::Unit::TestCase pollutes STDOUT, so write to a file
- File.open("exp_series-#{Time.now.strftime('%Y%m%d-%H%M')}.js", 'w'){|f|
+ File.open("exp_series-#{$date}-#{$tag}.js", 'w'){|f|
f.puts("#{results.to_json.gsub(/\[/, "").gsub(/(}[\],])/, "},\n")}")
}
View
44 ext/cbson/cbson.c
@@ -139,38 +139,14 @@ static void write_utf8(bson_buffer_t buffer, VALUE string, char check_null) {
#define EXTENDED RE_OPTION_EXTENDED
#endif
-/* TODO we ought to check that the malloc or asprintf was successful
- * and raise an exception if not. */
-/* TODO maybe we can use something more portable like vsnprintf instead
- * of this hack. And share it with the Python extension ;) */
-/* If we don't have ASPRINTF, there are two possibilities:
- * either use _scprintf and _snprintf on for Windows or
- * use snprintf for solaris. */
-#ifndef HAVE_ASPRINTF
+/* TODO review malloc versus Ruby Enterprise Edition with tcmalloc */
+/* TODO we ought to check that the malloc was successful and raise an exception if not. */
#ifdef _WIN32 || _MSC_VER
-#define INT2STRING(buffer, i) \
- { \
- int vslength = _scprintf("%d", i) + 1; \
- *buffer = malloc(vslength); \
- _snprintf(*buffer, vslength, "%d", i); \
- }
-#define FREE_INTSTRING(buffer) free(buffer)
+#define SCINT(i) (_scprintf("%d", i) + 1)
+#define SNPRINTF _snprintf
#else
-#define INT2STRING(buffer, i) \
- { \
- int vslength = snprintf(NULL, 0, "%d", i) + 1; \
- *buffer = malloc(vslength); \
- snprintf(*buffer, vslength, "%d", i); \
- }
-#define FREE_INTSTRING(buffer) free(buffer)
-#endif
-#else
-#define INT2STRING(buffer, i) asprintf(buffer, "%d", i);
-#ifdef USING_SYSTEM_ALLOCATOR_LIBRARY /* Ruby Enterprise Edition with tcmalloc */
-#define FREE_INTSTRING(buffer) system_free(buffer)
-#else
-#define FREE_INTSTRING(buffer) free(buffer)
-#endif
+#define SCINT(i) (snprintf(NULL, 0, "%d", i) + 1)
+#define SNPRINTF snprintf
#endif
#ifndef RREGEXP_SRC
@@ -312,15 +288,15 @@ static int write_element(VALUE key, VALUE value, VALUE extra, int allow_id) {
}
items = RARRAY_LENINT(value);
+ int vslength = SCINT(items);
+ char* name = malloc(vslength);
for(i = 0; i < items; i++) {
- char* name;
VALUE key;
- INT2STRING(&name, i);
+ SNPRINTF(name, vslength, "%d", i);
key = rb_str_new2(name);
write_element_with_id(key, rb_ary_entry(value, i), pack_extra(buffer, check_keys));
- FREE_INTSTRING(name);
}
-
+ free(name);
// write null byte and fill in length
SAFE_WRITE(buffer, &zero, 1);
obj_length = bson_buffer_get_position(buffer) - start_position;
Please sign in to comment.
Something went wrong with that request. Please try again.