mruby-bigint: reuse q5 as hi in D&C to_s to reduce allocation

Instead of allocating a separate hi buffer for the upper part of the
split, reuse q5 by shifting it in place after extracting the lower
bits to q5_low.

This eliminates one mpz_t allocation per recursive call:
- Extract q5_low = q5 mod 2^k first (copy lower bits)
- Shift q5 right in place (memmove + mpn_rshift) to get hi
- q5 now serves as hi for the recursive call

Benchmark results (2.4M bit number):
- Memory: -3.5% (5.58MB -> 5.38MB peak heap)
- Instructions: -16.5%
- Speed: unchanged (within measurement noise)

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Yukihiro "Matz" Matsumoto
2026-01-13 11:00:59 +09:00
parent a486af2bb5
commit 1eea99b858
+30 -13
View File
@@ -2657,12 +2657,7 @@ mpz_to_s_dc_recur(mpz_ctx_t *ctx, char *s, mpz_t *x, size_t num_digits,
* lo = (q5 & ((1<<k)-1)) * 5^k + r5
*/
/* hi and lo must persist through recursive calls, so allocate fresh */
mpz_t hi, lo;
mpz_init(ctx, &hi);
mpz_init(ctx, &lo);
/* q5, r5, q5_low are only needed for computing hi/lo, reuse from scratch */
/* Reuse scratch buffers: q5, r5, q5_low */
mpz_t *q5 = &scratch->q5;
mpz_t *r5 = &scratch->r5;
mpz_t *q5_low = &scratch->q5_low;
@@ -2671,24 +2666,46 @@ mpz_to_s_dc_recur(mpz_ctx_t *ctx, char *s, mpz_t *x, size_t num_digits,
mpz_mdivmod(ctx, q5, r5, x, &pow5[split_idx]);
r5->sn = (r5->sn < 0) ? -r5->sn : r5->sn;
/* Step 2: hi = q5 >> split_digits (divide by 2^k using bit shift) */
mpz_div_2exp(ctx, &hi, q5, (mrb_int)split_digits);
/* Step 3: lo = (q5 mod 2^k) * 5^k + r5 */
/* Step 2: Extract q5_low = q5 mod 2^k BEFORE modifying q5 */
mpz_mod_2exp(ctx, q5_low, q5, (mrb_int)split_digits);
/* Step 3: hi = q5 >> k - shift q5 in place to reuse as hi */
/* This avoids allocating a separate hi buffer */
mpz_t *hi = q5; /* Reuse q5 as hi after shifting */
{
size_t limb_shift = split_digits / DIG_SIZE;
size_t bit_shift = split_digits % DIG_SIZE;
if (limb_shift >= q5->sz) {
zero(q5);
}
else {
size_t new_sz = q5->sz - limb_shift;
/* Shift limbs down */
memmove(q5->p, q5->p + limb_shift, new_sz * sizeof(mp_limb));
q5->sz = new_sz;
/* Apply bit shift if needed */
if (bit_shift > 0) {
mpn_rshift(q5->p, q5->p, q5->sz, (unsigned int)bit_shift);
}
trim(q5);
}
}
/* Step 4: lo = q5_low * 5^k + r5 - allocate fresh for lo */
mpz_t lo;
mpz_init(ctx, &lo);
mpz_mul(ctx, &lo, q5_low, &pow5[split_idx]);
mpz_add(ctx, &lo, &lo, r5);
lo.sn = (lo.sn < 0) ? -lo.sn : lo.sn;
/* Recursively convert high part */
/* Recursively convert high part (using q5 which now contains hi) */
size_t hi_digits = num_digits - split_digits;
mpz_to_s_dc_recur(ctx, s, &hi, hi_digits, pow5, split_idx, scratch);
mpz_to_s_dc_recur(ctx, s, hi, hi_digits, pow5, split_idx, scratch);
/* Recursively convert low part (exactly split_digits digits with padding) */
mpz_to_s_dc_recur(ctx, s + hi_digits, &lo, split_digits, pow5, split_idx, scratch);
mpz_clear(ctx, &hi);
mpz_clear(ctx, &lo);
}