Permalink
Browse files

Optional native extension for unpacking composite columns

This cuts the cost of reading composite columns by about 30%.  Mostly
because it was really slow before.
  • Loading branch information...
natemueller committed Jul 20, 2012
1 parent fade7c2 commit 8301076030320d010e02d5a22fd56098fa94328c
Showing with 73 additions and 2 deletions.
  1. +1 −0 cassandra.gemspec
  2. +34 −0 ext/cassandra_native.c
  3. +9 −0 ext/extconf.rb
  4. +29 −2 lib/cassandra/composite.rb
View
@@ -19,6 +19,7 @@ Gem::Specification.new do |s|
s.rubygems_version = "1.8.10"
s.summary = "A Ruby client for the Cassandra distributed database."
s.test_files = ["test/cassandra_mock_test.rb", "test/ordered_hash_test.rb", "test/cassandra_client_test.rb", "test/cassandra_test.rb", "test/comparable_types_test.rb", "test/test_helper.rb", "test/eventmachine_test.rb"]
+ s.extensions = ['ext/extconf.rb']
if s.respond_to? :specification_version then
s.specification_version = 3
View
@@ -0,0 +1,34 @@
+#include <ruby.h>
+#include <arpa/inet.h>
+
+VALUE parts_ivar_id;
+
+VALUE rb_cassandra_composite_fast_unpack(VALUE self, VALUE packed_string_value) {
+ int i = 0;
+ int index = 0;
+ int message_length = RSTRING_LEN(packed_string_value);
+ char *packed_string = (char *)RSTRING_PTR(packed_string_value);
+
+ VALUE parts = rb_ary_new();
+ while (index < message_length) {
+ uint16_t length = ntohs(((uint16_t *)(packed_string+index))[0]);
+ VALUE part = rb_str_new("", length);
+ for (i = 0; i < length; i++) {
+ ((char *)RSTRING_PTR(part))[i] = packed_string[index+2+i];
+ }
+ rb_ary_push(parts, part);
+ index += length + 3;
+ }
+
+ rb_ivar_set(self, parts_ivar_id, parts);
+
+ return Qnil;
+}
+
+void Init_cassandra_native(void) {
+ VALUE cassandra_module = rb_const_get(rb_cObject, rb_intern("Cassandra"));
+ VALUE cassandra_composite_class = rb_define_class_under(cassandra_module, "Composite", rb_cObject);
+ rb_define_method(cassandra_composite_class, "fast_unpack", rb_cassandra_composite_fast_unpack, 1);
+
+ parts_ivar_id = rb_intern("@parts");
+}
View
@@ -0,0 +1,9 @@
+if defined?(RUBY_ENGINE) && RUBY_ENGINE =~ /jruby/
+ File.open('Makefile', 'w'){|f| f.puts "all:\n\ninstall:\n" }
+else
+ require 'mkmf'
+
+ $CFLAGS = "-g -O2 -Wall -Werror"
+
+ create_makefile 'cassandra_native'
+end
View
@@ -1,11 +1,12 @@
-
class Cassandra
class Composite
include ::Comparable
attr_reader :parts
attr_reader :column_slice
def initialize(*parts)
+ return if parts.empty?
+
options = {}
if parts.last.is_a?(Hash)
options = parts.pop
@@ -23,6 +24,12 @@ def initialize(*parts)
end
end
+ def self.new_from_packed(packed)
+ obj = new
+ obj.fast_unpack(packed)
+ return obj
+ end
+
def [](*args)
return @parts[*args]
end
@@ -76,6 +83,20 @@ def slice_end_of_component
return "\x00"
end
+ def fast_unpack(packed_string)
+ end_of_component = packed_string.slice(packed_string.length-1, 1)
+ while packed_string.length > 0
+ length = packed_string.unpack('n')[0]
+ @parts << packed_string.slice(2, length)
+
+ packed_string.slice!(0, length+3)
+ end
+
+ @column_slice = :after if end_of_component == "\x01"
+ @column_slice = :before if end_of_component == "\xFF"
+ @hash = packed_string.hash
+ end
+
private
def try_packed_composite(packed_string)
parts = []
@@ -101,11 +122,17 @@ def try_packed_composite(packed_string)
end
def hash
- return @hash || parts.hash + column_slice.hash
+ return @hash ||= pack.hash
end
def eql?(other)
return to_s == other.to_s
end
end
end
+
+begin
+ require "cassandra_native"
+rescue LoadError
+ puts "Unable to load cassandra_native extension. Defaulting to pure Ruby libraries."
+end

0 comments on commit 8301076

Please sign in to comment.