ruby-changes:64847
From: Kenta <ko1@a...>
Date: Wed, 13 Jan 2021 08:14:41 +0900 (JST)
Subject: [ruby-changes:64847] 4ba3a4491e (master): [ruby/bigdecimal] Optimize rb_float_convert_to_BigDecimal by using dtoa
https://git.ruby-lang.org/ruby.git/commit/?id=4ba3a4491e From 4ba3a4491e15ed50f26c8f7a602fb4ef5452085a Mon Sep 17 00:00:00 2001 From: Kenta Murata <mrkn@m...> Date: Tue, 12 Jan 2021 22:58:17 +0900 Subject: [ruby/bigdecimal] Optimize rb_float_convert_to_BigDecimal by using dtoa This improve the conversion speed several times faster than before. ``` RUBYLIB= BUNDLER_ORIG_RUBYLIB= /home/mrkn/.rbenv/versions/3.0.0/bin/ruby -v -S benchmark-driver /home/mrkn/src/github.com/ruby/bigdecimal/benchmark/from_float.yml ruby 3.0.0p0 (2020-12-25 revision 95aff21468) [x86_64-linux] Calculating ------------------------------------- bigdecimal 3.0.0 master flt_e0 156.400k 783.356k i/s - 100.000k times in 0.639388s 0.127656s flt_ep10 158.640k 777.978k i/s - 100.000k times in 0.630359s 0.128538s flt_ep100 101.676k 504.259k i/s - 100.000k times in 0.983512s 0.198311s flt_em10 103.439k 726.339k i/s - 100.000k times in 0.966751s 0.137677s flt_em100 79.675k 651.446k i/s - 100.000k times in 1.255095s 0.153505s Comparison: flt_e0 master: 783355.6 i/s bigdecimal 3.0.0: 156399.5 i/s - 5.01x slower flt_ep10 master: 777977.6 i/s bigdecimal 3.0.0: 158639.7 i/s - 4.90x slower flt_ep100 master: 504259.4 i/s bigdecimal 3.0.0: 101676.5 i/s - 4.96x slower flt_em10 master: 726338.6 i/s bigdecimal 3.0.0: 103439.2 i/s - 7.02x slower flt_em100 master: 651446.3 i/s bigdecimal 3.0.0: 79675.3 i/s - 8.18x slower ``` https://github.com/ruby/bigdecimal/commit/5bdaedd530 https://github.com/ruby/bigdecimal/commit/9bfff57f90 https://github.com/ruby/bigdecimal/commit/d071a0abbb diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 4997487..2d3f09d 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -74,7 +74,7 @@ static ID id_half; https://github.com/ruby/ruby/blob/trunk/ext/bigdecimal/bigdecimal.c#L74 #define BASE1 (BASE/10) #ifndef DBLE_FIG -#define DBLE_FIG rmpd_double_figures() /* figure of double */ +#define DBLE_FIG RMPD_DOUBLE_FIGURES /* figure of double */ #endif #define LOG10_2 0.3010299956639812 @@ -205,6 +205,7 @@ cannot_be_coerced_into_BigDecimal(VALUE exc_class, VALUE v) https://github.com/ruby/ruby/blob/trunk/ext/bigdecimal/bigdecimal.c#L205 } static inline VALUE BigDecimal_div2(VALUE, VALUE, VALUE); +static VALUE rb_inum_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception); static VALUE rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception); static VALUE rb_rational_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception); static VALUE rb_cstr_convert_to_BigDecimal(const char *c_str, size_t digs, int raise_exception); @@ -2824,8 +2825,118 @@ rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) https://github.com/ruby/ruby/blob/trunk/ext/bigdecimal/bigdecimal.c#L2825 rb_raise(rb_eArgError, "precision too large."); } - val = rb_funcall(val, id_to_r, 0); - return rb_rational_convert_to_BigDecimal(val, digs, raise_exception); + /* Use the same logic in flo_to_s to convert a float to a decimal string */ + char buf[DBLE_FIG + BASE_FIG + 2 + 1]; + int decpt, negative_p; + char *e; + char *p = BigDecimal_dtoa(d, 2, digs, &decpt, &negative_p, &e); + int len10 = (int)(e - p); + if (len10 >= (int)sizeof(buf)) + len10 = (int)sizeof(buf) - 1; + memcpy(buf, p, len10); + xfree(p); + + VALUE inum; + size_t RB_UNUSED_VAR(prec) = 0; + size_t exp = 0; + if (decpt > 0) { + if (decpt < len10) { + /* + * len10 |---------------| + * : |-------| frac_len10 = len10 - decpt + * decpt |-------| |--| ntz10 = BASE_FIG - frac_len10 % BASE_FIG + * : : : + * 00 dd dddd.dddd dd 00 + * prec |-----.----.----.-----| prec = exp + roomof(frac_len, BASE_FIG) + * exp |-----.----| exp = roomof(decpt, BASE_FIG) + */ + const size_t frac_len10 = len10 - decpt; + const size_t ntz10 = BASE_FIG - frac_len10 % BASE_FIG; + memset(buf + len10, '0', ntz10); + buf[len10 + ntz10] = '\0'; + inum = rb_cstr_to_inum(buf, 10, false); + + exp = roomof(decpt, BASE_FIG); + prec = exp + roomof(frac_len10, BASE_FIG); + } + else { + /* + * decpt |-----------------------| + * len10 |----------| : + * : |------------| exp10 + * : : : + * 00 dd dddd dd 00 0000 0000.0 + * : : : : + * : |--| ntz10 = exp10 % BASE_FIG + * prec |-----.----.-----| : + * : |----.----| exp10 / BASE_FIG + * exp |-----.----.-----.----.----| + */ + const size_t exp10 = decpt - len10; + const size_t ntz10 = exp10 % BASE_FIG; + + memset(buf + len10, '0', ntz10); + buf[len10 + ntz10] = '\0'; + inum = rb_cstr_to_inum(buf, 10, false); + + prec = roomof(len10 + ntz10, BASE_FIG); + exp = prec + exp10 / BASE_FIG; + } + } + else if (decpt == 0) { + /* + * len10 |------------| + * : : + * 0.dddd dddd dd 00 + * : : : + * : |--| ntz10 = prec * BASE_FIG - len10 + * prec |----.----.-----| roomof(len10, BASE_FIG) + */ + prec = roomof(len10, BASE_FIG); + const size_t ntz10 = prec * BASE_FIG - len10; + + memset(buf + len10, '0', ntz10); + buf[len10 + ntz10] = '\0'; + inum = rb_cstr_to_inum(buf, 10, false); + } + else { + /* + * len10 |---------------| + * : : + * decpt |-------| |--| ntz10 = prec * BASE_FIG - nlz10 - len10 + * : : : + * 0.0000 00 dd dddd dddd dd 00 + * : : : + * nlz10 |--| : decpt % BASE_FIG + * prec |-----.----.----.-----| roomof(decpt + len10, BASE_FIG) - exp + * exp |----| decpt / BASE_FIG + */ + decpt = -decpt; + + const size_t nlz10 = decpt % BASE_FIG; + exp = decpt / BASE_FIG; + prec = roomof(decpt + len10, BASE_FIG) - exp; + const size_t ntz10 = prec * BASE_FIG - nlz10 - len10; + + if (nlz10 > 0) { + memmove(buf + nlz10, buf, len10); + memset(buf, '0', nlz10); + } + memset(buf + nlz10 + len10, '0', ntz10); + buf[nlz10 + len10 + ntz10] = '\0'; + inum = rb_cstr_to_inum(buf, 10, false); + + exp = -exp; + } + + VALUE bd = rb_inum_convert_to_BigDecimal(inum, SIZE_MAX, raise_exception); + Real *vp; + TypedData_Get_Struct(bd, Real, &BigDecimal_data_type, vp); + assert(vp->Prec == prec); + vp->exponent = exp; + + if (negative_p) VpSetSign(vp, -1); + return bd; } static VALUE diff --git a/ext/bigdecimal/bigdecimal.gemspec b/ext/bigdecimal/bigdecimal.gemspec index 8a2f5b6..79009aa 100644 --- a/ext/bigdecimal/bigdecimal.gemspec +++ b/ext/bigdecimal/bigdecimal.gemspec @@ -21,7 +21,9 @@ Gem::Specification.new do |s| https://github.com/ruby/ruby/blob/trunk/ext/bigdecimal/bigdecimal.gemspec#L21 ext/bigdecimal/bigdecimal.h ext/bigdecimal/bits.h ext/bigdecimal/feature.h + ext/bigdecimal/missing.c ext/bigdecimal/missing.h + ext/bigdecimal/missing/dtoa.c ext/bigdecimal/static_assert.h lib/bigdecimal.rb lib/bigdecimal/jacobian.rb @@ -40,5 +42,5 @@ Gem::Specification.new do |s| https://github.com/ruby/ruby/blob/trunk/ext/bigdecimal/bigdecimal.gemspec#L42 s.add_development_dependency "rake", ">= 12.3.3" s.add_development_dependency "rake-compiler", ">= 0.9" s.add_development_dependency "minitest", "< 5.0.0" - s.add_development_dependency "pry" + s.add_development_dependency "irb" end diff --git a/ext/bigdecimal/bigdecimal.h b/ext/bigdecimal/bigdecimal.h index 6b6ac21..5f343db 100644 --- a/ext/bigdecimal/bigdecimal.h +++ b/ext/bigdecimal/bigdecimal.h @@ -77,6 +77,7 @@ extern VALUE rb_cBigDecimal; https://github.com/ruby/ruby/blob/trunk/ext/bigdecimal/bigdecimal.h#L77 # define RMPD_BASE ((DECDIG)100U) #endif +#define RMPD_DOUBLE_FIGURES (1+DBL_DIG) /* * NaN & Infinity @@ -175,7 +176,7 @@ rmpd_base_value(void) { return RMPD_BASE; } https://github.com/ruby/ruby/blob/trunk/ext/bigdecimal/bigdecimal.h#L176 static inline size_t rmpd_component_figures(void) { return RMPD_COMPONENT_FIGURES; } static inline size_t -rmpd_double_figures(void) { return 1+DBL_DIG; } +rmpd_double_figures(void) { return RMPD_DOUBLE_FIGURES; } #define VpBaseFig() rmpd_component_figures() #define VpDblFig() rmpd_double_figures() diff --git a/ext/bigdecimal/extconf.rb b/ext/bigdecimal/extconf.rb index d5140e8..c92aacb 100644 --- a/ext/bigdecimal/extconf.rb +++ b/ext/bigdecimal/extconf.rb @@ -66,6 +66,7 @@ have_func("llabs", "stdlib.h") https://github.com/ruby/ruby/blob/trunk/ext/bigdecimal/extconf.rb#L66 have_func("finite", "math.h") have_func("isfinite", "math.h") +have_header("ruby/atomic.h") have_header("ruby/internal/has/builtin.h") have_header("ruby/internal/static_assert.h") diff --git a/ext/bigdecimal/missing.c b/ext/bigdecimal/missing.c new file mode (... truncated) -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/