Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

setup_pool: option to exclude the current/first domain? #55

Open
mbacarella opened this issue Oct 30, 2021 · 4 comments
Open

setup_pool: option to exclude the current/first domain? #55

mbacarella opened this issue Oct 30, 2021 · 4 comments

Comments

@mbacarella
Copy link

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.

@avsm
Copy link
Contributor

avsm commented Nov 1, 2021

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.

@mbacarella
Copy link
Author

mbacarella commented Nov 1, 2021

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.

@kayceesrk
Copy link
Contributor

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?

@mbacarella
Copy link
Author

Hi @kayceesrk, that sounds like it would suffice, yes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants