Skip to content

Reduce allocations in FIXDecimal.Write - #787

Open
Denx00 wants to merge 1 commit into
quickfixgo:mainfrom
Denx00:perf/fixdecimal-write-fast-path
Open

Denx00 wants to merge 1 commit into
quickfixgo:mainfrom
Denx00:perf/fixdecimal-write-fast-path

Conversation

@Denx00

@Denx00 Denx00 commented Oct 25, 2026

Copy link
Copy Markdown

Closes #786.

FIXDecimal.Write formats via decimal.StringFixed, which rounds through big.Int and builds intermediate strings. This adds a fast path that formats directly from the int64 coefficient, rounding half away from zero as StringFixed does. It falls back to the existing path for coefficients beyond int64, negative scale, and scale >= 20, so output is unchanged.

Benchmark (BenchmarkFIXDecimalWrite, benchstat, 10 runs):

before after
time/op 155 ns 20.5 ns -86.8%
B/op 208 24 -88.5%
allocs/op 11 2 -81.8%

Tests: TestFIXDecimalWriteMatchesStringFixed compares against StringFixed over edge coefficients (including MinInt64), exponents -25..25 and several scales, including rounding ties and negative values that round to zero. go test -race and go vet pass. log/mongo fails on main too, since it needs a MongoDB instance.

Write went through decimal.StringFixed, which rounds via big.Int and
builds intermediate strings: 11 allocs/op for a typical price. Format
directly from the int64 coefficient when it fits, rounding half away
from zero as StringFixed does, and fall back otherwise (large
coefficients, negative or >19 scale).

BenchmarkFIXDecimalWrite: 203ns, 208B, 11 allocs -> 21ns, 24B, 2 allocs.
A differential test checks the result against StringFixed over edge
coefficients, exponents and scales.

@by-ae by-ae left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I checked this out locally (go1.27.3, linux/amd64) and tried to break the fast path against StringFixed:

  • Wrote a throwaway test that compares FIXDecimal{decimal.New(c, e), s}.Write() with StringFixed(s) over 5M random cases: small coefficients, full-range int64 (including MinInt64 and values near MaxInt64), exponents -25..25 and scales -2..21 so the fallback branches get hit too. 0 mismatches.
  • Also tried a few values from decimal.NewFromString (-0.004, 0.005, -0.005, 1.50, 99999999999999999999.5, -1e-30, 123456789.987654321) at scales 0, 2 and 8, plus the zero value FIXDecimal{} (nil coefficient). All match, and negative values that round to zero come out as 0.00 with no sign, same as before.
  • go test . and go vet . pass on the branch.

Benchmark on my machine, same BenchmarkFIXDecimalWrite:

main:   379.9 ns/op   208 B/op   11 allocs/op
PR:      43.4 ns/op    24 B/op    2 allocs/op

The r >= div-r half-up check is a nice touch since div can be above 1<<63 at scale 19. Looks good to me from the testing side.

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.

Reduce allocations in FIXDecimal.Write

2 participants