3 ms·
The problem is that it's not really benchmarking coroutines; it's benchmarking goroutines. Goroutines support preemptive parallelism, so channels require atomic
by wahern 4y ago
The problem is that it's not really benchmarking coroutines; it's benchmarking goroutines. Goroutines support preemptive parallelism, so channels require atomic operations. Atomic operations are performance killers.
IIRC/IIUC, Go in 2011 only supported a single logical processor (P) in its runtime model of (G)oroutines, Logical (P)rocessors, and (M)achine threads. It's possible channels didn't require any atomic operations at all. I'd be surprised if a coroutine style implementation would have been faster, as resuming and yielding coroutines typically involve more operations then entering into and returning from a function[1], but the overhead might have been low-enough to be negligible relative to elegance of the lexer code. (Inverting consumer/producer calling patterns is one of the areas at which coroutines excel, especially stackful coroutines where you can make full use of the stack as an implicit data structure.)
[1] Coroutines in Lua are relatively cheap, but because they have to do exception handling bookkeeping (i.e. setjmp) they're still more expensive than a regular function call. This is specific to Lua and not a necessary part of implementing coroutines. However, I can't imagine a situation where coroutines would be faster than a regular function call, and difficult to imagine them having the same cost unless a language uses a completely discontiguous stack for all call frames. In general coroutines have the same cost as a function call plus the need to save/restore at least some additional stack information.
- alex_smart 4y agoThe funny thing is, the final version of the lexer from Rob Pike's talk did not involve any goroutines at all.
- wahern 4y agoAh, from slide #39 A problem Can't run a goroutine to completion during initialization. Forbidden by the language specification. (Raises awful issues about order of init, best avoided.) That means we can't lex & parse a template during init. The goroutine is a problem.... https://talks.golang.org/2011/lex.slide#39 https://talks.golang.org/2011/lex.slide#39 Removing the use of goroutines seems to have been simple. Not too surprised as lexers don't usually need very deep call stacks, anyhow. (A single loop and switch can suffice in most situations. Or a single level of calls when one is inclined to break things out into a function dispatch table.) In Lua I've found coroutines more useful for writing iterators over trees, where there's mutual recursion.