Skip to content

reduce init time cost by 76% without lazy initialization - #159

Merged
rafiss merged 3 commits into
cockroachdb:masterfrom
mvdan:init-cost
Oct 20, 2026
Merged

rafiss merged 3 commits into
cockroachdb:masterfrom
mvdan:init-cost

Conversation

@mvdan

@mvdan mvdan commented Oct 12, 2026

Copy link
Copy Markdown
Contributor

(see commit messages - please do not squash)

mvdan added 2 commits October 12, 2026 22:17
makeConstWithPrecision parsed and rounded the ~2000-digit ln(10) and
1/ln(10) constants at init time via Decimal.SetString and Context.Round.
Both need powers of ten far beyond pow10LookupTable to count digits and
to round, which made them the bulk of the package's init cost.

Since the constants are positive decimal strings, round their digits
directly as RoundHalfUp would and build each Decimal from its digits.
The test checks every value against Decimal.SetString and Context.Round.

The coefficients stay in heap-allocated big.Int values, as Context.Round
left them; storing the low-precision ones inline made BenchmarkLn about
4% slower at P10.

As measured by https://github.com/mvdan/benchinit:

                              │     old     │                 mid                 │
                              │   sec/op    │   sec/op     vs base                │
    GithubComCockroachdbApdV3   694.9µ ± 1%   237.3µ ± 0%  -65.85% (p=0.000 n=10)

                              │     old      │                 mid                  │
                              │     B/op     │     B/op      vs base                │
    GithubComCockroachdbApdV3   554.5Ki ± 0%   138.6Ki ± 0%  -75.01% (p=0.000 n=10)

                              │     old     │                 mid                 │
                              │  allocs/op  │  allocs/op   vs base                │
    GithubComCockroachdbApdV3   2.426k ± 0%   1.457k ± 0%  -39.94% (p=0.000 n=10)

Ln and Log10 are the only users of these constants, and their speed is
unaffected. BenchmarkLog10 mirrors BenchmarkLn; both were run with
GODEBUG=randautoseed=0 so that both sides use the same random inputs:

                          │     old     │                new                │
                          │   sec/op    │   sec/op     vs base              │
    Ln/P10/S10/D10-24       12.35µ ± 2%   12.44µ ± 1%       ~ (p=0.169 n=8)
    Log10/P10/S10/D10-24    11.15µ ± 1%   11.20µ ± 9%       ~ (p=0.442 n=8)
    Ln/P100/S10/D10-24      472.2µ ± 2%   473.6µ ± 1%       ~ (p=0.328 n=8)
    Log10/P100/S10/D10-24   462.9µ ± 3%   463.5µ ± 2%       ~ (p=0.798 n=8)

                          │     old     │                 new                 │
                          │  allocs/op  │  allocs/op   vs base                │
    Ln/P10/S10/D10-24        9.000 ± 0%    9.000 ± 0%       ~ (p=1.000 n=8)
    Log10/P10/S10/D10-24     9.000 ± 0%    9.000 ± 0%       ~ (p=1.000 n=8)
    Ln/P100/S10/D10-24      5.191k ± 0%   5.191k ± 0%       ~ (p=1.000 n=8)
    Log10/P100/S10/D10-24   5.036k ± 0%   5.036k ± 0%       ~ (p=1.000 n=8)
pow10LookupTable computed each power of ten with a separate Exp call,
and digitsLookupTable computed each entry via String and Exp too.
Instead, build each power of ten by multiplying the previous one by ten,
and derive each digits entry from pow10LookupTable.

As measured by https://github.com/mvdan/benchinit:

                              │     mid     │                 new                 │
                              │   sec/op    │   sec/op     vs base                │
    GithubComCockroachdbApdV3   237.3µ ± 0%   165.4µ ± 2%  -30.27% (p=0.000 n=10)

                              │     mid      │                 new                  │
                              │     B/op     │     B/op      vs base                │
    GithubComCockroachdbApdV3   138.6Ki ± 0%   107.9Ki ± 0%  -22.10% (p=0.000 n=10)

                              │     mid     │                new                 │
                              │  allocs/op  │ allocs/op   vs base                │
    GithubComCockroachdbApdV3   1457.0 ± 0%   511.0 ± 0%  -64.93% (p=0.000 n=10)

The tables hold the same values, so NumDigits is unaffected. It is in
fact slightly faster for small numbers, as Exp always left its results
in heap-allocated big.Int values, whereas the borders which fit inline
are now stored that way:

                       │     old      │                 new                 │
                       │    sec/op    │   sec/op     vs base                │
    NumDigitsLookup-24   8.612µ ± 17%   7.542µ ± 8%  -12.42% (p=0.014 n=12)
    NumDigitsFull-24     2.709m ±  3%   2.765m ± 4%        ~ (p=0.478 n=12)
@mvdan

mvdan commented Oct 12, 2026

Copy link
Copy Markdown
Contributor Author

This is a second version of #137, with most of the benefit but no slowdown :) cc @yuzefovich @michae2

Document the init ordering between the two lookup tables, the input
format makeConstWithPrecision now accepts, and the preconditions of
roundDigitsHalfUp. Fix the test message that named PowerOfTenDec
instead of tableExp10.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

@rafiss rafiss left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for your contribution! i've adjusted a few comments for clarity and will merge this.

@rafiss
rafiss merged commit d451b15 into cockroachdb:master Oct 20, 2026
18 checks passed
@mvdan

mvdan commented Oct 20, 2026

Copy link
Copy Markdown
Contributor Author

Thanks very much!

cueckoo pushed a commit to cue-lang/cue that referenced this pull request Oct 21, 2026
Its master builds the rounding constants from their digits directly and
the digit lookup tables from each other at init, rather than parsing
each constant with Decimal.SetString and rounding it with Context.Round
at every precision, which computes powers of ten far beyond the lookup
table; see cockroachdb/apd#159.

cmd/cue links the package, so every invocation pays its init cost;
the total init cost of cmd/cue is reduced as measured by benchinit
plus benchstat:

                       │     old     │                 new                 │
                       │   sec/op    │   sec/op     vs base                │
    CuelangOrgGoCmdCue   1.683m ± 2%   1.143m ± 1%  -32.13% (p=0.000 n=12)

                       │     old      │                 new                  │
                       │     B/op     │     B/op      vs base                │
    CuelangOrgGoCmdCue   977.5Ki ± 0%   531.9Ki ± 0%  -45.59% (p=0.000 n=12)

                       │     old     │                 new                 │
                       │  allocs/op  │  allocs/op   vs base                │
    CuelangOrgGoCmdCue   5.585k ± 0%   3.670k ± 0%  -34.29% (p=0.000 n=12)

Signed-off-by: Daniel Martí <mvdan@mvdan.cc>
Change-Id: Iba39e74df4e09a517d5aa12715edc9e6a73c0d04
GitOrigin-RevId: ceb2e988c72a0b690c751966dc8b5eb935cc3d82
@mvdan
mvdan deleted the init-cost branch October 21, 2026 21:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants