Repository navigation
perf(query): skip per-node memclr in newNode (Allocate vs AllocateAligned) #9711
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -120,7 +120,19 @@ type node struct { | |
| child *node | ||
| } | ||
|
|
||
| var nodeSize = int(unsafe.Sizeof(node{})) | ||
| var ( | ||
| nodeSize = int(unsafe.Sizeof(node{})) | ||
| nodeAlign = int(unsafe.Alignof(node{})) | ||
| ) | ||
|
|
||
| // init enforces that nodeSize is a multiple of nodeAlign so that consecutive | ||
| // bump-allocations from a base-aligned buffer all return aligned slices. This | ||
| // is what makes the AllocateAligned-free fast path in newNode safe. | ||
| func init() { | ||
| if nodeSize%nodeAlign != 0 { | ||
| panic("query: nodeSize must be a multiple of nodeAlign") | ||
| } | ||
| } | ||
|
|
||
| func newEncoder() *encoder { | ||
| idSlice := make([]string, 1) | ||
|
|
@@ -300,8 +312,18 @@ type fastJsonNode *node | |
|
|
||
| // newNode returns a fastJsonNode with its attr set to attr, | ||
| // and all other meta set to their default value. | ||
| // | ||
| // We use Allocate (no zeroing) rather than AllocateAligned because: | ||
| // 1. The underlying allocator buffers come from z.Calloc and are zero-initialized. | ||
| // 2. The allocator is freshly created per encoder (not pooled) and never reuses | ||
| // space within its lifetime (Allocate is a strict-forward bump allocator). | ||
| // 3. nodeSize (24 on 64-bit) is a multiple of nodeAlign — see init() — so | ||
| // consecutive Allocate(nodeSize) calls preserve alignment. | ||
| // | ||
| // Skipping the per-node memclr eliminates the runtime.memclrNoHeapPointers | ||
| // hotspot (66% of CPU in BenchmarkFastJsonNode2Chilren). | ||
| func (enc *encoder) newNode(attr uint16) fastJsonNode { | ||
| b := enc.alloc.AllocateAligned(nodeSize) | ||
| b := enc.alloc.Allocate(nodeSize) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is safe today, but it now leans on two rules that nothing enforces, and the comment on
|
||
| n := (*node)(unsafe.Pointer(&b[0])) | ||
| enc.setAttr(n, attr) | ||
| return n | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can't fire. Go always rounds a struct's size up to a multiple of its alignment (arrays depend on it), so
nodeSize % nodeAlignis always 0. I'd drop theinit()andnodeAlign. The rule that can actually break is that every allocation fromenc.allocisnodeSizebytes (see my comment onnewNode).