[#27003] [Bug #2422] splat operator fails on array of 1 element — Raul Parolari <redmine@...>

Bug #2422: splat operator fails on array of 1 element

12 messages 2009/12/02

[#27025] [Backport #2431] StringIO#{gets,readlines} with "" (paragraph mode) trims last "\n" — Hiroshi NAKAMURA <redmine@...>

Backport #2431: StringIO#{gets,readlines} with "" (paragraph mode) trims last "\n"

8 messages 2009/12/04

[#27086] [Feature #2454] OpenSSL has no maintainer — Yui NARUSE <redmine@...>

Feature #2454: OpenSSL has no maintainer

16 messages 2009/12/07

[#27120] #to_enum ignores block? — Roger Pack <rogerdpack@...>

Is #to_enum ignoring its block expected?

11 messages 2009/12/09

[#27135] better GC? — Roger Pack <rogerdpack@...>

Could I put in a small plea for a better GC?

56 messages 2009/12/10
[#27136] Re: better GC? — Yukihiro Matsumoto <matz@...> 2009/12/11

Hi,

[#27476] Re: better GC? — Paul Brannan <pbrannan@...> 2010/01/07

On Fri, Dec 11, 2009 at 09:07:16AM +0900, Yukihiro Matsumoto wrote:

[#27477] Re: better GC? — Eero Saynatkari <ruby-ml@...> 2010/01/07

Excerpts from Paul Brannan's message of Thu Jan 07 21:53:34 +0200 2010:

[#27563] Re: better GC? — Brent Roman <brent@...> 2010/01/12

[#27199] [Backport #2488] thread usage can result in bad HANDLE — Roger Pack <redmine@...>

Backport #2488: thread usage can result in bad HANDLE

12 messages 2009/12/16

[#27286] [Bug #2515] Array#select! — Roger Pack <redmine@...>

Bug #2515: Array#select!

17 messages 2009/12/22

[#27327] [Bug #2531] Ruby 1.8.7-p248 fails to cross-compile same version — Luis Lavena <redmine@...>

Bug #2531: Ruby 1.8.7-p248 fails to cross-compile same version

9 messages 2009/12/25

[#27360] [Feature #2542] URI lib should be updated to RFC 39886 — Marc-Andre Lafortune <redmine@...>

Feature #2542: URI lib should be updated to RFC 39886

15 messages 2009/12/31

[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;

In This Thread