From: Kurt Stephens Date: 2009-11-30T20:47:53+09:00 Subject: [ruby-core:26958] Re: Caching #to_s for immutables (and a possible future for constant-folding) [with patch] This is a multi-part message in MIME format. --------------050401040706090109050903 Content-Type: text/plain; charset=ISO-8859-1; format=flowed Content-Transfer-Encoding: 7bit The attached patch add caching of #to_s results to the main immutable classes in MRI Ruby core. It requires that callers assume that #to_s may return a frozen String, if the receiver is not already a String. The semantic change is the source of the improvement. It should be easy to back-port to 1.8. It returns frozen Strings for nil.to_s, true.to_s, and false.to_s. Float#to_s, Rational#to_s and Complex#to_s use a @to_s instance variable to cache the value for the lifetime of the receiver. These methods can be changed to use an external hash table. Fixnum#to_s and Bignum#to_s use an external hash table because they have a radix option. I'm not convinced that the Fixnum#to_s cache is useful in real practice. The hash tables are fixed to 255 entries; I have not experimentally tuned them. The hash function is quick but is likely to be sub-optimal. Rational#to_s and Complex#to_s assumed that #to_s returned a mutable String. That was changed to build up from an empty String. Those methods could probably be improved but I'm not sure if dup'ing is faster or slower. Overall the patch should make: puts :'foo bar'; str << :'baz' more space- and time-efficient than: puts 'foo bar'; str << 'baz' since all String literals create garbage and Symbol#to_s is now a no-op. This technique could be easily be extended to cache #inspect if we adopt the rule "#inspect may return a frozen String." Could also cache Float, Bignum, Rational, and Complex construction using the same hash tables with very little effort. Roger Pack wrote: >> It reduces the number of #to_s Strings created during the MRI test suite >> for NilClass, TrueClass, FalseClass, Symbol and Float objects by 1890 >> Strings. > > Sounds like it has potential, especially for long-lived constants and > for symbols, which themselves are never collected anyway. > Symbol already had a frozen cached String; the patch to Symbol#to_s removes the duplication of it. (Why aren't IDs just Symbol VALUES? The Symbol code in parse.y seems really crufty. Is it a bootstrapping issue? Do Symbols really need to be that closely tied to the parser?) > Perhaps adding an Object#to_s_nonfrozen would be convenient? > The attached patch aliases the old #to_s methods as #to_s_new. > Also do you have any benchmarks showing improvement? Yes. The MRI test suite runs at 45 sec with these changes and at 53 sec without. The MRI tests pass with no issues. There is a naive test in the attached patch that demonstrates the improvements. >Does it break rails? I don't know yet. I need to setup a proper Rails test environment. There maybe some native thread issues with the cache hash tables, but I did start adding a mutex to each one. I have not tested on 64-bit or anything other that Ubunutu 9.10. See http://github.com/kstephens/ruby/commits/to_s_maybe_frozen/ for more details. > -r > Thanks, Kurt Stephens http://kurtstephens.com --------------050401040706090109050903 Content-Type: text/x-diff; name="ruby-to_s_maybe_frozen.diff" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="ruby-to_s_maybe_frozen.diff" diff --git a/.gitignore b/.gitignore index 645d1c4..559c24e 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ *.sav *.swp *~ +*.o .*-* .*.list .*.time diff --git a/common.mk b/common.mk index d11866c..30d6e43 100644 --- a/common.mk +++ b/common.mk @@ -82,6 +82,8 @@ COMMONOBJS = array.$(OBJEXT) \ vm_dump.$(OBJEXT) \ thread.$(OBJEXT) \ cont.$(OBJEXT) \ + value_cache.$(OBJEXT) \ + to_s_cache.$(OBJEXT) \ $(BUILTIN_ENCOBJS) \ $(BUILTIN_TRANSOBJS) \ $(MISSING) diff --git a/complex.c b/complex.c index c6c9d37..497f287 100644 --- a/complex.c +++ b/complex.c @@ -1225,7 +1225,8 @@ f_format(VALUE self, VALUE (*func)(VALUE)) impos = f_tpositive_p(dat->imag); - s = (*func)(dat->real); + s = rb_usascii_str_new(0, 0); + rb_str_concat(s, (*func)(dat->real)); rb_str_cat2(s, !impos ? "-" : "+"); rb_str_concat(s, (*func)(f_abs(dat->imag))); @@ -1242,8 +1243,8 @@ f_format(VALUE self, VALUE (*func)(VALUE)) * * Returns the value as a string. */ -static VALUE -nucomp_to_s(VALUE self) +VALUE +rb_comp2str(VALUE self) { return f_format(self, f_to_s); } @@ -1970,7 +1971,7 @@ Init_Complex(void) rb_define_method(rb_cComplex, "hash", nucomp_hash, 0); rb_define_method(rb_cComplex, "eql?", nucomp_eql_p, 1); - rb_define_method(rb_cComplex, "to_s", nucomp_to_s, 0); + rb_define_method(rb_cComplex, "to_s", rb_comp2str, 0); rb_define_method(rb_cComplex, "inspect", nucomp_inspect, 0); rb_define_method(rb_cComplex, "marshal_dump", nucomp_marshal_dump, 0); diff --git a/gc.c b/gc.c index 4021220..f82fb4d 100644 --- a/gc.c +++ b/gc.c @@ -2170,6 +2170,7 @@ garbage_collect(rb_objspace_t *objspace) rb_gc_mark_threads(); rb_gc_mark_symbols(); rb_gc_mark_encodings(); + rb_gc_mark_to_s_cache(); /* mark protected global variables */ for (list = global_List; list; list = list->next) { @@ -2200,6 +2201,8 @@ garbage_collect(rb_objspace_t *objspace) gc_sweep(objspace); GC_PROF_SWEEP_TIMER_STOP; + rb_to_s_cache_gc_after(); + GC_PROF_TIMER_STOP; if (GC_NOTIFY) printf("end garbage_collect()\n"); return TRUE; diff --git a/inits.c b/inits.c index 6fb7463..3bd75d3 100644 --- a/inits.c +++ b/inits.c @@ -58,6 +58,7 @@ rb_call_inits(void) CALL(Cont); CALL(Rational); CALL(Complex); + CALL(to_s_cache); CALL(version); } #undef CALL diff --git a/numeric.c b/numeric.c index 8d0c753..5d476bf 100644 --- a/numeric.c +++ b/numeric.c @@ -541,8 +541,8 @@ rb_float_new(double d) * ``-Infinity''. */ -static VALUE -flo_to_s(VALUE flt) +VALUE +rb_flo2str(VALUE flt) { enum {decimal_mant = DBL_MANT_DIG-DBL_DIG}; enum {float_dig = DBL_DIG+1}; @@ -575,6 +575,7 @@ flo_to_s(VALUE flt) return rb_usascii_str_new2(buf); } + /* * MISSING: documentation */ @@ -3306,7 +3307,7 @@ Init_Numeric(void) rb_define_const(rb_cFloat, "MAX", DBL2NUM(DBL_MAX)); rb_define_const(rb_cFloat, "EPSILON", DBL2NUM(DBL_EPSILON)); - rb_define_method(rb_cFloat, "to_s", flo_to_s, 0); + rb_define_method(rb_cFloat, "to_s", rb_flo2str, 0); rb_define_method(rb_cFloat, "coerce", flo_coerce, 1); rb_define_method(rb_cFloat, "-@", flo_uminus, 0); rb_define_method(rb_cFloat, "+", flo_plus, 1); diff --git a/object.c b/object.c index 10eb983..960a6c6 100644 --- a/object.c +++ b/object.c @@ -865,6 +865,7 @@ nil_to_f(VALUE obj) return DBL2NUM(0.0); } + /* * call-seq: * nil.to_s => "" @@ -872,10 +873,11 @@ nil_to_f(VALUE obj) * Always returns the empty string. */ +static VALUE nil_to_s_str; static VALUE nil_to_s(VALUE obj) { - return rb_usascii_str_new(0, 0); + return nil_to_s_str; } /* @@ -925,10 +927,11 @@ nil_inspect(VALUE obj) * The string representation of true is "true". */ +static VALUE true_to_s_str; static VALUE true_to_s(VALUE obj) { - return rb_usascii_str_new2("true"); + return true_to_s_str; } @@ -1002,10 +1005,11 @@ true_xor(VALUE obj, VALUE obj2) * 'nuf said... */ +static VALUE false_to_s_str; static VALUE false_to_s(VALUE obj) { - return rb_usascii_str_new2("false"); + return false_to_s_str; } /* @@ -2611,6 +2615,7 @@ Init_Object(void) rb_cNilClass = rb_define_class("NilClass", rb_cObject); rb_define_method(rb_cNilClass, "to_i", nil_to_i, 0); rb_define_method(rb_cNilClass, "to_f", nil_to_f, 0); + rb_global_variable(&nil_to_s_str); nil_to_s_str = rb_str_freeze(rb_usascii_str_new(0, 0)); rb_define_method(rb_cNilClass, "to_s", nil_to_s, 0); rb_define_method(rb_cNilClass, "to_a", nil_to_a, 0); rb_define_method(rb_cNilClass, "inspect", nil_inspect, 0); @@ -2682,6 +2687,7 @@ Init_Object(void) rb_undef_alloc_func(rb_cData); rb_cTrueClass = rb_define_class("TrueClass", rb_cObject); + rb_global_variable(&true_to_s_str); true_to_s_str = rb_str_freeze(rb_usascii_str_new2("true")); rb_define_method(rb_cTrueClass, "to_s", true_to_s, 0); rb_define_method(rb_cTrueClass, "&", true_and, 1); rb_define_method(rb_cTrueClass, "|", true_or, 1); @@ -2691,6 +2697,7 @@ Init_Object(void) rb_define_global_const("TRUE", Qtrue); rb_cFalseClass = rb_define_class("FalseClass", rb_cObject); + rb_global_variable(&false_to_s_str); false_to_s_str = rb_str_freeze(rb_usascii_str_new2("false")); rb_define_method(rb_cFalseClass, "to_s", false_to_s, 0); rb_define_method(rb_cFalseClass, "&", false_and, 1); rb_define_method(rb_cFalseClass, "|", false_or, 1); diff --git a/rational.c b/rational.c index 43917bc..9317896 100644 --- a/rational.c +++ b/rational.c @@ -1513,7 +1513,8 @@ f_format(VALUE self, VALUE (*func)(VALUE)) VALUE s; get_dat1(self); - s = (*func)(dat->num); + s = rb_usascii_str_new(0, 0); + rb_str_concat(s, (*func)(dat->num)); rb_str_cat2(s, "/"); rb_str_concat(s, (*func)(dat->den)); @@ -1532,8 +1533,8 @@ f_format(VALUE self, VALUE (*func)(VALUE)) * Rational(-8, 6).to_s #=> "-4/3" * Rational('0.5').to_s #=> "1/2" */ -static VALUE -nurat_to_s(VALUE self) +VALUE +rb_rat2str(VALUE self) { return f_format(self, f_to_s); } @@ -2328,7 +2329,7 @@ Init_Rational(void) rb_define_method(rb_cRational, "hash", nurat_hash, 0); - rb_define_method(rb_cRational, "to_s", nurat_to_s, 0); + rb_define_method(rb_cRational, "to_s", rb_rat2str, 0); rb_define_method(rb_cRational, "inspect", nurat_inspect, 0); rb_define_method(rb_cRational, "marshal_dump", nurat_marshal_dump, 0); diff --git a/string.c b/string.c index a722ac1..8a7cd34 100644 --- a/string.c +++ b/string.c @@ -7062,7 +7062,7 @@ rb_sym_to_s(VALUE sym) { ID id = SYM2ID(sym); - return str_new3(rb_cString, rb_id2str(id)); + return rb_id2str(id); } diff --git a/to_s_cache.c b/to_s_cache.c new file mode 100644 index 0000000..91cb9ff --- /dev/null +++ b/to_s_cache.c @@ -0,0 +1,272 @@ +/* + to_s_cache.c: Coded by Kurt Stephens 2009 + + Caches #to_s results as frozen Strings for immutable values. + + THIS CODE MAY NOT BE NATIVE THREAD SAFE. + + Non-immediates: Float, etc may perform better with adding an instance variable to hold the + cached #to_s String value. + + + WITH to_s_cache.c: + + > time make test >/dev/null 2>&1 + +real 0m45.892s +user 0m14.089s +sys 0m7.568s + + > ./ruby to_s_test.rb + user system total real +String#to_s 1.560000 0.000000 1.560000 ( 1.577850) +NilClass#to_s 1.450000 0.000000 1.450000 ( 1.463904) +FalseClass#to_s 1.470000 0.000000 1.470000 ( 1.483500) +Symbol#to_s 1.640000 0.000000 1.640000 ( 1.657962) +Fixnum#to_s 1.800000 0.010000 1.810000 ( 1.802241) +Float#to_s 1.700000 0.000000 1.700000 ( 1.715049) +Bignum#to_s 1.790000 0.000000 1.790000 ( 1.802215) +Rational#to_s 1.720000 0.000000 1.720000 ( 1.731056) +Complex#to_s 1.770000 0.000000 1.770000 ( 1.809769) + user system total real +String#inspect 4.550000 0.000000 4.550000 ( 4.562447) +NilClass#inspect 2.310000 0.000000 2.310000 ( 2.302505) +FalseClass#inspect 1.980000 0.000000 1.980000 ( 1.982822) +Symbol#inspect 4.920000 0.000000 4.920000 ( 4.915270) +Fixnum#inspect 2.220000 0.000000 2.220000 ( 2.226601) +Float#inspect 2.270000 0.000000 2.270000 ( 2.269433) +Bignum#inspect 2.250000 0.000000 2.250000 ( 2.244143) +Rational#inspect 9.550000 0.000000 9.550000 ( 9.554098) +Complex#inspect 43.760000 0.010000 43.770000 ( 43.754527) + + WITHOUT to_s_cache.c: + + > time make test >/dev/null 2>&1 + +real 0m53.779s +user 0m16.733s +sys 0m8.053s + + > ~/local/ruby/trunk/bin/ruby to_s_test.rb + user system total real +String#to_s 1.530000 0.000000 1.530000 ( 1.554432) +NilClass#to_s 2.090000 0.000000 2.090000 ( 2.096685) +FalseClass#to_s 2.300000 0.000000 2.300000 ( 2.296527) +Symbol#to_s 2.650000 0.000000 2.650000 ( 2.647547) +Fixnum#to_s 2.760000 0.010000 2.770000 ( 2.758249) +Float#to_s 19.380000 0.000000 19.380000 ( 19.505156) +Bignum#to_s 10.740000 0.000000 10.740000 ( 10.909859) +Rational#to_s 6.380000 0.010000 6.390000 ( 6.420801) +Complex#to_s 28.360000 0.000000 28.360000 ( 28.585827) + user system total real +String#inspect 4.730000 0.000000 4.730000 ( 4.763734) +NilClass#inspect 2.290000 0.000000 2.290000 ( 2.321438) +FalseClass#inspect 2.960000 0.000000 2.960000 ( 2.978007) +Symbol#inspect 5.200000 0.000000 5.200000 ( 5.248013) +Fixnum#inspect 3.290000 0.000000 3.290000 ( 3.308988) +Float#inspect 19.630000 0.000000 19.630000 ( 19.878016) +Bignum#inspect 11.320000 0.010000 11.330000 ( 11.384277) +Rational#inspect 10.750000 0.000000 10.750000 ( 10.751159) +Complex#inspect 35.370000 0.010000 35.380000 ( 35.355241) + +*/ + +#include "value_cache.h" + +/*************************************************************************************/ + +static ID id_to_s, id_to_s_new, id_ivar_to_s; + +static rb_value_cache fix_cache = { "fix" }; +static +VALUE fix_to_s_cached(int argc, VALUE *argv, VALUE x) +{ + rb_value_cache_val val; + rb_value_cache_entry *e; + volatile VALUE result; + + int base; + + if (argc == 0) base = 10; + else { + VALUE b; + + rb_scan_args(argc, argv, "01", &b); + base = NUM2INT(b); + } + + val.u.x[0] = val.u.x[1] = 0; + val.u.i = NUM2INT(x); + e = rb_value_cache_find(&fix_cache, &val, base); + if ( ! e->result_valid ) { + e->result_valid = 1; + e->result = rb_fix2str(x, base); + rb_str_freeze(e->result); + } + result = e->result; + if ( fix_cache.mutex ) rb_mutex_unlock(fix_cache.mutex); + return result; +} + + +static rb_value_cache flo_cache = { "flo" }; +static +VALUE flo_to_s_cached(VALUE x) +{ + rb_value_cache_val val; + rb_value_cache_entry *e; + volatile VALUE result; + + val.u.x[0] = val.u.x[1] = 0; + val.u.d = RFLOAT_VALUE(x); + e = rb_value_cache_find(&flo_cache, &val, 0); + if ( ! e->result_valid ) { + e->result_valid = 1; + e->result = rb_flo2str(x); + rb_str_freeze(e->result); + } + result = e->result; + if ( flo_cache.mutex ) rb_mutex_unlock(flo_cache.mutex); + return result; +} + + +static rb_value_cache big_cache = { "big" }; +static +VALUE big_to_s_cached(int argc, VALUE *argv, VALUE x) +{ + rb_value_cache_val val; + rb_value_cache_entry *e; + volatile VALUE result; + + int base; + + if (argc == 0) base = 10; + else { + VALUE b; + + rb_scan_args(argc, argv, "01", &b); + base = NUM2INT(b); + } + + val.u.x[0] = val.u.x[1] = 0; + val.u.o = x; + e = rb_value_cache_find(&big_cache, &val, base); + if ( ! e->result_valid ) { + e->result_valid = 1; + e->result = rb_big2str(x, base); + rb_str_freeze(e->result); + } + result = e->result; + if ( big_cache.mutex ) rb_mutex_unlock(big_cache.mutex); + return result; +} + + +static rb_value_cache rat_cache = { "rat" }; +static +VALUE rat_to_s_cached(VALUE x) +{ + rb_value_cache_val val; + rb_value_cache_entry *e; + volatile VALUE result; + + val.u.x[0] = val.u.x[1] = 0; + val.u.o = x; + e = rb_value_cache_find(&rat_cache, &val, 0); + if ( ! e->result_valid ) { + e->result_valid = 1; + e->result = rb_rat2str(x); + rb_str_freeze(e->result); + } + result = e->result; + if ( rat_cache.mutex ) rb_mutex_unlock(rat_cache.mutex); + return result; +} + + +static rb_value_cache comp_cache = { "comp" }; +static +VALUE comp_to_s_cached(VALUE x) +{ + rb_value_cache_val val; + rb_value_cache_entry *e; + volatile VALUE result; + + val.u.x[0] = val.u.x[1] = 0; + val.u.o = x; + e = rb_value_cache_find(&comp_cache, &val, 0); + if ( ! e->result_valid ) { + e->result_valid = 1; + e->result = rb_comp2str(x); + rb_str_freeze(e->result); + } + result = e->result; + if ( comp_cache.mutex ) rb_mutex_unlock(comp_cache.mutex); + return result; +} + + +/********************************************************************/ + +void rb_gc_mark_to_s_cache() +{ + rb_value_cache_gc_mark(&fix_cache); + rb_value_cache_gc_mark(&flo_cache); +} + +void rb_to_s_cache_gc_after() +{ + // rb_value_cache_clear(&fix_cache); + // rb_value_cache_clear(&flo_cache); + rb_value_cache_clear(&big_cache); + rb_value_cache_clear(&rat_cache); + rb_value_cache_clear(&comp_cache); +} + +static VALUE +any_to_s_cached_ivar(VALUE x) +{ + VALUE str; + /* Do we need a mutex?. */ + str = rb_ivar_get(x, id_ivar_to_s); + if ( NIL_P(str) ) { + str = rb_funcall(x, id_to_s_new, 0); + rb_str_freeze(str); + rb_ivar_set(x, id_ivar_to_s, str); + } + return str; +} + +/********************************************************************/ + +void Init_to_s_cache() { + id_ivar_to_s = rb_intern("@to_s"); + + id_to_s = rb_intern("to_s"); + id_to_s_new = rb_intern("to_s_new"); + + rb_value_cache_initialize(&fix_cache); + rb_define_alias( rb_cFixnum, "to_s_new", "to_s"); + rb_define_method(rb_cFixnum, "to_s", fix_to_s_cached, -1); + + rb_value_cache_initialize(&flo_cache); + rb_define_alias( rb_cFloat, "to_s_new", "to_s"); + // rb_define_method(rb_cFloat, "to_s", flo_to_s_cached, 0); + rb_define_method(rb_cFloat, "to_s", any_to_s_cached_ivar, 0); + + rb_value_cache_initialize(&big_cache); + rb_define_alias( rb_cBignum, "to_s_new", "to_s"); + rb_define_method(rb_cBignum, "to_s", big_to_s_cached, -1); + + rb_value_cache_initialize(&rat_cache); + rb_define_alias( rb_cRational, "to_s_new", "to_s"); + // rb_define_method(rb_cRational, "to_s", rat_to_s_cached, 0); + rb_define_method(rb_cRational, "to_s", any_to_s_cached_ivar, 0); + + rb_value_cache_initialize(&comp_cache); + rb_define_alias( rb_cComplex, "to_s_new", "to_s"); + // rb_define_method(rb_cComplex, "to_s", comp_to_s_cached, 0); + rb_define_method(rb_cComplex, "to_s", any_to_s_cached_ivar, 0); +} + diff --git a/to_s_test.rb b/to_s_test.rb new file mode 100644 index 0000000..1b075b8 --- /dev/null +++ b/to_s_test.rb @@ -0,0 +1,40 @@ +require 'benchmark' +require 'rational' +require 'complex' + +OBJECTS = [ + "", + nil, + false, + :symbol, + 12345, + 12345.678, + 1234567890123456789, + Rational(1234, 5678), + Complex(1234, 12345.678), +] + +=begin +OBJECTS.each do | obj | + puts "#{obj.class.name} => #{Marshal.dump(obj).inspect}" +end +=end + + +N = 10000000 +Benchmark.bm(22) do | bm | + OBJECTS.each do | obj | + ObjectSpace.garbage_collect + bm.report("#{obj.class.name}#to_s") { N.times { obj.to_s } } + end +end + +Benchmark.bm(22) do | bm | + OBJECTS.each do | obj | + ObjectSpace.garbage_collect + bm.report("#{obj.class.name}#inspect") { N.times { obj.inspect } } + end +end + + + diff --git a/value_cache.c b/value_cache.c new file mode 100644 index 0000000..bd43a8d --- /dev/null +++ b/value_cache.c @@ -0,0 +1,102 @@ +#include "value_cache.h" +#include + +#ifndef RB_VALUE_CACHE_LOG +#define RB_VALUE_CACHE_LOG 0 +#endif + +#if RB_VALUE_CACHE_LOG +FILE* rb_value_cache_log() { + static FILE *log; + if ( ! log ) { + log = fopen("/tmp/ruby_value_cache.log", "a+"); + sprintf(log, "STARTING: ruby pid=%d\n", getpid()); + } + return log; +} +#endif + +void rb_value_cache_initialize(rb_value_cache *self) +{ +#if 0 + if ( ! self->mutex ) { + extern VALUE rb_cMutex; + self->mutex = rb_funcall(rb_cMutex, rb_intern("new"), 0); + } +#endif +} + +rb_value_cache_entry * rb_value_cache_find(rb_value_cache *self, rb_value_cache_val *val, int option ) { + unsigned int entries_n = sizeof(self->entries) / sizeof(self->entries[0]); + /* crappy but fast hash function */ + unsigned int hash = (option + val->u.x[0] ^ (val->u.x[1] << 1) ^ (val->u.x[0] << 3) ^ (val->u.x[1] >> 5)) % entries_n; + rb_value_cache_entry *e = &self->entries[hash % entries_n]; + + if ( self->mutex ) { + rb_mutex_lock(self->mutex); + } + + if ( e->result_valid && (e->val.u.x[0] == val->u.x[0] && e->val.u.x[1] == val->u.x[1] && e->option == option) ) { + if ( ! ++ self->n_hit_since_clear ) ++ self->n_hit_since_clear; +#if RB_VALUE_CACHE_LOG + fprintf(rb_value_cache_log(), "%-8s: [%4u] hit %p %d\n", self->name, hash, (void*) val->u.o, (int) option); +#endif + } else { + e->result_valid = 0; + e->result = 0; + e->val = *val; + e->option = option; + if ( ! ++ self->n_miss_since_clear ) ++ self->n_miss_since_clear; + +#if RB_VALUE_CACHE_LOG + fprintf(rb_value_cache_log(), "%-8s: [%4u] miss %p %d\n", self->name, hash, (void*) val->u.o, (int) option); +#endif +} + return e; +} + +void rb_value_cache_clear(rb_value_cache *self) +{ + unsigned int entries_n = sizeof(self->entries) / sizeof(self->entries[0]); + unsigned int i; + rb_value_cache_entry *e; + + if ( (self->n_hit_since_clear || self->n_miss_since_clear) ) { + +#if RB_VALUE_CACHE_LOG + fprintf(rb_value_cache_log(), "%-8s: clear\n", self->name); +#endif + + e = self->entries; + for ( i = entries_n; (-- i) > 0; ) { + e->result_valid = 0; + e->result = 0; + ++ e; + } + } + self->n_hit_since_clear = self->n_miss_since_clear = 0; +} + + +void rb_value_cache_gc_mark(rb_value_cache *self) +{ + unsigned int entries_n = sizeof(self->entries) / sizeof(self->entries[0]); + unsigned int i; + rb_value_cache_entry *e = self->entries; + + if ( ! (self->n_hit_since_clear || self->n_miss_since_clear) ) { + return; + } + +#if RB_VALUE_CACHE_LOG + fprintf(rb_value_cache_log(), "%-8s: gc_mark\n", self->name); +#endif + + for ( i = entries_n; (-- i) > 0; ) { + if ( e->result && e->result_valid ) + rb_gc_mark(e->result); + ++ e; + } +} + + diff --git a/value_cache.h b/value_cache.h new file mode 100644 index 0000000..5188da1 --- /dev/null +++ b/value_cache.h @@ -0,0 +1,39 @@ + +#include "ruby/ruby.h" +#include "ruby/intern.h" + +typedef struct rb_value_cache_val { + union { + VALUE o; + int i; + double d; + unsigned int x[2]; + } u; +} rb_value_cache_val; + +typedef struct rb_value_cache_entry { + rb_value_cache_val val; + int option; + VALUE result; + int result_valid; +} rb_value_cache_entry; + +typedef struct rb_value_cache { + const char *name; + rb_value_cache_entry entries[255]; /* arbitrary size */ + VALUE mutex; + int n_hit_since_clear; + int n_miss_since_clear; +} rb_value_cache; + + +FILE* rb_value_cache_log(); + +void rb_value_cache_initialize(rb_value_cache *self); + +/* Calls rb_mutex_lock(self->mutex); expects caller to rb_mutex_unlock() */ +rb_value_cache_entry * rb_value_cache_find(rb_value_cache *self, rb_value_cache_val *val, int option ); + +void rb_value_cache_clear(rb_value_cache *self); +void rb_value_cache_gc_mark(rb_value_cache *self); + --------------050401040706090109050903--