about summary refs log tree commit homepage
diff options
context:
space:
mode:
authorEric Wong <e@yhbt.net>2011-01-17 03:05:42 +0000
committerEric Wong <normalperson@yhbt.net>2011-01-17 08:28:47 +0000
commit3107a78110309a203b0c2ef7cc4cd9d18d294a46 (patch)
tree470795dcd99d3f90af83d83549f1796e5a4fc019
parent069d047d3dcb0eefe59babbd295cfee922aa1476 (diff)
downloadruby-tdb-3107a78110309a203b0c2ef7cc4cd9d18d294a46.tar.gz
This allows apps to reduce GC thrashing by reusing
a string buffer.
-rw-r--r--ext/tdb/tdb.c46
-rw-r--r--test/test_tdb.rb44
2 files changed, 76 insertions, 14 deletions
diff --git a/ext/tdb/tdb.c b/ext/tdb/tdb.c
index a502d3b..31f71c8 100644
--- a/ext/tdb/tdb.c
+++ b/ext/tdb/tdb.c
@@ -331,7 +331,9 @@ struct fetch_parse_args {
         struct tdb_context *tdb;
         union {
                 TDB_DATA key;
-                TDB_DATA val;
+                long value_len;
+                char *value_ptr;
+                VALUE value;
         } as;
         VALUE value;
 };
@@ -341,11 +343,12 @@ static VALUE str_new_tdb_data(TDB_DATA *val)
         return rb_str_new((const char *)val->dptr, val->dsize);
 }
 
-static void *gvl_str_new(void *data)
+static void *gvl_str_resize(void *data)
 {
         struct fetch_parse_args *f = data;
 
-        f->value = str_new_tdb_data(&f->as.val);
+        rb_str_resize(f->value, f->as.value_len);
+        f->as.value_ptr = RSTRING_PTR(f->value);
 
         return NULL;
 }
@@ -354,8 +357,10 @@ static int fetch_parse(TDB_DATA key, TDB_DATA val, void *data)
 {
         struct fetch_parse_args *f = data;
 
-        f->as.val = val;
-        (void)rb_thread_call_with_gvl(gvl_str_new, data);
+        f->as.value_len = val.dsize;
+        (void)rb_thread_call_with_gvl(gvl_str_resize, data);
+        memcpy(f->as.value_ptr, val.dptr, val.dsize);
+        f->as.value = f->value;
 
         return 0;
 }
@@ -367,16 +372,24 @@ static VALUE nogvl_parse_record(void *ptr)
         if (tdb_parse_record(f->tdb, f->as.key, fetch_parse, ptr) == -1)
                 return Qnil;
 
-        return f->value;
+        return f->value == f->as.value ? f->value : Qnil;
 }
 
-static VALUE fetch(VALUE self, VALUE key)
+static VALUE fetch(int argc, VALUE *argv, VALUE self)
 {
         struct fetch_parse_args f;
+        VALUE key;
+
+        rb_scan_args(argc, argv, "11", &key, &f.value);
+        if (NIL_P(f.value)) {
+                f.value = rb_str_new(0, 0);
+        } else {
+                StringValue(f.value);
+                rb_str_set_len(f.value, 0);
+        }
 
         f.tdb = db(self, 1);
         TO_TDB_DATA(f.as.key, key);
-        f.value = Qnil;
 
         return my_tbr(nogvl_parse_record, &f);
 }
@@ -549,12 +562,17 @@ static VALUE nuke(VALUE self, VALUE key)
         return my_tbr(nogvl_delete, &d);
 }
 
-static VALUE delete(VALUE self, VALUE key)
+static VALUE aref(VALUE self, VALUE key)
+{
+        return fetch(1, &key, self);
+}
+
+static VALUE delete(int argc, VALUE *argv, VALUE self)
 {
-        VALUE rc = fetch(self, key);
+        VALUE rc = fetch(argc, argv, self);
 
         if (! NIL_P(rc))
-                if (nuke(self, key) == Qfalse)
+                if (nuke(self, argv[0]) == Qfalse)
                         return Qnil;
         return rc;
 }
@@ -674,8 +692,8 @@ void Init_tdb_ext(void)
         rb_define_method(cTDB, "close", tdbclose, 0);
         rb_define_method(cTDB, "closed?", closed, 0);
 
-        rb_define_method(cTDB, "fetch", fetch, 1);
-        rb_define_method(cTDB, "[]", fetch, 1);
+        rb_define_method(cTDB, "fetch", fetch, -1);
+        rb_define_method(cTDB, "[]", aref, 1);
         rb_define_method(cTDB, "store", store, 2);
         rb_define_method(cTDB, "[]=", store, 2);
         rb_define_method(cTDB, "insert!", insert_bang, 2);
@@ -689,7 +707,7 @@ void Init_tdb_ext(void)
         rb_define_method(cTDB, "member?", has_key, 1);
         rb_define_method(cTDB, "each", each, 0);
         rb_define_method(cTDB, "nuke!", nuke, 1);
-        rb_define_method(cTDB, "delete", delete, 1);
+        rb_define_method(cTDB, "delete", delete, -1);
 
         rb_define_method(cTDB, "lockall", lockall, 0);
         rb_define_method(cTDB, "trylockall", trylockall, 0);
diff --git a/test/test_tdb.rb b/test/test_tdb.rb
index d7a6a8a..7e77f14 100644
--- a/test/test_tdb.rb
+++ b/test/test_tdb.rb
@@ -275,6 +275,50 @@ class TestTdb < Test::Unit::TestCase
     assert ! @tdb.include?("hello")
   end
 
+  def test_fetch_reuse_buf
+    assert_nothing_raised do
+      @tmp = Tempfile.new('tdb')
+      File.unlink(@tmp.path)
+    end
+    @tdb = TDB.new(@tmp.path)
+    @tdb["HELLO"] = "WORLD"
+    rbuf = "HI"
+    rv = @tdb.fetch("HELLO", rbuf)
+    assert_equal rbuf.object_id, rv.object_id
+
+    assert_equal "WORLD", rbuf
+    assert_nil @tdb.fetch("HELLO!", rbuf)
+    assert_equal "", rbuf
+
+    @tdb["HELLO"] = "WORLD" * 100
+    rv = @tdb.fetch("HELLO", rbuf)
+    assert_equal rbuf.object_id, rv.object_id
+    assert_equal "WORLD" * 100, rbuf
+  end
+
+  def test_delete_reuse_buf
+    assert_nothing_raised do
+      @tmp = Tempfile.new('tdb')
+      File.unlink(@tmp.path)
+    end
+    @tdb = TDB.new(@tmp.path)
+    @tdb["HELLO"] = "WORLD"
+    rbuf = "HI"
+    rv = @tdb.delete("HELLO", rbuf)
+    assert_equal rbuf.object_id, rv.object_id
+    assert_equal "WORLD", rbuf
+    assert ! @tdb.include?("HELLO")
+
+    assert_nil @tdb.delete("HELLO!", rbuf)
+    assert_equal "", rbuf
+
+    @tdb["HELLO"] = "WORLD" * 100
+    rv = @tdb.delete("HELLO", rbuf)
+    assert_equal rbuf.object_id, rv.object_id
+    assert_equal "WORLD" * 100, rbuf
+    assert ! @tdb.include?("HELLO")
+  end
+
   def test_repack_file
     assert_nothing_raised do
       @tmp = Tempfile.new('tdb')