Open mbacarella opened 3 years ago
I think it should be possible to exclude the current domain in the interface. Would you have any pointers to the relevant developer docs for the Linux/macOS cases you reference above? I couldn't find anything on a quick search about the various affinity requirements.
Firstly I'd like to say I'm really excited about multicore since it speeds up interactive application performance use-cases quite nicely in initial tests. My hands were shaking from excitement when I saw the results!
Re: affinity requirements, I don't have a primary source from Apple SDK docs, but. it's. well. observed. "Apple Cocoa main thread" for the OS imposed one. Though some libraries (like GLUT in the last link) may also elect this on all platforms.
For more background, if you are writing, say, a game or something with interactive game-like requirements (AR, or hifi audio mixing with visualization), you would ideally have at least three domains without tasks traveling between them.
Domain 1: which includes the first/main thread for GUI/video system interaction. You want this thread not to get held up because it would drop your frame rate and reduce controller interactivity. In my app this is the thread with Lwt, as well, since I think of it as the primary driver of the game.
Domain 2: this is a thread that simply polls the sound card to feed it the next audio sample. If this ever stutters it sounds terrible, probably worse than dropping frames. This needs to be very low latency because while background music is predictable and you can buffer ahead of time, game effects in response to a mouse click must be heard immediately.
Domain 3...n: for the rest of the cores on the machine. This handles bulk compute tasks like constructing textures or loading/processing assets. Seems right for Domainslib Task pool, especially if you want to do more of this stuff in pure OCaml rather than dispatching to C libraries sans runtime lock and taking on a bigger bug burden.
I get the first Domain automatically and the second if I do Domain.spawn sound_thread_loop
. The 3..n
pool I think I'd have to construct with something like: Domain.spawn (fun _ -> Domainslib.setup_pool ...)
and include a communication channel for sending jobs to it. Would be nice to perhaps include that logic inside of Domainslib itself.
Thanks for the feature request. The use case for not having the main thread be part of the pool seems valid. FWIW setting up the pool only creates the additional worker domains which wait for work (asyncs) to be pushed on the queue. It is the further calls to async
, await
and parallel_for*
that creates makes the caller pick up steal work from the queue.
One way to achieve this is to provide an async_push
function that ensures that the caller will only push the task to the queue by not execute it. With this, the intended use case can be implemented as:
(* main thread *)
let pool = setup_pool ~num_additional_domains () in
let promise = async_push pool initial_task in
(* the workers are now executing the [initial_task] and
its children. main thread is free to do its thing. *)
....
(* when it is time to terminate, for cleanup, you may optionally do *)
let res = await pool promise (* waits for the promise to resolve, if not already *)
teardown_pool pool
Will that be sufficient for your use case @mbacarella?
Hi @kayceesrk, that sounds like it would suffice, yes.
I notice there's no way to exclude the first domain from the Task pool. Is this a design invariant?
One concern I have with this is that in some environments the first thread has special status. E.g. on MacOS the first thread is the only one that's allowed to call into the GUI/video system. I believe the nVidia GPU drivers on Linux also require this. So, for these applications, these are threads I would definitely not want to have participate in a work pool.