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

Consider using MAP_JIT on macOS #73

Open
mkeeter opened this issue Oct 12, 2022 · 1 comment
Open

Consider using MAP_JIT on macOS #73

mkeeter opened this issue Oct 12, 2022 · 1 comment

Comments

@mkeeter
Copy link
Contributor

mkeeter commented Oct 12, 2022

On macOS, the recommended way to implement a JIT system is by creating the memory map with PROT_WRITE | PROT_EXEC and the MAP_JIT flag, then using pthread_jit_write_protect_np to switch between writing and executing the buffer.

(This is kinda weird, because the W^X behavior is tracked on a per-thread basis, rather than per-region; I found it easiest to only enable W right before copying into the region, then disable it afterwards)

Anyways, it turns out that this is much faster than using mmap to swap regions from PROT_WRITE to PROT_EXEC!

Here's a flamegraph using mmap

Screen Shot 2022-10-12 at 8 39 43 AM

(note the calls to mprotect and memmove taking up a good chunk of time)

Here's what it looks like with pthread_jit_write_protect_np

Screen Shot 2022-10-12 at 8 40 18 AM

(those calls are gone, and pthread_jit_write_protect_np doesn't even show up)

I see one benchmark go from 112 ms down to 62 ms, almost a 50% improvement!

(My benchmarks are admittedly weird, in that they compile a lot of very small functions 😆)

This requires ditching / forking the memmap2 crate, which doesn't support this behavior. Here's how I did it.

Right now, it's easy for users to do this on their own: I'm using a VecAssembler then copying into this custom struct Mmap, which works fine. Still, this would be a decent optimization for the stock Assembler.

As always, the dynasm-rs is great, and I really appreciate the work that went into it!

@CensoredUsername
Copy link
Owner

Interesting, seems like Apple probably also ran into perf bottlenecks with memory protection swapping due to needing to JIT x64 code, so they added a thread-based switch that alters the behaviour of page table permission checks without needing to edit them (and the fairly expensive cache flushes that that tends to cause).

I have no apple hardware myself (I'm just a student who wrote this initially for a fun side project). So it's hard to validate these things, but then again, this is the reason dynasmrt exports basically all the needed components to construct your own custom assemblers as there's too many different but valid ways that you might want to manage your JIT memory. It's therefore probably not needed for this support to be in tree, but let me know if you're missing any components that could be reused outside of the internals of dynasmrt that aren't exported.

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

No branches or pull requests

2 participants