[Full disclosure: some of the code and content linked below was done with the help of automated tools. This post is not.]
After a few years holding off on it, it was finally time to do the migration to OCaml 5 for the liquidsoap project! The excellent work done to tame OCaml 5 GC pacing gave us confidence that our memory issues should be in the past now.
The main motivation, for us, is a rather unconventional (I believe) way to use effects: to collect globally the result of local operations happening while liquidsoap scripts are executed. This is detailed here.
Once the switch to OCaml 5 was done, it was time to measure the benefits! The particular standpoint of liquidsoap is that it does both I/O- and CPU-intensive processing: I/O for networking and on-disk operations and CPU for media encoding and decoding. So this was a good opportunity to test both sides!
The first blog post has a lot of details on this topic including how we were finally able to lift our scheduler (named duppy) to take full advantage of its event loop. A decade after the initial design (which is very similar to node/libev), it was nice to see it finally be confirmed for I/O workflows.
For CPU-bound workflow, however, the initial experiments were focused on very heavy video processing, where media computations, mostly done outside of OCaml, dominate so this didn’t yield anything too exciting.
The situation is also interesting with Liquidsoap because it runs a real-time streaming loop so, all the advantages of multi-core super fast processing are kinda lost since you have to wait sometimes 70% of the time to avoid getting ahead of time..
However, thinking about it more deeply, a good use-case would, in contrast, be a situation where one audio encoding is shared by multiple sources. Audio encoding is already much less compute-heavy and, when shared, concurrency happening at the OCaml level starts being much more relevant.
This was confirmed in a second blog post. We converted all our clocks (the abstractions responsible for producing content in real-time) from system threads to duppy, domain-backed tasks and.. voila! Finally we were able to see some real benefit:
This shows that, with multi core ocaml supporting those audio streams, we’re able to double the number of streams a single process can support before being too slow to generate data real time.
Another interesting graph is then the number of context switches between threads and domain implementations:
Full details:

