[GHC Blog] The keepAlive# story

In this post I describe the motivation and technical design of the new keepAlive# primop introduced in GHC 9.0, along with some guidance on when and how users should use the new operation.

I’m happy to answer questions here.

4 Likes

This question is only tangentially related, but you mention that safe ffi calls allow Haskell to continue running other threads in the background, does that imply that unsafe calls cannot be run multithreaded?

I have tested it out and it seems that unsafe ffi calls still run concurrently if I use the +RTS -N option up to a point, but it is really unpredictable if the number of concurrent threads is larger than the number of CPU threads. Is there any logic to that? How can I make sure that my unsafe calls still get to run concurrently to get the best possible performance?

I guess a good rule of thumb would be to use safe for long ffi calls and unsafe for short ffi calls, but even short ffi calls might block other threads from running, so I wonder if there is any way to reason about that.

Here is my test setup: https://gist.github.com/noughtmare/8f5c80b777b7aad7b81e472ee8da17ca

Also, there is a [TODO: elaborate] left in your post.

And an [introduced][] about the BoxedRep.

And “which had lurked in previous soundness issues which had lurked in previous releases”, at the end.

This question is only tangentially related, but you mention that safe ffi calls allow Haskell to continue running other threads in the background, does that imply that unsafe calls cannot be run multithreaded?

A Haskell thread can indeed run an unsafe call while the other Haskell threads are running. However, a GC cannot begin until the unsafe call has returned. This is the case because the RTS must synchronize with all capabilities before a GC can proceed.

How can I make sure that my unsafe calls still get to run concurrently to get the best possible performance?

unsafe calls are essentially “fat” assembly instructions: They require no intervention from the RTS and essentially no overhead beyond the usual C calling convention. However, they can also come back to bite you: if you perform a long-running unsafe call then GC can quickly become a bottleneck since all threads will be unable to start the GC, and therefore make progress, until the call finishes.

As a rule of thumb, I would generally avoid using a unsafe foreign calls for anything that might last more than a few dozen microseconds and certainly avoid them in cases which block. Of course, this poses an interesting dilemma in the case of operations (e.g. some system calls) which are “usually” fast but sometimes may block. In this case I would generally advise using a safe call.

1 Like