MIRROR: javascript for ๐Ÿœ's, a tiny runtime with big ambitions
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

optimize bigint toString performance with radix conversion using limb-based arithmetic

+80 -40
+50 -40
src/ant.c
··· 2418 2418 int radix = 10; 2419 2419 if (nargs >= 1 && vtype(args[0]) == T_NUM) { 2420 2420 radix = (int)tod(args[0]); 2421 - if (radix < 2 || radix > 36) { 2422 - return js_mkerr(js, "radix must be between 2 and 36"); 2423 - } 2421 + if (radix < 2 || radix > 36) return js_mkerr(js, "radix must be between 2 and 36"); 2424 2422 } 2425 2423 2426 2424 bool neg = bigint_IsNegative(js, val); 2427 - size_t dlen; 2428 - const char *digits = bigint_digits(js, val, &dlen); 2425 + size_t dlen; const char *digits = bigint_digits(js, val, &dlen); 2429 2426 2430 2427 if (radix == 10) { 2431 2428 size_t buflen = dlen + 2; 2432 2429 char *buf = (char *)ant_calloc(buflen); 2433 2430 if (!buf) return js_mkerr(js, "oom"); 2434 - size_t n = 0; 2435 - if (neg) buf[n++] = '-'; 2436 - memcpy(buf + n, digits, dlen); 2437 - n += dlen; 2438 - jsval_t ret = js_mkstr(js, buf, n); 2439 - free(buf); 2431 + size_t n = 0; if (neg) buf[n++] = '-'; 2432 + memcpy(buf + n, digits, dlen); n += dlen; 2433 + jsval_t ret = js_mkstr(js, buf, n); free(buf); 2440 2434 return ret; 2441 2435 } 2442 2436 2437 + const uint32_t base = 1000000000U; 2443 2438 size_t result_cap = dlen * 4 + 16; 2439 + 2444 2440 char *result = (char *)ant_calloc(result_cap); 2445 2441 if (!result) return js_mkerr(js, "oom"); 2442 + 2446 2443 size_t rpos = result_cap - 1; 2447 2444 result[rpos] = '\0'; 2448 - 2449 - char *num = (char *)ant_calloc(dlen + 1); 2450 - if (!num) { free(result); return js_mkerr(js, "oom"); } 2451 - memcpy(num, digits, dlen); 2452 - num[dlen] = '\0'; 2453 - size_t numlen = dlen; 2454 - 2455 - while (numlen > 0 && !(numlen == 1 && num[0] == '0')) { 2456 - int remainder = 0; 2457 - for (size_t i = 0; i < numlen; i++) { 2458 - int d = remainder * 10 + (num[i] - '0'); 2459 - num[i] = (char)('0' + (d / radix)); 2460 - remainder = d % radix; 2445 + 2446 + size_t limb_cap = (dlen + 8) / 9 + 1; 2447 + uint32_t *limbs = (uint32_t *)ant_calloc(limb_cap * sizeof(uint32_t)); 2448 + if (!limbs) { free(result); return js_mkerr(js, "oom"); } 2449 + size_t limb_len = 1; 2450 + 2451 + for (size_t i = 0; i < dlen; i++) { 2452 + uint64_t carry = (uint64_t)(digits[i] - '0'); 2453 + for (size_t j = 0; j < limb_len; j++) { 2454 + uint64_t cur = (uint64_t)limbs[j] * 10 + carry; 2455 + limbs[j] = (uint32_t)(cur % base); 2456 + carry = cur / base; 2461 2457 } 2462 - size_t start = 0; 2463 - while (start < numlen - 1 && num[start] == '0') start++; 2464 - memmove(num, num + start, numlen - start + 1); 2465 - numlen -= start; 2466 - if (numlen == 1 && num[0] == '0') numlen = 0; 2458 + if (carry != 0) { 2459 + if (limb_len == limb_cap) { 2460 + size_t new_cap = limb_cap * 2; 2461 + uint32_t *new_limbs = (uint32_t *)ant_realloc(limbs, new_cap * sizeof(uint32_t)); 2462 + if (!new_limbs) { free(limbs); free(result); return js_mkerr(js, "oom"); } 2463 + limbs = new_limbs; 2464 + limb_cap = new_cap; 2465 + } 2466 + limbs[limb_len++] = (uint32_t)carry; 2467 + } 2468 + } 2469 + 2470 + static const char digit_map[] = "0123456789abcdefghijklmnopqrstuvwxyz"; 2471 + while (limb_len > 0 && !(limb_len == 1 && limbs[0] == 0)) { 2472 + uint64_t remainder = 0; 2473 + for (size_t i = limb_len; i-- > 0;) { 2474 + uint64_t cur = (uint64_t)limbs[i] + remainder * base; 2475 + limbs[i] = (uint32_t)(cur / (uint64_t)radix); 2476 + remainder = cur % (uint64_t)radix; 2477 + } 2478 + 2479 + while (limb_len > 0 && limbs[limb_len - 1] == 0) limb_len--; 2467 2480 if (rpos == 0) { 2468 2481 size_t new_cap = result_cap * 2; 2469 2482 char *new_result = (char *)ant_calloc(new_cap); 2470 - if (!new_result) { free(num); free(result); return js_mkerr(js, "oom"); } 2483 + if (!new_result) { free(limbs); free(result); return js_mkerr(js, "oom"); } 2484 + 2471 2485 size_t used = result_cap - rpos; 2472 2486 memcpy(new_result + new_cap - used, result + rpos, used); 2473 2487 free(result); 2488 + 2474 2489 result = new_result; 2475 2490 rpos = new_cap - used; 2476 2491 result_cap = new_cap; 2477 2492 } 2478 - rpos--; 2479 - result[rpos] = (char)(remainder < 10 ? '0' + remainder : 'a' + (remainder - 10)); 2480 - } 2481 - 2482 - free(num); 2483 - 2484 - if (rpos == result_cap - 1) { 2485 - result[--rpos] = '0'; 2493 + result[--rpos] = digit_map[remainder]; 2486 2494 } 2495 + 2496 + free(limbs); 2487 2497 2498 + if (rpos == result_cap - 1) result[--rpos] = '0'; 2488 2499 if (neg) result[--rpos] = '-'; 2489 2500 2490 2501 jsval_t ret = js_mkstr(js, result + rpos, result_cap - 1 - rpos); 2491 - free(result); 2492 - return ret; 2502 + free(result); return ret; 2493 2503 } 2494 2504 2495 2505 static jsval_t mkobj(struct js *js, jsoff_t parent) {
+30
tests/bench_bigint_toString.js
··· 1 + const now = () => Date.now(); 2 + 3 + function bench(name, fn) { 4 + const start = now(); 5 + fn(); 6 + const end = now(); 7 + console.log(`${name}: ${end - start}ms`); 8 + } 9 + 10 + const digits = '9'.repeat(2000); 11 + const big = BigInt(digits); 12 + const big2 = big * big + 123456789n; 13 + 14 + bench('toString(10) x200', () => { 15 + for (let i = 0; i < 200; i++) { 16 + big2.toString(10); 17 + } 18 + }); 19 + 20 + bench('toString(16) x200', () => { 21 + for (let i = 0; i < 200; i++) { 22 + big2.toString(16); 23 + } 24 + }); 25 + 26 + bench('toString(2) x40', () => { 27 + for (let i = 0; i < 40; i++) { 28 + big2.toString(2); 29 + } 30 + });