Thanks for the comments!
default handlers
Default handlers are inspired from Eff, but there are some differences. Eff has the notion of effect instances and default handlers are associated with effect instances. Multicore OCaml does not have the concept of effect instances and default handlers are associated with effects (operations in Eff parlance). In Eff, if the default handler evaluates to an operation, then runtime error is reported. In Multicore, if the default handler performs an effect, we look for the default handler of that performed effect. If that effect does not have a default handler, then, unlike eff which reports a runtime error, we discontinue the continuation of the original perform with an exception.
effect E : unit
effect F with function _ -> perform E
try perform F with Unhandled ->
print_string "Raised unhandled since E doesn't have a default handler"
prints the error message. With default handlers, we can have the same Unix
or Sys
module signature, which would behave like vanilla OCaml without a handler, and be asynchronous/fiber-safe with an appropriate handler.
You briefly mention continuation copying in this paper, did you find a way to fix these issues, or a programming style that avoids them?
The current solution is to relegate them to Obj
module It is unclear whether there is a clean fix for this issue especially with resource. I would like to discourage the use of multi-shot continuations for typical use. But they indeed are useful in certain domain-specific instances – backtracking search, memoization, etc.
Could the Go results be explained by the fact that you are not in Go’s “pain zone” yet, and a test at even higher load show qualitatively different results because both the OCaml and the Go implementations are in trouble?
This is quite possible. As I am discovering, a high-performance web-server is a piece of finely tuned engineering (not unlike the GC). There is work to be done on the multicore GC tuning, but even more so for configuring the right socket option. It also seems like there is accept loop starvation on the OCaml side; OCaml servers don’t seem to accept connections as fast as Go server as the main accept loop is preempted in favour of request processing. Still tinkering with this benchmark.
Finally, I’m not sure what to conclude of the Async vs. Effects benchmark.
There is a misconception that direct-style is slower than indirect-style c.f threads vs events debate in early-2000s. Some language runtimes have moved away from lightweight/green threads (Rust, various JVM implementations) for various reasons, shift away from M:N to 1:1 threading on multicore. Go is an exception and seems to have done rather well. The story here is that direct-style implementations need not be necessarily slower just because they offer a better abstraction. A prototype effect-based asynchronous I/O library can compete with the well-engineered Async library, while offering advantages of direct-style programming (easier comprehension, backtraces and stack based profiling…).