From: Kurt Stephens Date: 2009-12-01T16:34:06+09:00 Subject: [ruby-core:26970] Re: Caching #to_s for immutables (and a possible future for constant-folding) [with patch v2] This is a multi-part message in MIME format. --------------060803020005090701050703 Content-Type: text/plain; charset=ISO-8859-1; format=flowed Content-Transfer-Encoding: 7bit Attached is a new version of the patch. Kurt Stephens wrote: > > Roger Pack wrote: >>> 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. >> >> Wow that's a pretty good improvement. I like it. >> >> What's the speed difference for make test-all? >> > Didn't know about test-all. Right now test-all has some failures > related to frozen String. So there is still more work to be done. :( > I fixed issues uncovered by running make test-all. There were a few tests and modules that assumed Symbol#to_s was mutable -- #method_missing appears to be a hot-bed for this assumption. There were 7 common failures between the patched code and the svn trunk: WITHOUT THE PATCH: real 5m19.335s user 2m15.804s sys 0m35.850s WITH THE PATCH: real 4m37.127s user 1m51.243s sys 0m33.454s Approx 14% faster. > >>>> Does it break rails? >>> I don't know yet. I need to setup a proper Rails test environment. >> >> Yeah that would be nice, as well as comparing speeds for the test runs >> to see if it's useful. >> >>> 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. >> >> Would there be thread issues if each thread runs under the GLI? Not sure. >> >> Thanks. >> -r > Thank you, Kurt --------------060803020005090701050703 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/ext/dl/lib/dl/value.rb b/ext/dl/lib/dl/value.rb index 56dfcef..cdb2691 100644 --- a/ext/dl/lib/dl/value.rb +++ b/ext/dl/lib/dl/value.rb @@ -71,6 +71,7 @@ module DL if( ty.is_a?(Array) ) return arg.unpack('C*') else + arg = arg.dup if arg.frozen? case SIZEOF_VOIDP when SIZEOF_LONG return [arg].pack("p").unpack("l!")[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/lib/date/format.rb b/lib/date/format.rb index b30cf3a..c58c2c0 100644 --- a/lib/date/format.rb +++ b/lib/date/format.rb @@ -116,7 +116,7 @@ class Date end def method_missing(t, *args, &block) - t = t.to_s + t = t.to_s.dup set = t.chomp!('=') t = t.intern if set @@ -142,6 +142,7 @@ class Date end s = e.to_s + s = s.dup if s.frozen? if f[:s] && f[:p] == '0' f[:w] -= 1 diff --git a/lib/minitest/spec.rb b/lib/minitest/spec.rb index 81bd088..ee95051 100644 --- a/lib/minitest/spec.rb +++ b/lib/minitest/spec.rb @@ -23,7 +23,7 @@ class Module next if new_name =~ skip_re regexp, replacement = map.find { |re, _| new_name =~ re } - new_name.sub! regexp, replacement if replacement + new_name = new_name.sub regexp, replacement if replacement # warn "%-22p -> %p %p" % [meth, new_name, regexp] self.class_eval <<-EOM diff --git a/lib/minitest/unit.rb b/lib/minitest/unit.rb index e936f39..39533ef 100644 --- a/lib/minitest/unit.rb +++ b/lib/minitest/unit.rb @@ -46,6 +46,7 @@ module MiniTest module Assertions def mu_pp(obj) s = obj.inspect + s = s.dup if s.frozen? s = s.force_encoding(Encoding.default_external) if defined? Encoding s end diff --git a/lib/rexml/parsers/xpathparser.rb b/lib/rexml/parsers/xpathparser.rb index aafa72a..cf0448e 100644 --- a/lib/rexml/parsers/xpathparser.rb +++ b/lib/rexml/parsers/xpathparser.rb @@ -17,6 +17,7 @@ module REXML end def parse path + path = path.dup if path.frozen? path.gsub!(/([\(\[])\s+/, '\1') # Strip ignorable spaces path.gsub!( /\s+([\]\)])/, '\1' ) parsed = [] diff --git a/numeric.c b/numeric.c index 8d0c753..56e6f42 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}; @@ -3306,7 +3306,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..416624b 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,16 @@ 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); + if ( ! nil_to_s_str ) { + rb_gc_register_address(&nil_to_s_str); + nil_to_s_str = rb_usascii_str_new_cstr(""); + rb_str_freeze(nil_to_s_str); + } + return nil_to_s_str; } /* @@ -925,10 +932,17 @@ 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"); + if ( ! true_to_s_str ) { + rb_gc_register_address(&true_to_s_str); + true_to_s_str = rb_usascii_str_new_cstr("true"); + rb_str_freeze(true_to_s_str); + } + + return true_to_s_str; } @@ -1002,10 +1016,17 @@ 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"); + if ( ! false_to_s_str ) { + rb_gc_register_address(&false_to_s_str); + false_to_s_str = rb_usascii_str_new_cstr("false"); + rb_str_freeze(false_to_s_str); + } + + return false_to_s_str; } /* 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..814e529 100644 --- a/string.c +++ b/string.c @@ -7048,8 +7048,8 @@ sym_inspect(VALUE sym) /* * call-seq: - * sym.id2name => string - * sym.to_s => string + * sym.id2name => string (a new String) + * sym.to_s => string (a cached frozen String) * * Returns the name or string corresponding to sym. * @@ -7062,6 +7062,15 @@ rb_sym_to_s(VALUE sym) { ID id = SYM2ID(sym); + return rb_id2str(id); +} + + +static VALUE +sym_id2name(VALUE sym) +{ + ID id = SYM2ID(sym); + return str_new3(rb_cString, rb_id2str(id)); } @@ -7484,7 +7493,7 @@ Init_String(void) rb_define_method(rb_cSymbol, "===", sym_equal, 1); rb_define_method(rb_cSymbol, "inspect", sym_inspect, 0); rb_define_method(rb_cSymbol, "to_s", rb_sym_to_s, 0); - rb_define_method(rb_cSymbol, "id2name", rb_sym_to_s, 0); + rb_define_method(rb_cSymbol, "id2name", sym_id2name, 0); rb_define_method(rb_cSymbol, "intern", sym_to_sym, 0); rb_define_method(rb_cSymbol, "to_sym", sym_to_sym, 0); rb_define_method(rb_cSymbol, "to_proc", sym_to_proc, 0); diff --git a/test/ruby/test_sprintf_comb.rb b/test/ruby/test_sprintf_comb.rb index 5dee730..ee92288 100644 --- a/test/ruby/test_sprintf_comb.rb +++ b/test/ruby/test_sprintf_comb.rb @@ -432,6 +432,7 @@ class TestSprintfComb < Test::Unit::TestCase result = '0' * (precision+1 - result.length) + result end if precision != 0 || hs + result = result.dup if precision == 0 result << '.' else diff --git a/to_s_cache.c b/to_s_cache.c new file mode 100644 index 0000000..920e669 --- /dev/null +++ b/to_s_cache.c @@ -0,0 +1,385 @@ +/* + 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 + + 1) Failure: +test_array_comparisons(Rake::TestFileList) [/home/kurt/local/src/ruby-kstephens/test/rake/test_filelist.rb:462]: +<1> expected but was +. + + 2) Error: +test_strptime__2(TestDateStrptime): +NoMethodError: undefined method `rjust' for # + /home/kurt/local/src/ruby-kstephens/test/date/test_date_strptime.rb:335:in `block (2 levels) in test_strptime__2' + /home/kurt/local/src/ruby-kstephens/test/date/test_date_strptime.rb:315:in `each' + /home/kurt/local/src/ruby-kstephens/test/date/test_date_strptime.rb:315:in `block in test_strptime__2' + /home/kurt/local/src/ruby-kstephens/test/date/test_date_strptime.rb:313:in `each' + /home/kurt/local/src/ruby-kstephens/test/date/test_date_strptime.rb:313:in `test_strptime__2' + + 3) Failure: +test_systemcallerror_eq(TestDelegateClass) [/home/kurt/local/src/ruby-kstephens/test/test_delegate.rb:18]: +[ruby-dev:34808] + + 4) Failure: +test_rm_pathname(TestFileUtils) [/home/kurt/local/src/ruby-kstephens/test/fileutils/test_fileutils.rb:456]: +file not exist: tmp/[rmtmp]. +Expected block to return true value. + + 5) Failure: +test_sprintf_p(TestM17N) [/home/kurt/local/src/ruby-kstephens/test/ruby/test_m17n.rb:764]: +<#> expected but was +<#>. + + 6) Failure: +test_handle_special_CROSSREF_no_underscore(TestRDocMarkupToHtmlCrossref) [/home/kurt/local/src/ruby-kstephens/test/rdoc/test_rdoc_markup_to_html_crossref.rb:142]: +Expected "

