[...] using a ForeignPtr here, with free() as its finalizer, adds so much overhead that [...]
Where is the overhead coming from? Is it the cost of a C call or the cost of the standard malloc library?
It's the combined cost of - malloc() - creating a weak pointer to register the finalizer - running the finalizer - free() I can't remember the exact break-down, but I believe more than half the cost is in malloc+free.
If the latter, I imagine that a custom allocator would have similar performance to using pinned objects.
Yes, using a custom allocator is likely to get you most of the benefit (I say most, because you'd still need a finalizer and a free() routine, compared to GC). But this is an argument in favour of the new interface, because it abstracts away from the actual allocator used, so the implementor is free to provide a custom allocator. That's a win, isn't it?
(I'm sort of assuming that pinned objects are more expensive than normal objects.)
btw I don't know if it's relevant but there's an important semantic difference between allocating on the GHC heap and allocating on the C heap. The C version can be manipulated by the malloc.h functions. In particular, you can call realloc on it. Do that on the GHC heap version and... well, it won't be pretty. I don't know if this is relevant because using realloc with ForeignPtr'd memory is likely to be a delicate procedure no matter how it got allocated.
Yes, I thought about this. Fortunately a ForeignPtr isn't mutable, even using GHC extensions, so I can't see a way to safely call realloc() once you've made a ForeignPtr. Anyway, the docs for mallocForeignPtr would have to say something like "the pointer is not guarnateed to have been returned by malloc()". Cheers, Simon