Skip to content

runtime,cl: add Go-compatible sampled memory profiling#2027

Open
cpunion wants to merge 6 commits into
xgo-dev:mainfrom
cpunion:codex/stage5-memprofile
Open

runtime,cl: add Go-compatible sampled memory profiling#2027
cpunion wants to merge 6 commits into
xgo-dev:mainfrom
cpunion:codex/stage5-memprofile

Conversation

@cpunion

@cpunion cpunion commented Jul 4, 2026

Copy link
Copy Markdown
Collaborator

Implements Go-compatible, stack-keyed sampled heap profiles for LLGo.

Problem

LLGo's runtime.MemProfile only kept size-class counters, so heap profiles had no allocation stacks or source-line attribution. During integration with current main, the first sample could also lazily initialize frame metadata from inside AllocZ/AllocU; that reentrant allocation corrupted reflected closure calls (_demo/go/reflectcallfn).

Changes

  • Samples allocated bytes with an exponential threshold based on runtime.MemProfileRate and stores raw counts keyed by physical call stack.
  • Adds exact allocation-line anchors and preserves frames in packages that read memory profiles.
  • Trims allocator/runtime plumbing when exposing records and retries profile-buffer sizing when the bucket set grows.
  • Initializes frame metadata before installing the allocator sampling hook, so sampling never performs first-use metadata initialization inside an allocation.

Coverage and validation

  • Go heapsampling.go conformance now passes and its xfails are removed.
  • Acceptance coverage checks exact per-line attribution; runtime tests exercise tiny allocations, pprof output, and reflected closure calls while every allocation is sampled.
  • Compiler tests cover both public-runtime path spellings, call/global detection, unrelated runtime use, and missing package metadata. packageReadsMemProfile coverage is 95.7% on macOS/arm64 and Linux/amd64.
  • Local macOS/arm64 and Linux/amd64 tests pass, including 500 repeated reflectcallfn executions on the current-main merge tree.
  • GitHub CI: 41 successful checks, 1 expected skip; Codecov patch passes.

Uses the frame-pointer/funcinfo infrastructure already merged in #2024.

@codecov

codecov Bot commented Jul 4, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.44444% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
cl/instr.go 94.11% 1 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@cpunion
cpunion force-pushed the codex/stage5-memprofile branch 4 times, most recently from c647b14 to 2b22d07 Compare July 4, 2026 12:52
@cpunion
cpunion force-pushed the codex/stage5-memprofile branch 2 times, most recently from 4695ad3 to 9d9f6b0 Compare July 8, 2026 07:02
@cpunion
cpunion force-pushed the codex/stage5-memprofile branch 3 times, most recently from e6bbe61 to 2e36dbf Compare July 9, 2026 05:52
@cpunion cpunion added go-test-compat Go standard-library and GOROOT test compatibility go1.26 Go 1.26 compatibility and support feature labels Jul 16, 2026
cpunion and others added 3 commits July 19, 2026 07:35
Replaces the size-class counters with gc-shaped heap profiling: sampled
allocations are attributed to physical call stacks at exact statement
lines, and records hold RAW sampled counts — consumers (pprof, goroot
heapsampling.go) apply the Poisson correction themselves, exactly as
with gc.

- Sampling mirrors gc's mcache.nextSample: bytes count down to an
  exponentially distributed threshold (mean MemProfileRate), sample once
  on crossing, redraw. The memoryless distribution is load-bearing: with
  any bounded-support threshold a near-periodic allocation pattern
  phase-locks the sample points onto the large sites (observed 1.6x
  per-site skew on heapsampling's interleaved sizes). ln() is a small
  local approximation — the runtime core cannot import math.
- Stacks come from the FP walk at sample time (fpCallers via a hook the
  public runtime registers), bucketed by stack hash; allocator plumbing
  (including __llgo_stub. wrapper frames of the hook) is trimmed at read
  time. A reentrancy flag spans the whole decision path: threshold
  drawing and bucket allocation themselves allocate, and a recursive
  sample overflows the stack.
- Heap allocations get statement anchors in tracked functions, and a
  package that reads the memory profile (runtime.MemProfile /
  MemProfileRate under either the "runtime" or the patched
  lib-runtime spelling) pins all its trackable functions: per-site
  attribution loses sites to inlining otherwise. Profiling packages are
  rare and accuracy beats inlining there; gc gets both via its inline
  tree (P4).

goroot heapsampling.go passes on darwin/arm64 and linux/arm64 (the
latter was a pre-existing platform gap). Depends on --icf=none from the
line-directive PR: heapsampling's three identical wrapper functions must
keep distinct pcs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
An acceptance regression asserts exact per-line attribution at rate=1
(raw counts are exact there), and a cl unit test covers the
memprofile-package pinning criterion under both runtime spellings.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… capture path

The frame-table init allocates; when one of those allocations crossed the
sampling threshold, captureMemProfileStack -> fpCallers re-entered
initRuntimeFuncPCFramesSlow on the thread that already held the Busy
latch and usleep-spun forever. First testing.callerName call of a test
binary triggers the init, so whole test binaries hung at startup —
which packages hit it depends on the deterministic threshold sequence
meeting the binary's pre-init allocation volume: net/rpc and
net/rpc/jsonrpc under go1.24 stdlib, net/http/expvar/cookiejar under
go1.26 (CI shard timeouts on ubuntu, both attempts).

Entering an Uninit latch from the capture path is safe (the whole sample
runs under memProfileInSample, so init's own allocations cannot
re-sample); only Busy must not be waited on. Drop that one sample.
@cpunion cpunion changed the title runtime,cl: stack-keyed sampled memory profiling with gc semantics runtime,cl: add Go-compatible sampled memory profiling Jul 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

go1.26 Go 1.26 compatibility and support go-test-compat Go standard-library and GOROOT test compatibility

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants