[ruby-core:26970] Re: Caching #to_s for immutables (and a possible future for constant-folding) [with patch v2]
From:
Kurt Stephens <ks@...>
Date:
2009-12-01 07:34:06 UTC
List:
ruby-core #26970
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
Attachments (1)
ruby-to_s_maybe_frozen.diff
(28 KB, text/x-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)
* ``<code>-Infinity</code>''.
*/
-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 <code>true</code> 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 <i>sym</i>.
*
@@ -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
+<nil>.
+
+ 2) Error:
+test_strptime__2(TestDateStrptime):
+NoMethodError: undefined method `rjust' for #<MatchData "%u" 1:nil 2:nil 3:"u">
+ /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]:
+<#<Encoding:US-ASCII>> expected but was
+<#<Encoding:ASCII-8BIT>>.
+
+ 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 "<p> <a href=\"../files/home/kurt/local/src/ruby-kstephens/test/rdoc/rdoc_markup_to_html_crossref_reference_rb.html\">/home/kurt/local/src/ruby-kstephens/test/rdoc/rdoc_markup_to_html_crossref_reference.rb</a> </p> ", not "<p> /home/kurt/local/src/ruby-kstephens/test/rdoc/rdoc_markup_to_html_crossref_reference.rb </p> ".
+/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:
+<#<Timeout::Error: execution expired>>.
+
+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
+<nil>.
+
+ 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]:
+<#<Encoding:US-ASCII>> expected but was
+<#<Encoding:ASCII-8BIT>>.
+
+ 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 "<p> <a href=\"../files/home/kurt/local/src/ruby-trunk/test/rdoc/rdoc_markup_to_html_crossref_reference_rb.html\">/home/kurt/local/src/ruby-trunk/test/rdoc/rdoc_markup_to_html_crossref_reference.rb</a> </p> ", not "<p> /home/kurt/local/src/ruby-trunk/test/rdoc/rdoc_markup_to_html_crossref_reference.rb </p> ".
+/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:
+<#<Timeout::Error: execution expired>>.
+
+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 <stdlib.h>
+
+#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;