[#26488] Add Standard Deviation Function to Math Module — Daniel Cohen <danielc2017@...>

This patch adds a Standard Deviation function to the Math Module. It takes

25 messages 2009/11/02
[#26489] Re: Add Standard Deviation Function to Math Module — Yukihiro Matsumoto <matz@...> 2009/11/03

Hi,

[#26490] Re: Add Standard Deviation Function to Math Module — Daniel Cohen <danielc2017@...> 2009/11/03

OK,

[#26493] Re: Add Standard Deviation Function to Math Module — Yukihiro Matsumoto <matz@...> 2009/11/03

Hi,

[#26511] Re: Add Standard Deviation Function to Math Module — Yusuke ENDOH <mame@...> 2009/11/03

Hi,

[#26492] HashWithIndifferentAccess to core — Urabe Shyouhei <shyouhei@...>

Hello,

35 messages 2009/11/03
[#26496] Re: HashWithIndifferentAccess to core — Yukihiro Matsumoto <matz@...> 2009/11/03

Hi,

[#26507] Re: HashWithIndifferentAccess to core — Jeremy Kemper <jeremy@...> 2009/11/03

On Tue, Nov 3, 2009 at 6:48 AM, Yukihiro Matsumoto <matz@ruby-lang.org> wro=

[#26514] Re: HashWithIndifferentAccess to core — "Martin J. Dst" <duerst@...> 2009/11/04

Just a thought: What about implementing this with an option on Hash:new,

[#26522] Re: HashWithIndifferentAccess to core — Yusuke ENDOH <mame@...> 2009/11/04

Hi,

[#26555] Re: HashWithIndifferentAccess to core — Yukihiro Matsumoto <matz@...> 2009/11/05

Hi,

[#26584] Re: HashWithIndifferentAccess to core — Yugui <yugui@...> 2009/11/07

2009/11/6 Yukihiro Matsumoto <matz@ruby-lang.org>:

[#26589] Re: HashWithIndifferentAccess to core — Yukihiro Matsumoto <matz@...> 2009/11/07

Hi,

[#26593] Re: HashWithIndifferentAccess to core — Lourens Naud<lourens@...> 2009/11/07

Hi,

[#26523] [Bug #2330] Non systematic segmentation fault with autoload rubyspec — Marc-Andre Lafortune <redmine@...>

Bug #2330: Non systematic segmentation fault with autoload rubyspec

12 messages 2009/11/04

[#26560] [Feature #2340] Removing YAML/Syck — Yui NARUSE <redmine@...>

Feature #2340: Removing YAML/Syck

38 messages 2009/11/06
[#26562] [Feature #2340] Removing YAML/Syck — Yui NARUSE <redmine@...> 2009/11/06

Issue #2340 has been updated by Yui NARUSE.

[#26567] Re: [Feature #2340] Removing YAML/Syck — James Edward Gray II <james@...> 2009/11/06

On Nov 6, 2009, at 4:02 AM, Yui NARUSE wrote:

[#26568] Re: [Feature #2340] Removing YAML/Syck — Jon <jon.forums@...> 2009/11/06

> > Issue #2340 has been updated by Yui NARUSE.

[#26571] Re: [Feature #2340] Removing YAML/Syck — "NARUSE, Yui" <naruse@...> 2009/11/06

Jon wrote:

[#26574] Re: [Feature #2340] Removing YAML/Syck — Aaron Patterson <aaron@...> 2009/11/06

On Sat, Nov 07, 2009 at 12:59:25AM +0900, NARUSE, Yui wrote:

[#26635] [Feature #2348] RBTree Should be Added to the Standard Library — James Gray <redmine@...>

Feature #2348: RBTree Should be Added to the Standard Library

20 messages 2009/11/08
[#28842] [Feature #2348] RBTree Should be Added to the Standard Library — James Gray <redmine@...> 2010/03/21

Issue #2348 has been updated by James Gray.

[#26650] [Feature #2350] Unicode specific functionality on String in 1.9 — Manfred Stienstra <redmine@...>

Feature #2350: Unicode specific functionality on String in 1.9

12 messages 2009/11/09
[#28985] [Feature #2350](Rejected) Unicode specific functionality on String in 1.9 — Yusuke Endoh <redmine@...> 2010/03/25

Issue #2350 has been updated by Yusuke Endoh.

[#28993] Re: [Feature #2350](Rejected) Unicode specific functionality on String in 1.9 — Nikolai Weibull <now@...> 2010/03/25

On Thu, Mar 25, 2010 at 14:45, Yusuke Endoh <redmine@ruby-lang.org> wrote:

[#26704] Maintainer confirmation process done. — "Yugui (Yuki Sonoda)" <yugui@...>

I'm sorry for my closing the maintainer confirmation process so late.

13 messages 2009/11/12

[#26736] [Bug #2365] Matrix: poor handling of coercion errors [patch] — Marc-Andre Lafortune <redmine@...>

Bug #2365: Matrix: poor handling of coercion errors [patch]

12 messages 2009/11/14

[#26772] [Bug #2378] Regression in ParseDate.parsedate('nn-nn') — Vladimir Sizikov <redmine@...>

Bug #2378: Regression in ParseDate.parsedate('nn-nn')

10 messages 2009/11/16

[#26774] Ruby constant lookup — Yehuda Katz <wycats@...>

Over the past six months or so, I have been working with the new Ruby 1.9

22 messages 2009/11/16
[#26775] Re: Ruby constant lookup — Shugo Maeda <shugo@...> 2009/11/17

Hi,

[#26777] Re: Ruby constant lookup — Yehuda Katz <wycats@...> 2009/11/17

Shugo,

[#26778] Re: Ruby constant lookup — Shugo Maeda <shugo@...> 2009/11/17

Hi,

[#26869] Caching #to_s for immutables (and a possible future for constant-folding) — Kurt Stephens <ks@...>

I have a proof-of-concept patch to MRI that caches #to_s values for

16 messages 2009/11/23
[#26936] Re: Caching #to_s for immutables (and a possible future for constant-folding) — Roger Pack <rogerdpack@...> 2009/11/29

> =A0It reduces the number of #to_s Strings created during the MRI test sui=

[#26958] Re: Caching #to_s for immutables (and a possible future for constant-folding) [with patch] — Kurt Stephens <ks@...> 2009/11/30

The attached patch add caching of #to_s results to the main immutable

[#26960] Re: Caching #to_s for immutables (and a possible future for constant-folding) [with patch] — Roger Pack <rogerdpack@...> 2009/11/30

> Yes. =A0The MRI test suite runs at 45 sec with these changes and at 53 se=

[#26963] Re: Caching #to_s for immutables (and a possible future for constant-folding) [with patch] — Kurt Stephens <ks@...> 2009/11/30

I just ran rubyspec against it; ~ 5% time improvement.

[ruby-core:26958] Re: Caching #to_s for immutables (and a possible future for constant-folding) [with patch]

From: Kurt Stephens <ks@...>
Date: 2009-11-30 11:47:53 UTC
List: ruby-core #26958
   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

Attachments (1)

ruby-to_s_maybe_frozen.diff (19.6 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/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)
  *  ``<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};
@@ -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 <code>true</code> 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 <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);
+

In This Thread