Skip to content

Conversation

@dominiquelefevre
Copy link

What type of PR is this? (check all applicable)

  • [ X ] Refactor
  • Feature
  • Bug Fix
  • [ X ] Optimization
  • Documentation Update
  • Go Version Update
  • Dependency Update

Description

Decoder.setDefaults() happens to use a lot of stack space for its local variables.

Large stack usage by setDefaults() is a performance problem because the go runtime assumes that goroutine stacks stay small. A big frame for local variables makes it very probable that the go runtime will need to grow the stack upon a call to setDefaults(). In a service where I have observed this behaviour, Decoder.Decode() spends 3x more time in runtime.newstack() than in the rest of Decoder.Decode().

This patch set decreases the frame for setDefaults()'s local variables from 2408 bytes to 312 (the figures are for go 1.25 on amd64).

NOTE: the stack frame size of 2408 bytes guarantees that the goroutine stack will grow if setDefaults() is called soon after a goroutine starts. Normally, there are some calls on the stack before setDefaults(), like the http server, the muxer, possibly otelhttp, etc. Because of those setDefaults() is only likely to grow the stack, not guaranteed to do so.

Aside from reducing the stack usage, this patch series improves the speed of go test -count=10000 by some 4%.

See individual commit messages for the rationale of each change.

Added/updated tests?

  • Yes
  • [ X ] No, and this is why: the patch adds no new features, and fixes no known bugs
  • I need help with writing tests

Run verifications and test

  • [ X ] make verify is passing: make verify fails in main due to gosec complaining about possible truncating conversions in convertPointer(); this patch series adds no new failures.
  • [ X ] make test is passing

Less nesting an a switch instead of if ... else if ... else if are
easier to read.
…ts().

Using MultiError.merge() forces the caller to allocate an instance of
MultiError just to add one error. Let us have a method add() that does
this a temporary map. Also, add constants for errors that are copied-
and-pasted.

This may seem a minor performance improvement to skip an allocation,
but in fact it is not. The go compiler detects that a temporary
MultiError instance does not escape to the heap, and allocates it
on the stack. Go 1.25 on amd64 would use 2408 bytes for the local
variables of setDefaults(). This change reduces the local variables
frame to 840 bytes.

Large stack usage by setDefaults() is in fact very costly. Goroutines
start with a small stack. A big frame for local variables makes it
very probable that the go runtime will need to grow the stack upon
a call to setDefaults(). In a service where I have observed this
behaviour, Decoder.Decode() spends 3x more time in runtime.newstack()
than in the rest of Decoder.Decode().
Inlining these calls gains nothing performance-wise, but uses a lot of
stack. With these two calls de-inlined, setDefaults() uses 528 bytes
for the local variables instead of 840.
Now setDefaults() needs 312 bytes for local variables instead of 528.
@dominiquelefevre
Copy link
Author

Is anyone out here? @lll-lll-lll-lll?

@zak905
Copy link
Contributor

zak905 commented Oct 30, 2025

Hi @dominiquelefevre, unfortunately the project have been abandoned by its maintainers. I talked to one of them few months ago, who said he's going to do something to move things, but until today, nothing can't be merged.

With this being said, I am the person behind adding the default functionality to the project #183 , so I can take a look at this PR, and give you feedback, if you'd like.

@dominiquelefevre
Copy link
Author

@zak905, thank you for chiming in. Well, in this case I'd rather migrate my project off gorilla/*.

@dominiquelefevre dominiquelefevre closed this by deleting the head repository Oct 31, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants