Skip to content

FFM#54

Merged
skeet70 merged 15 commits intomainfrom
0.31.0-ffm
Apr 3, 2026
Merged

FFM#54
skeet70 merged 15 commits intomainfrom
0.31.0-ffm

Conversation

@skeet70
Copy link
Copy Markdown
Member

@skeet70 skeet70 commented Mar 31, 2026

Benchmarks using the new upstream benches show a marked improvement, see below the fold.

Major changes:

  • the core JNA to FFM conversion
    • replaced the Library interface with MethodHandle lookups. Every native function has a static MH_* field with its function descriptor.
    • RustBuffer switched from a JNA Structure to an FFM MemorySegment-backed type
    • uniffiRustCall helpers use MemorySegment as well. Also added primitive-specialized versions to avoid a long boxing/unboxing chain (which didn't change performance due to JIT in tested cases, but is "more correct")
  • future polling uses Linker.nativeLinker().upcallStub(), so do callback interfaces
  • ObjectCleanerHelper had a major change. The FFM was much faster than JNA and the some of the benches that create objects effectively blitzed objects because of it. The static cleaner reaper couldn't keep up and the benches would OOM. I confirmed that with a unit test (and that it failed before the fix). The fix was to introduce backpressure to the cleaner and have it opportunistically drain when registering something new with the cleaner. It seemed to have a side effect of improving performance due to more consistent memory use (I was expecting a small hit).
  • mechanical changes to mod.rs to get the right types back from filters
  • used PanamaPort to support Android via a post processing step to replace the FFM qualifications. Consider Android support experimental for now.

This probably puts the nail in the coffin of #23, since it requires Java 22 and make some major changes around FFM compatibility.

Java FFM vs JNA Speedup

Test Case Fn Call Speedup Callback Speedup
call-only 426x 36x
primitives 360x 45x
strings 35x 40x
large-strings 4.2x 4.5x
records 31x 26x
enums 43x 28x
vecs 23x 19x
hash-maps 20x 17x
interfaces 11x 8x
trait-interfaces 23x 9x
nested-data 6.6x 10x
errors 6.6x 10x

function-calls

Test Case FFM (Java) (Kotlin) (Python) (Swift)
call-only 4.2 ns 3.68 us 825.57 ns 175.41 ns
primitives 4.6 ns 3.34 us 1.80 us 205.84 ns
strings 326.17 ns 16.52 us 10.17 us 1.04 us
large-strings 3.87 us 30.83 us 15.00 us 1.34 us
records 353.93 ns 19.62 us 23.41 us 2.71 us
enums 315.79 ns 16.84 us 18.74 us 2.16 us
vecs 561.60 ns 24.46 us 19.71 us 4.78 us
hash-maps 632.82 ns 16.48 us 30.89 us 6.77 us
interfaces 549.40 ns 16.86 us 4.45 us 428.84 ns
trait-interfaces 622.55 ns 13.54 us 4.63 us 494.57 ns
nested-data 1.96 us 21.20 us 80.96 us 21.53 us
errors 759.53 ns 8.92 us 3.85 us 656.41 ns

callbacks

Test Case FFM (Java) (Kotlin) (Python) (Swift)
call-only 60.98 ns 3.48 us 581.12 ns 125.33 ns
primitives 60.29 ns 5.02 us 983.99 ns 175.32 ns
strings 368.22 ns 21.42 us 10.06 us 865.17 ns
large-strings 3.95 us 27.06 us 11.23 us 2.07 us
records 432.14 ns 21.14 us 15.20 us 3.56 us
enums 362.55 ns 17.79 us 12.68 us 2.52 us
vecs 526.44 ns 22.19 us 20.14 us 4.90 us
hash-maps 706.77 ns 17.40 us 21.64 us 8.10 us
interfaces 1.14 us 18.49 us 4.15 us 493.11 ns
trait-interfaces 1.26 us 15.05 us 4.29 us 592.55 ns
nested-data 1.94 us 17.29 us 69.78 us 26.91 us
errors 620.86 ns 8.62 us 4.30 us 597.79 ns

On some things like trait-interfaces there is significant variation due to GC pressure, the lows that seem to be more normal cases are ~250-500ns.

skeet70 added 4 commits March 31, 2026 12:38
TODO
- chase down memory leak
- cleanup
Java (and Kotlin) have to autobox things to use the generic uniffi FFI converter (since generics require boxed values).
This technically makes for a super strange series of calls for uniffi calls:

`long→Long→Long→long→FFI→long→Long→Long→long` for a primitive add call for example.

From my benchmarking (after making these changes) it looks like the JIT is already optimizing those extra calls away,
but this changes makes it more correct by default instead of depending on the JIT always figuring the right thing out.
@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 1, 2026

File Coverage Lines
All files 91% 91%
src/gen_java/mod.rs 94% 94%

Minimum allowed coverage is 0%

Generated by 🐒 cobertura-action against a0c0339

skeet70 added 7 commits April 1, 2026 15:46
- close() vs GC race doesn't mess things up under contention with the backpressure cleaner
- Arena\.global() memory stability (may come back to this one after thinking on global more)
- renabled the testsprites fixture from forever ago
- REUSABLE_STATUS segment zeroing doesn't corrupt error data between calls
Which removes the known small memory leak. Benches showed there isn't
a significant performance impact.
This hasn't been tested on a real Android device, but if it doesn't work
for someone we can deal with it then.
@skeet70 skeet70 marked this pull request as ready for review April 2, 2026 05:12
@skeet70 skeet70 requested a review from a team as a code owner April 2, 2026 05:12
@skeet70 skeet70 requested review from giarc3 and removed request for a team April 2, 2026 05:12
Copy link
Copy Markdown
Member

@giarc3 giarc3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really impressive results

README.md Outdated

* Java 20+: `javac`, and `jar`
* The [Java Native Access](https://github.com/java-native-access/jna#download) JAR downloaded and its path added to your `$CLASSPATH` environment variable.
* Java 21+: `javac`, and `jar`
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this needs to be 22

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the current shape of the FFM API is actually available in 21+, it left preview in 22+. PanamaPort lists 22+ for the Android support part though, so might as well list that as well.

@skeet70 skeet70 merged commit c3ba8e7 into main Apr 3, 2026
7 checks passed
@skeet70 skeet70 deleted the 0.31.0-ffm branch April 3, 2026 18:16
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

Successfully merging this pull request may close these issues.

3 participants