6 ms·
I'm not sure we have a sufficiently efficient concurrency mechanism that doesn't simply give the user control over scheduling decisions. Depending on how many
by tmyklebu 10y ago
I'm not sure we have a sufficiently efficient concurrency mechanism that doesn't simply give the user control over scheduling decisions.
Depending on how many tens we're talking about, those tens of nanoseconds for a context switch might themselves blow your time budget even on ordinary hardware. 10 gigabits per second means one byte is about 800ps long, so a 64-byte ethernet frame (plus the gunk before and after) is about 7 tens of nanoseconds. To keep up with this traffic pattern, we need to be able to process packets at least this quickly.
A goroutine context switch plus one unbuffered channel read and one unbuffered channel write on my machine seems to take about 18 tens of nanoseconds on my machine when Go's runtime is made to use the same thread for everything. (I'm not sure how to better isolate context switch overhead in Go, but a context switch, channel read, and channel write per packet does seem to be the anticipated use case.)
- jstimpfle 10y agoIs 180ns that bad? Assuming that at each turn you process a packet of 180 bytes...
- tmyklebu 10y ago180ns overhead per packet means the time budget per packet for your business logic goes down by 180ns. That said, I suspect you can live a long life without ever encountering a situation where this matters.
- rumcajz 10y agoThe question to ask is: Can you do better? Say you use a state machine instead of using coroutines. Will one state transition of the machine be faster than one context switch? 180ns can definitely be improved on (with the C implementation in the project it's more likely to be 60ns or so) there's no fundamental reason why one approach should be faster than the other, given that both are doing basically the same thing, albeit on the different layers of the stack.
- tmyklebu 10y agoI'm not clear on why coroutines or an explicit state machine are necessary to implement network protocols. We have been doing it for decades by writing straightforward imperative C code and for years by writing C++ template classes that are templated on the downstream type (CRTP). Both of these approaches result in near-zero overhead (computers are good at fetching and decoding consecutive instructions) or even negative apparent overhead (if the compiler sees caller and callee, inlining and dead code elimination can potentially eliminate lower-level protocol handling irrelevant to the higher-level protocol). I would guess that transforming coroutine-based code into state machines hampers performance over the long term by scaring the programmer away from wanting to touch it. However, I would also guess that the direct cost of a coroutine context switch (save and restore all callee-save registers and stack pointer then indirect-jump somewhere unpredictable) outweighs the direct cost of a state machine transition (update some variable). I do not want to pass judgment on the approach beyond saying that its attendant overhead is too high for some workloads.
- rumcajz 10y agoOk, but doesn't "straight imperative code" rely on blocking API underneath it? If it does not, the implementation has to store the current state in case it cannot proceed immediately, then restore it later -- which is a context switch. As for state machine vs. coroutine performance, agreed. But the cost is somewhere around 10-15ns per context switch. Which may be worth paying to get a maintainable code.
- tmyklebu 10y agoYou can use the usual BSD socket API (in which case the context switches to/from the kernel for calling read() puts the contemplated workload out of reach) or any number of vendor-specific userland network stacks. There is no need to context switch. (Function calls and function returns are not context switches.)