/home/kurt/local/src/ruby-kstephens/test/rdoc/rdoc_markup_to_html_crossref_reference.rb

", not "

/home/kurt/local/src/ruby-kstephens/test/rdoc/rdoc_markup_to_html_crossref_reference.rb

". +/home/kurt/local/src/ruby-kstephens/test/rdoc/test_rdoc_markup_to_html_crossref.rb:142:in `verify_file_crossref' +/home/kurt/local/src/ruby-kstephens/test/rdoc/test_rdoc_markup_to_html_crossref.rb:211:in `verify_invariant_crossrefs' +/home/kurt/local/src/ruby-kstephens/test/rdoc/test_rdoc_markup_to_html_crossref.rb:234:in `test_handle_special_CROSSREF_no_underscore' + + 7) Failure: +test_exit_action(TestSignal) [/home/kurt/local/src/ruby-kstephens/test/ruby/test_signal.rb:52]: +[ruby-dev:26128]. +Exception raised: +<#>. + +6836 tests, 1833816 assertions, 6 failures, 1 errors, 0 skips +make: *** [yes-test-all] Error 1 + +real 4m37.127s +user 1m51.243s +sys 0m33.454s + + > ./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) + +Finished in 257.595721 seconds + + > time mspec -t /home/kurt/local/ruby/kstephens/bin/ruby +Finished in 257.595721 seconds + +2849 files, 13035 examples, 186461 expectations, 231 failures, 441 errors + +real 4m18.626s +user 0m23.349s +sys 0m8.685s + + WITHOUT to_s_cache.c: + + > time make test >/dev/null 2>&1 + +real 0m53.779s +user 0m16.733s +sys 0m8.053s + + 1) Failure: +test_array_comparisons(Rake::TestFileList) [/home/kurt/local/src/ruby-trunk/test/rake/test_filelist.rb:462]: +<1> expected but was +. + + 2) Failure: +test_systemcallerror_eq(TestDelegateClass) [/home/kurt/local/src/ruby-trunk/test/test_delegate.rb:18]: +[ruby-dev:34808] + + 3) Failure: +test_rm_pathname(TestFileUtils) [/home/kurt/local/src/ruby-trunk/test/fileutils/test_fileutils.rb:456]: +file not exist: tmp/[rmtmp]. +Expected block to return true value. + + 4) Failure: +test_sprintf_p(TestM17N) [/home/kurt/local/src/ruby-trunk/test/ruby/test_m17n.rb:764]: +<#> expected but was +<#>. + + 5) Failure: +test_handle_special_CROSSREF_no_underscore(TestRDocMarkupToHtmlCrossref) [/home/kurt/local/src/ruby-trunk/test/rdoc/test_rdoc_markup_to_html_crossref.rb:142]: +Expected "

