Skip to content

Instantly share code, notes, and snippets.

@skippy
Last active August 29, 2015 14:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save skippy/63c9025db92019886aa0 to your computer and use it in GitHub Desktop.
Save skippy/63c9025db92019886aa0 to your computer and use it in GitHub Desktop.
hash optimizations (c vs mix ruby/c vs jruby)
#!/usr/bin/env ruby
require 'rubygems'
require 'bundler/setup'
require 'benchmark'
require 'google/protobuf'
pool = Google::Protobuf::DescriptorPool.new
pool.build do
add_message "TestMessage" do
optional :optional_int32, :int32, 1
optional :optional_int64, :int64, 2
optional :optional_uint32, :uint32, 3
optional :optional_uint64, :uint64, 4
optional :optional_bool, :bool, 5
optional :optional_float, :float, 6
optional :optional_double, :double, 7
optional :optional_string, :string, 8
optional :optional_bytes, :bytes, 9
optional :optional_msg, :message, 10, "TestMessage2"
repeated :repeated_int32, :int32, 12
repeated :repeated_int64, :int64, 13
repeated :repeated_uint32, :uint32, 14
repeated :repeated_uint64, :uint64, 15
repeated :repeated_bool, :bool, 16
repeated :repeated_float, :float, 17
repeated :repeated_double, :double, 18
repeated :repeated_string, :string, 19
repeated :repeated_bytes, :bytes, 20
repeated :repeated_msg, :message, 21, "TestMessage2"
end
add_message "TestMessage2" do
optional :foo, :int32, 1
end
end
TestMessage = pool.lookup("TestMessage").msgclass
TestMessage2 = pool.lookup("TestMessage2").msgclass
tm = TestMessage.new
tm.repeated_string << 'ok1'
tm.repeated_string << 'ok2'
tm.optional_string = 'another string'
tm.optional_int32 = 102
iters = 100_000
Benchmark.bm(12) do |b|
b.report('#to_h-java'){ iters.times{ tm.to_h } }
b.report('#to_h-pure'){ iters.times{ tm.to_h2 } }
end
// just the relavent method
VALUE Message_to_h(VALUE _self) {
MessageHeader* self;
TypedData_Get_Struct(_self, MessageHeader, &Message_type, self);
VALUE hash = rb_hash_new();
rb_gc_register_address(&hash);
upb_msg_field_iter it;
for (upb_msg_field_begin(&it, self->descriptor->msgdef);
!upb_msg_field_done(&it);
upb_msg_field_next(&it)) {
const upb_fielddef* field = upb_msg_iter_field(&it);
VALUE msg_value = layout_get(self->descriptor->layout, Message_data(self), field);
// see benchmark results for the difference between using a symbol and a string
// VALUE msg_key = rb_str_new2(upb_fielddef_name(field));
VALUE msg_key = ID2SYM(upb_fielddef_name(field));
if (upb_fielddef_label(field) == UPB_LABEL_REPEATED) {
msg_value = RepeatedField_to_ary(msg_value);
}
rb_hash_aset(hash, msg_key, msg_value);
}
return hash;
}
// just the relavent method
@JRubyMethod(name = {"to_h", "to_hash"})
public IRubyObject toHash(ThreadContext context) {
Ruby runtime = context.runtime;
RubyHash ret = RubyHash.newHash(runtime);
for (Descriptors.FieldDescriptor fdef : this.descriptor.getFields()) {
IRubyObject value = getField(context, fdef);
if (value.respondsTo("to_h")) {
value = Helpers.invoke(context, value, "to_h");
} else if (value.respondsTo("to_a")) {
value = Helpers.invoke(context, value, "to_a");
}
ret.fastASet(runtime.newString(fdef.getName()), value);
}
return ret;
}
# just the ruby implementation
# which is backed by self.class.descriptor.each, which is a custom c method
def to_h2
self.class.descriptor.each_with_object({}) do |field, hash|
name = field.name
val = self[name]
val = val.to_a if val.respond_to?(:to_a)
hash[name] = val
end
end
### MRI 2.2.0
#using strings as keys
user system total real
#to_h-c 1.460000 0.010000 1.470000 ( 1.491718)
#to_h-pure 3.080000 0.030000 3.110000 ( 3.179323)
#using symbols as keys
user system total real
#to_h-c 0.350000 0.010000 0.360000 ( 0.371077)
#to_h-pure 2.750000 0.050000 2.800000 ( 2.881573)
### jruby-1.7.19
# NOTE: I know jruby has some benchmarking things to watch out for... here I just fired up pry and ran the script... not ideal
# but it does seem that some optimisations may be needed
#using strings as keys
user system total real
#to_h-java 2.350000 0.020000 2.370000 ( 2.392000)
#to_h-pure 5.710000 0.080000 5.790000 ( 6.324000)
#using symbols as keys
user system total real
#to_h-java 0.980000 0.020000 1.000000 ( 0.991000)
#to_h-pure 5.720000 0.110000 5.830000 ( 5.900000)
### jruby 9.0.0.0.pre1 and jruby 9.0.0.0.pre2
fails with:
TypeError: wrong element type String (expected array)
from org/jruby/RubyArray.java:1569:in `each'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment