Skip to content

Avoid a channel receive in the hot-path closed check - #1018

Open
asnyatkov wants to merge 1 commit into
mainfrom
perf/taskloop-err-atomic
Open

asnyatkov wants to merge 1 commit into
mainfrom
perf/taskloop-err-atomic

Conversation

@asnyatkov

Copy link
Copy Markdown
Contributor

Loop.Err is a non-blocking select on the done channel, which Go compiles to a runtime.selectnbrecv call that cannot be inlined away. Conn.Write and Conn.Read call it once per packet, so it is measurable on packet-heavy workloads: it accounted for 5% of flat CPU in a profile of an SFU fanning video to ~7k subscribers.

Add Loop.Closed, an atomic load that mirrors done and inlines into its callers, and use it to gate the Conn.Read, Conn.Write and Conn.WriteToPair error paths and the Loop.Run fast path. The flag is stored after done is closed, so Closed always implies Err is non-nil and the callers can keep reporting Err itself. Err and Done are untouched and remain the exact context.Context implementation, both reading only the done channel.

BenchmarkErr-10       4.65 ns/op
BenchmarkClosed-10    2.13 ns/op

goos: darwin, goarch: arm64, cpu: Apple M1 Pro

Description

Reference issue

Fixes #...

@JoTurk

JoTurk commented Sep 28, 2026 •

Copy link
Copy Markdown
Member

Did you change your mind about releasing this to v4? :)

Main is v5

@asnyatkov

Copy link
Copy Markdown
Contributor Author

My local branch got into a weird state. I'll re-apply to v4 after.

Loop.Err is a non-blocking select on the done channel, which Go
compiles to a runtime.selectnbrecv call that cannot be inlined away.
Conn.Write and Conn.Read call it once per packet, so it is measurable
on packet-heavy workloads: it accounted for 5% of flat CPU in a profile
of an SFU fanning video to ~7k subscribers.

Add Loop.Closed, an atomic load that mirrors done and inlines into its
callers, and use it to gate the Conn.Read, Conn.Write and
Conn.WriteToPair error paths and the Loop.Run fast path. The flag is
stored after done is closed, so Closed always implies Err is non-nil
and the callers can keep reporting Err itself. Err and Done are
untouched and remain the exact context.Context implementation, both
reading only the done channel.

    BenchmarkErr-10       4.65 ns/op
    BenchmarkClosed-10    2.13 ns/op

goos: darwin, goarch: arm64, cpu: Apple M1 Pro
@asnyatkov
asnyatkov force-pushed the perf/taskloop-err-atomic branch from e728202 to d4aba4f Compare September 28, 2026 18:16
@codecov

codecov Bot commented Sep 28, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.00000% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 91.88%. Comparing base (1925aac) to head (d4aba4f).

Files with missing lines Patch % Lines
transport.go 83.33% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1018      +/-   ##
==========================================
- Coverage   91.90%   91.88%   -0.02%     
==========================================
  Files          46       46              
  Lines        5510     5511       +1     
==========================================
  Hits         5064     5064              
- Misses        446      447       +1     
Flag Coverage Δ
go 91.88% <90.00%> (-0.02%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants