4 ms·
I don't think tokio's slowness here is "to be expected". There isn't much reason for tokio tasks to have that much overhead over normal thread pools. The I/O dr
by kprotty 5y ago
I don't think tokio's slowness here is "to be expected". There isn't much reason for tokio tasks to have that much overhead over normal thread pools. The I/O driver shouldn't be called in such a benchmark given there's no I/O work happening. Waker's only add a reference count inc/dec + an atomic cas for wake() which should only happen on the JoinHandle `awaits` [4] compared to just an atomic swap for the Zig case on the join handle.
Golang doesn't poll for I/O under such cases [0] and tokio should be using the `ParkThread` parker for putting threads to sleep [1] given `net` features aren't enabled (not sure if this is the actually the case) which you can force with a custom Runtime initialization instead of `#[tokio::main]` as an exercise.
`crossbeam-deque` requires heap allocation for the run queues, heap allocates on growth, and garbage collects said memory. This is an overhead I wished to avoid and is something tokio has been improvements with avoiding as well [2].
`rayon` isn't a good comparison here given `rayon::join` is optimized to hook directly into the scheduler and run the caller only until the other forked-section completes [3]. This isn't general purpose and it takes advantage of unbounded stack allocation which can technically cause a stack overflow. Zig could do this and also take advantage of batch scheduling, but it complicates the code and is unfair here given `async` usage. Tokio, golang, and the Zig benchmarks require heap allocation on spawn so I believe it makes it a fairer comparison. This is also why I used rayon scopes instead of join(): less specialization and reintroduced the heap allocation from the unbounded concurrency.
The `unsafe` there is from me copying the benchmark code from the tokio version to the rayon version and forgetting to remove the hack. In tokio, ownership of the array needed to be passed into the function given the lifetime was no longer linear from the spawn() I assume (correct me if i'm wrong here, but this is what the compile error hinted at). So I needed to recreate the array after the function, hence unsafe. If there's a better way for the tokio version, please send a PR. I see you've done so for the rayon version and I gladly merged it.
[0]: See `atomic.Load(&netpollWaiters) > 0` in https://golang.org/src/runtime/proc.go https://golang.org/src/runtime/proc.go
[1]: https://github.com/tokio-rs/tokio/blob/master/tokio/src/runtime/driver.rs#L30p https://github.com/tokio-rs/tokio/blob/master/tokio/src/runt...
[2]: https://tokio.rs/blog/2019-10-scheduler#a-better-run-queue https://tokio.rs/blog/2019-10-scheduler#a-better-run-queue
[3]: https://docs.rs/rayon-core/1.9.1/src/rayon_core/join/mod.rs.html#152 https://docs.rs/rayon-core/1.9.1/src/rayon_core/join/mod.rs....
[4]: https://github.com/tokio-rs/tokio/blob/98578a6f4a494e709f00018b1cd5b0e3dd9a0f72/tokio/src/runtime/task/harness.rs#L226-L241 https://github.com/tokio-rs/tokio/blob/98578a6f4a494e709f000...
- skohan 5y agoHonestly tokio is so complex, and it serves so many use-cases it's hard for me to believe it could ever be truly optimal for any one use-case.
- kristoff_it 5y agoIsn't Go's scheduler just as versatile?
- skohan 5y agoI'm not an expert on the go scheduler, but my perception is that it is more of a focused single-purpose component whereas tokio seems like a sprawling swiss-army-knife of a library if you browse the source
- bbatha 5y agoThe tokio scheduler and the go scheduler are roughly equivalent. Much of tokios bulk is reimplementing much of the std lib in an async compatible way (io, os, blocking).
- kprotty 5y agoIf you browse the source, the go scheduler has complexities to deal with that tokio doesn't as well. The thread pool is unified between worker threads & blocking threads. Go also does goroutine preemption via signaling/SuspendThread + a background monitor thread called sysmon. Go does garbage collection and the tracing/race-detection/semantics are tightly coupled to both its allocator and it's scheduler. Go also exposes and maintains its entire standard library which includes an http client/server (tokio the org maintains their own as hyper but its separated from tokio the runtime). It can be fair to argue that Go is just as "swiss-army-knife" of a system as tokio.
- tick_tock_tick 5y agoIt's much more versatile but also has many more years of optimization.