Understanding the Go Runtime: The Scheduler (internals-for-interns.com)

by valyala 48 comments 158 points
Read article View on HN

48 comments

[−] pss314 65d ago
I enjoyed both these GopherCon talks:

GopherCon 2018: The Scheduler Saga - Kavya Joshi https://www.youtube.com/watch?v=YHRO5WQGh0k

GopherCon 2017: Understanding Channels - Kavya Joshi https://www.youtube.com/watch?v=KBZlN0izeiY

[−] c0balt 65d ago
https://m.youtube.com/watch?v=-K11rY57K7k - Dmitry Vyukov — Go scheduler: Implementing language with lightweight concurrency

This one notably also explains the design considerations for golangs M:N:P in comparison to other schemes and which specific challenges it tries to address.

[−] konart 64d ago
And by Dmitry himself.
[−] jvillegasd 65d ago
Good videos, thanks for sharing!
[−] withinboredom 65d ago
My biggest issue with go is it’s incredibly unfair scheduler. No matter what load you have, P99 and especially P99.9 latency will be higher than any other language. The way that it steals work guarantees that requests “in the middle” will be served last.

It’s a problem that only go can solve, but that means giving up some of your speed that are currently handled immediately that shouldn’t be. So overall latency will go up and P99 will drop precipitously. Thus, they’ll probably never fix it.

If you have a system that requires predictable latency, go is not the right language for it.

[−] mknyszek 64d ago

> Thus, they’ll probably never fix it.

I'm sorry you had a bad experience with Go. What makes you say this? Have you filed an issue upstream yet? If not, I encourage you to do so. I can't promise it'll be fixed or delved into immediately, but filing detailed feedback like this is really helpful for prioritizing work.

[−] melodyogonna 64d ago

> If you have a system that requires predictable latency, go is not the right language for it.

Having a garbage collector already make this the case, it is a known trade off.

[−] _rlh 62d ago
“It’s a problem that only go can solve”

I had this discussion a decade ago and concluded that a reasonable fair scheduler could be built on top of the go runtime scheduler by gating the work presented. The case was be made that the application is the proper, if not only, place to do this. Other than performance, if you encountered a runtime limitation then filing an issue is how the Go community moves forward.

[−] pjmlp 64d ago
It misses having a custom scheduler option, like Java and .NET runtimes offer, unfortunely that is too many knobs for the usual Go approach to language design.

Having a interface for how it is supposed to behave, a runtime.SetScheduler() or something, but it won't happen.

[−] red_admiral 65d ago

> If you have a system that requires predictable latency, go is not the right language for it.

I presume that's by design, to trade off against other things google designed it for?

[−] kjksf 64d ago

> No matter what load you have, P99 and especially P99.9 latency will be higher than any other language

I strongly call BS on that.

Strong claim and evidence seems to be a hallucination in your own head.

There are several writeups of large backends ported from node/python/ruby to Go which resulted in dramatic speedups, including drop in P99 and P99.9 latencies by 10x

That's empirical evidence your claim is BS.

What exactly is so unfair about Go scheduler and what do you compare it to?

Node's lack of multi-threading?

Python's and Ruby's GIL?

Just leaving this to OS thread scheduler which, unlike Go, has no idea about i/o and therefore cannot optimize for it?

Apparently the source of your claim is https://github.com/php/frankenphp/pull/2016

Which is optimizing for a very specific micro-benchmark of hammering std-lib http server with concurrent request. Which is not what 99% of go servers need to handle. And is exercising way more than a scheduler. And is not benchmarking against any other language, so the sweeping statement about "higher than any other language" is literally baseless.

And you were able to make a change that trades throughput for P99 latency without changing the scheduler, which kind of shows it wasn't the scheduler but an interaction between a specific implementation of HTTP server and Go scheduler.

And there are other HTTP servers in Go that focus on speed. It's just 99.9% of Go servers don't need any of that because the baseline is 10x faster than python/ruby/javascript and on-par with Java or C#.

[−] pothamk 64d ago
[dead]
[−] desdenova 64d ago

> If you have a system, go is not the right language for it.

FTFY

[−] Someone 64d ago

> a goroutine’s state is surprisingly small. The mcall() assembly function only saves 3 values — the stack pointer, the program counter, and the base pointer — into a tiny gobuf struct. That’s it. Why so few? Because goroutine switches happen at function call boundaries, and at those points the compiler has already spilled any important registers to the stack following normal calling conventions.

Wouldn’t that mean go never uses registers to pass arguments to functions?

If so, that seems in conflict with https://go.dev/src/cmd/compile/abi-internal#function-call-ar..., which says “Because access to registers is generally faster than access to the stack, arguments and results are preferentially passed in registers”

Or does the compiler always Go’s stable ABI, known as ABI0 in functions where it inserts code to potentially context switch, and only uses the (potentially) faster ABI that passes arguments in registers elsewhere?

[−] avabuildsdata 64d ago
The unfair scheduling point resonates. I run a lot of concurrent HTTP workloads in Go (scraping, data pipelines) and the scheduler is honestly fine for throughput-oriented work where you don't care about tail latency. But the moment you need consistent response times under load it becomes a real problem. GOMAXPROCS tuning and runtime.LockOSThread help in narrow cases but they're band-aids. The lack of priority or fairness knobs is a deliberate design choice but it does push certain workloads toward other runtimes.
[−] Horos 64d ago
Isn't a dedicated worker pool with priority queues enough to get predictable P99 without leaving Go?

If you fix N workers and control dispatch order yourself, the scheduler barely gets involved — no stealing, no surprises.

The inter-goroutine handoff is ~50-100ns anyway.

Isn't the real issue using go f() per request rather than something in the language itself?

[−] GeertVL 65d ago
This is an excellent idea as a blog. Kudos!
[−] capricio_one 64d ago
Go missed a big opportunity to be Rust when we needed Rust more than anything. I have long since moved on from Go and C#/.NET is widely available nowadays and in many respects less held back by some strange political choices when it comes to DevEx (I am of course talking about generics).