Go版本
go版本 go1.22.0 darwin/arm64
在你的模块/工作区中go env
的输出:
GO111MODULE=''
GOARCH='arm64'
GOBIN='/Users/***/gopath/bin'
GOCACHE='/Users/***/Library/Caches/go-build'
GOENV='/Users/***/Library/Application Support/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFLAGS=''
GOHOSTARCH='arm64'
GOHOSTOS='darwin'
GOINSECURE=''
GOMODCACHE='/Users/***/gopath/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='darwin'
GOPATH='/Users/***/gopath'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/opt/homebrew/Cellar/go/1.22.0/libexec'
GOSUMDB='sum.golang.org'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/opt/homebrew/Cellar/go/1.22.0/libexec/pkg/tool/darwin_arm64'
GOVCS=''
GOVERSION='go1.22.0'
GCCGO='gccgo'
AR='ar'
CC='cc'
CXX='c++'
CGO_ENABLED='1'
GOMOD='/dev/null'
GOWORK=''
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
PKG_CONFIG='pkg-config'
GOGCCFLAGS='-fPIC -arch arm64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -ffile-prefix-map=/var/folders/0q/57ygjhqj3k59y4b8txgw839r0000gn/T/go-build2558853606=/tmp/go-build -gno-record-gcc-switches -fno-common'
你做了什么?
运行我的应用程序,我调查了一些导致传入消息处理积压的长尾延迟。应用程序每秒处理4,000到50,0000条传入的JSON消息,通过websocket连接传输。这些消息在8到16个单独的套接字上进行解组,每个套接字都有一个goroutine进行读取。所有解组后的消息都通过一个通道发送给另一个执行对这些消息结果状态的时间敏感计算的goroutine。几乎没有内存分配和垃圾回收暂停,通常每30秒发生一次。这个以高速运行计算的goroutine对于应用程序的成功至关重要。
你看到了什么?
使用go 1.22进行正常编译时,完成计算所需的时间(99.99%分位数)存在显著的长尾延迟(5-15毫秒),而该计算通常需要30-90微秒(99%分位数)。在使用asyncpreemptoff
标志设置GODEBUG时,这种延迟(99.99%分位数)低于1毫秒。以下图像显示了设置标志前后的变化:
你期望看到什么?
我理解为什么抢占对于长时间运行的goroutine很有用,它们会饿死其他goroutine访问CPU,但我不希望由于其使用而出现这样的延迟。能够为特定goroutine禁用抢占可能是一个解决方案。如果你需要任何更具体的跟踪或分析,我很乐意提供它们。由于涉及的所有goroutine的时间非常依赖于第三方外部状态,所以很难为我提供一个重现器。
5条答案
按热度按时间ryevplcw1#
cc @golang/runtime
chy5wohz2#
有趣的问题。异步抢占的目的是减少尾部延迟,所以看到它反而增加了尾部延迟并不理想。
我能想象到的一种情况是,如果你有很多goroutines,但它们可以分为两类:高优先级,对延迟敏感或低优先级,对延迟容忍。如果高优先级的goroutine碰巧有紧密的循环,没有同步抢占点,那么异步抢占会更频繁地抢占它们。这将以高优先级goroutine的延迟为代价来提高低优先级goroutine的延迟。这是否描述了你的应用程序?
我希望看到的一张数据图是从 https://pkg.go.dev/runtime/metrics 到
/sched/latencies:seconds
的asyncpreemptoff
的累积直方图。请注意,这个直方图是自进程启动以来累计的,所以你只需要在最后收集一次。https://go.dev/cl/513115 有一些代码,你可以复制并将其格式化为ASCII图形。avwztpqn3#
cc @golang/runtime @mknyszek@aclements
p8h8hvxi4#
你好,
我可能在Grafana中使用了错误的PromQL(irate vs rate with a push gateway and inconsistent intervals)来生成上面看似确凿的图表。似乎
asyncpreemptoff
与我的问题无关,因为下面的/sched/latencies:seconds
数据可能表明了这一点。我无法再次重现asyncpreemptoff
影响延迟的效果。当我启用或禁用
asyncpreemptoff
时,我仍然看到关键goroutine中的CPU绑定函数每隔大约10,000条消息就需要花费很多毫秒才能完成,尽管实际处理部分只需要微秒就能完成。这会在队列中产生积压,从而对关键goroutine的后续迭代产生连锁React。正如你所描述的那样,有两个goroutine集合,一个对延迟敏感,不想被中断,以及许多等待网络上字节到来的异步goroutine。另一个用另一种语言编写的调度程序更好地讨论了这些问题和可能的解决方案:https://docs.rs/tokio/latest/tokio/#cpu-bound-tasks-and-blocking-code
这不一定是一个优先级问题,只是一个“请不要在我达到一个channel send或receive操作之前打断我,我很快就会做到”的问题。如果可以在goroutine开始时设置这样一个标志,那将有助于我的用例。如果为所有goroutine设置了这个标志,那么我认为CPU饥饿是有可能发生的......
无论如何,感谢你的时间!任何建议都乐于接受!如果需要,请随时关闭这个问题。
带有:
GODEBUG=asyncpreemptoff=1
的结果:
不带:
的结果:
rks48beu5#
谢谢,我很高兴现在问题得到了更好的理解。
您正在寻求的是在执行某些关键部分时禁用抢占的机制。我认为我们没有现有的问题需要解决这个问题,所以这可以达到这个目的,尽管我不认为这是我们明确想要做的事情。
我想我们也会有一个关于goroutine优先级的问题,尽管我似乎找不到一个。