/home/kurt/local/src/ruby-trunk/test/rdoc/rdoc_markup_to_html_crossref_reference.rb

", not "

/home/kurt/local/src/ruby-trunk/test/rdoc/rdoc_markup_to_html_crossref_reference.rb

". +/home/kurt/local/src/ruby-trunk/test/rdoc/test_rdoc_markup_to_html_crossref.rb:142:in `verify_file_crossref' +/home/kurt/local/src/ruby-trunk/test/rdoc/test_rdoc_markup_to_html_crossref.rb:211:in `verify_invariant_crossrefs' +/home/kurt/local/src/ruby-trunk/test/rdoc/test_rdoc_markup_to_html_crossref.rb:234:in `test_handle_special_CROSSREF_no_underscore' + + 6) Failure: +test_exit_action(TestSignal) [/home/kurt/local/src/ruby-trunk/test/ruby/test_signal.rb:52]: +[ruby-dev:26128]. +Exception raised: +<#>. + +6836 tests, 1841206 assertions, 6 failures, 0 errors, 0 skips +make: *** [yes-test-all] Error 1 + +real 5m19.335s +user 2m15.804s +sys 0m35.850s + + > ~/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) + + > time mspec -t /home/kurt/local/ruby/trunk/bin/ruby +2849 files, 13035 examples, 187339 expectations, 246 failures, 318 errors + +real 4m30.655s +user 0m28.386s +sys 0m9.857s + +*/ + +#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); +#if 0 + /* UGLY: if object is already frozen, we cannot update the ivar! */ + if ( ! OBJ_FROZEN(x) ) { + rb_str_freeze(str); + rb_ivar_set(x, id_ivar_to_s, str); + } +#else + rb_str_freeze(str); + rb_ivar_set_(x, id_ivar_to_s, str, 0); +#endif + } + 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); + diff --git a/variable.c b/variable.c index b3f6244..80b4759 100644 --- a/variable.c +++ b/variable.c @@ -1033,7 +1033,7 @@ rb_attr_get(VALUE obj, ID id) } VALUE -rb_ivar_set(VALUE obj, ID id, VALUE val) +rb_ivar_set_(VALUE obj, ID id, VALUE val, int check_frozen) { struct st_table *iv_index_tbl; st_data_t index; @@ -1042,7 +1042,7 @@ rb_ivar_set(VALUE obj, ID id, VALUE val) if (!OBJ_UNTRUSTED(obj) && rb_safe_level() >= 4) rb_raise(rb_eSecurityError, "Insecure: can't modify instance variable"); - if (OBJ_FROZEN(obj)) rb_error_frozen("object"); + if ( check_frozen && OBJ_FROZEN(obj) ) rb_error_frozen("object"); switch (TYPE(obj)) { case T_OBJECT: iv_index_tbl = ROBJECT_IV_INDEX_TBL(obj); @@ -1107,6 +1107,12 @@ rb_ivar_set(VALUE obj, ID id, VALUE val) } VALUE +rb_ivar_set(VALUE obj, ID id, VALUE val) +{ + return rb_ivar_set_(obj, id, val, 1); +} + +VALUE rb_ivar_defined(VALUE obj, ID id) { VALUE val; --------------060803020005090701050703--