mirror of
https://github.com/ruby/ruby.git
synced 2026-10-28 16:36:20 +08:00
bignum: emit two decimal digits per iteration in big2str_2bdigits
Extend the 2-digit lookup-table itoa optimisation from rb_fix2str to
the inner conversion loop used by Bignum#to_s. big2str_2bdigits has
two code paths — a leading-chunk path that emits variable-length
digits, and a recursive-chunk path that emits a fixed-width zero-
padded block — and both gain from the halved division count. The
classic per-digit loop is preserved for non-base-10 conversion.
Moves the ruby_decimal_digit_pairs table from a file-static in
numeric.c to bignum.c next to ruby_digitmap, and exposes it through
internal/bignum.h so both files share the same 200-byte .rodata
instance.
Benchmark (Apple M-series, best of 3 runs, measures bignum-only
speedup against the preceding fixnum commit):
case base patch delta
--------- ----- ----- -----
big_20dig 10^19+... 146 ns/op 124 ns/op -15%
big_40dig 10^39+... 174 ns/op 152 ns/op -13%
big_100dig 10^99+42 236 ns/op 213 ns/op -10%
big_500dig 10^499+7 1119 ns/op 1086 ns/op -3%
big_1000dig 10^999 3490 ns/op 3459 ns/op -1%
fix_19dig 2^62-1 76 ns/op 76 ns/op 0% (unchanged path)
Wins concentrate in the 20-100 digit range where big2str_2bdigits
is the dominant cost. Above ~500 digits the Karatsuba divmod
recursion dominates and the digit-emission saving shrinks to the
noise floor. The 20-100 range is what actual Ruby code exercises
(financial high-precision sums, nanosecond timestamps, large
counters); crypto-size (1000+ digit) bignums are rare in to_s paths.
Correctness: 100k random fixnum fuzz unchanged, 500 random bignum
fuzz up to 2^256 with cross-check against sprintf("%d"), bases
2/8/16/36 round-trip, plus edge cases (0, just-above-fixnum, ±2^100,
20-digit strings near the fixnum boundary). test/ruby/test_integer.rb
stays at 38 tests / 421628 assertions / 0 failures, test_bignum.rb
passes 74 / 607 / 0 failures, full make test-all reports 34694
tests / 0 new failures (same TestArgf#test_puts pre-existing flake
as master).
This commit is contained in:
1 parent
5bfb7e02a2
commit
c5df6de835
3 files changed
+81
-27
No files matched your search
@@ -64,6 +64,21 @@ static const bool debug_integer_pack = (
|
||||
|
||||
const char ruby_digitmap[] = "0123456789abcdefghijklmnopqrstuvwxyz";
|
||||
|
||||
/* Two-digit decimal lookup table. Offset 2*n holds the ASCII pair for
|
||||
* n in the range 0..99. Used by both rb_fix2str in numeric.c and
|
||||
* big2str_2bdigits below to emit two base-10 digits per iteration. */
|
||||
const char ruby_decimal_digit_pairs[201] =
|
||||
"00010203040506070809"
|
||||
"10111213141516171819"
|
||||
"20212223242526272829"
|
||||
"30313233343536373839"
|
||||
"40414243444546474849"
|
||||
"50515253545556575859"
|
||||
"60616263646566676869"
|
||||
"70717273747576777879"
|
||||
"80818283848586878889"
|
||||
"90919293949596979899";
|
||||
|
||||
#ifndef SIZEOF_BDIGIT_DBL
|
||||
# if SIZEOF_INT*2 <= SIZEOF_LONG_LONG
|
||||
# define SIZEOF_BDIGIT_DBL SIZEOF_LONG_LONG
|
||||
@@ -4814,11 +4829,34 @@ big2str_2bdigits(struct big2str_struct *b2s, BDIGIT *xds, size_t xn, size_t tail
|
||||
return;
|
||||
p = buf;
|
||||
j = sizeof(buf);
|
||||
do {
|
||||
BDIGIT_DBL idx = num % b2s->base;
|
||||
num /= b2s->base;
|
||||
p[--j] = ruby_digitmap[idx];
|
||||
} while (num);
|
||||
if (b2s->base == 10) {
|
||||
/* Emit two decimal digits per iteration from ruby_decimal_digit_pairs.
|
||||
* See the comment on the table in bignum.c near ruby_digitmap. */
|
||||
while (num >= 100) {
|
||||
BDIGIT_DBL idx = (num % 100) * 2;
|
||||
num /= 100;
|
||||
j -= 2;
|
||||
p[j] = ruby_decimal_digit_pairs[idx];
|
||||
p[j + 1] = ruby_decimal_digit_pairs[idx + 1];
|
||||
}
|
||||
if (num >= 10) {
|
||||
BDIGIT_DBL idx = num * 2;
|
||||
j -= 2;
|
||||
p[j] = ruby_decimal_digit_pairs[idx];
|
||||
p[j + 1] = ruby_decimal_digit_pairs[idx + 1];
|
||||
}
|
||||
else {
|
||||
/* num is 1..9 here (0 was handled above) */
|
||||
p[--j] = (char)('0' + num);
|
||||
}
|
||||
}
|
||||
else {
|
||||
do {
|
||||
BDIGIT_DBL idx = num % b2s->base;
|
||||
num /= b2s->base;
|
||||
p[--j] = ruby_digitmap[idx];
|
||||
} while (num);
|
||||
}
|
||||
len = sizeof(buf) - j;
|
||||
big2str_alloc(b2s, len + taillen);
|
||||
MEMCPY(b2s->ptr, buf + j, char, len);
|
||||
@@ -4826,11 +4864,39 @@ big2str_2bdigits(struct big2str_struct *b2s, BDIGIT *xds, size_t xn, size_t tail
|
||||
else {
|
||||
p = b2s->ptr;
|
||||
j = b2s->hbase2_numdigits;
|
||||
do {
|
||||
BDIGIT_DBL idx = num % b2s->base;
|
||||
num /= b2s->base;
|
||||
p[--j] = ruby_digitmap[idx];
|
||||
} while (j);
|
||||
if (b2s->base == 10) {
|
||||
/* Non-beginning chunks must emit EXACTLY hbase2_numdigits,
|
||||
* zero-padded on the left. Consume num in 2-digit groups,
|
||||
* handle the odd trailing digit, then memset remaining
|
||||
* positions with '0'. */
|
||||
while (num >= 100) {
|
||||
BDIGIT_DBL idx = (num % 100) * 2;
|
||||
num /= 100;
|
||||
j -= 2;
|
||||
p[j] = ruby_decimal_digit_pairs[idx];
|
||||
p[j + 1] = ruby_decimal_digit_pairs[idx + 1];
|
||||
}
|
||||
if (num >= 10) {
|
||||
BDIGIT_DBL idx = num * 2;
|
||||
j -= 2;
|
||||
p[j] = ruby_decimal_digit_pairs[idx];
|
||||
p[j + 1] = ruby_decimal_digit_pairs[idx + 1];
|
||||
}
|
||||
else if (num > 0) {
|
||||
p[--j] = (char)('0' + num);
|
||||
}
|
||||
if (j > 0) {
|
||||
memset(p, '0', j);
|
||||
j = 0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
do {
|
||||
BDIGIT_DBL idx = num % b2s->base;
|
||||
num /= b2s->base;
|
||||
p[--j] = ruby_digitmap[idx];
|
||||
} while (j);
|
||||
}
|
||||
len = b2s->hbase2_numdigits;
|
||||
}
|
||||
b2s->ptr += len;
|
||||
|
||||
@@ -115,6 +115,7 @@ struct RBignum {
|
||||
|
||||
/* bignum.c */
|
||||
extern const char ruby_digitmap[];
|
||||
extern const char ruby_decimal_digit_pairs[];
|
||||
double rb_big_fdiv_double(VALUE x, VALUE y);
|
||||
VALUE rb_big_uminus(VALUE x);
|
||||
VALUE rb_big_hash(VALUE);
|
||||
|
||||
@@ -4034,23 +4034,10 @@ rb_int_uminus(VALUE num)
|
||||
}
|
||||
}
|
||||
|
||||
/* Two-digit lookup table for base-10 conversion. Each pair of characters
|
||||
* encodes a two-digit decimal number from "00" through "99" at offset 2*n.
|
||||
* Halves the number of divisions in the conversion loop compared to the
|
||||
* classic digit-at-a-time approach. See e.g. Facebook's folly, LLVM
|
||||
* libc, Go's strconv — this is the standard itoa optimisation.
|
||||
*/
|
||||
static const char ruby_decimal_digit_pairs[201] =
|
||||
"00010203040506070809"
|
||||
"10111213141516171819"
|
||||
"20212223242526272829"
|
||||
"30313233343536373839"
|
||||
"40414243444546474849"
|
||||
"50515253545556575859"
|
||||
"60616263646566676869"
|
||||
"70717273747576777879"
|
||||
"80818283848586878889"
|
||||
"90919293949596979899";
|
||||
/* ruby_decimal_digit_pairs is defined in bignum.c and declared in
|
||||
* internal/bignum.h. See there for the rationale of the 2-digit
|
||||
* lookup-table itoa optimisation; both rb_fix2str here and big2str_2bdigits
|
||||
* in bignum.c consume it. */
|
||||
|
||||
VALUE
|
||||
rb_fix2str(VALUE x, int base)
|
||||
|
||||
Reference in new issue
Block a user