diff options
| author | Michael Anthony Knyszek <mknyszek@google.com> | 2025-11-19 03:17:54 +0000 |
|---|---|---|
| committer | Michael Knyszek <mknyszek@google.com> | 2025-11-21 14:04:31 -0800 |
| commit | d68aec8db1bc3c167d2f0e5fdee8c1346ee35418 (patch) | |
| tree | 3faf1812974764db8e7c1941f8651f9faddb1470 /src/runtime/tracecpu.go | |
| parent | 8d9906cd34a1052868c1c0273e6f2d22632e0e84 (diff) | |
| download | go-d68aec8db1bc3c167d2f0e5fdee8c1346ee35418.tar.xz | |
runtime: replace trace seqlock with write flag
The runtime tracer currently uses a per-M seqlock to indicate whether a
thread is writing to a local trace buffer. The seqlock is updated with
two atomic adds, read-modify-write operations. These are quite
expensive, even though they're completely uncontended.
We can make these operations slightly cheaper by using an atomic store.
The key insight here is that only one thread ever writes to the value at
a time, so only the "write" of the read-modify-write actually matters.
At that point, it doesn't really matter that we have a monotonically
increasing counter. This is made clearer by the fact that nothing other
than basic checks make sure the counter is monotonically increasing:
everything only depends on whether the counter is even or odd.
At that point, all we really need is a flag: an atomic.Bool, which we
can update with an atomic Store, a write-only instruction.
Change-Id: I0cfe39b34c7634554c34c53c0f0e196d125bbc4a
Reviewed-on: https://go-review.googlesource.com/c/go/+/721840
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Michael Pratt <mpratt@google.com>
Diffstat (limited to 'src/runtime/tracecpu.go')
| -rw-r--r-- | src/runtime/tracecpu.go | 14 |
1 files changed, 7 insertions, 7 deletions
diff --git a/src/runtime/tracecpu.go b/src/runtime/tracecpu.go index e64ca32cdf..c9c3a1511f 100644 --- a/src/runtime/tracecpu.go +++ b/src/runtime/tracecpu.go @@ -224,20 +224,20 @@ func traceCPUSample(gp *g, mp *m, pp *p, stk []uintptr) { // We're going to conditionally write to one of two buffers based on the // generation. To make sure we write to the correct one, we need to make - // sure this thread's trace seqlock is held. If it already is, then we're + // sure this thread's trace write flag is set. If it already is, then we're // in the tracer and we can just take advantage of that. If it isn't, then // we need to acquire it and read the generation. locked := false - if mp.trace.seqlock.Load()%2 == 0 { - mp.trace.seqlock.Add(1) + if !mp.trace.writing.Load() { + mp.trace.writing.Store(true) locked = true } gen := trace.gen.Load() if gen == 0 { - // Tracing is disabled, as it turns out. Release the seqlock if necessary + // Tracing is disabled, as it turns out. Clear the write flag if necessary // and exit. if locked { - mp.trace.seqlock.Add(1) + mp.trace.writing.Store(false) } return } @@ -275,8 +275,8 @@ func traceCPUSample(gp *g, mp *m, pp *p, stk []uintptr) { trace.signalLock.Store(0) - // Release the seqlock if we acquired it earlier. + // Clear the write flag if we set it earlier. if locked { - mp.trace.seqlock.Add(1) + mp.trace.writing.Store(false) } } |
