Skip to content

Conversation

@ktoso
Copy link
Collaborator

@ktoso ktoso commented Feb 9, 2026

This also improves the Array.getJNIValue(in:) when the array is of bytes, addressing a long standing TODO.

I also worked on optimizing the toByteArray some more and now we're able to get such performance when doing the copying (by avoiding iterating and mapping data into jbytes):

Benchmark                                      (dataSize)  (taskCount)  Mode  Cnt      Score      Error  Units
// how you'd do it naively:
JNIDataBenchmark.jni_data_toByteArrayIndirectCopy       4          N/A  avgt   30    616.328 ±   13.157  ns/op
JNIDataBenchmark.jni_data_toByteArrayIndirectCopy     100          N/A  avgt   30   4030.721 ±   48.059  ns/op
JNIDataBenchmark.jni_data_toByteArrayIndirectCopy    1000          N/A  avgt   30  35415.920 ±  253.776  ns/op <<< "what you'd do naively"

// how we do it now:
JNIDataBenchmark.jni_data_toByteArray                   4          N/A  avgt   30    264.089 ±   10.726  ns/op
JNIDataBenchmark.jni_data_toByteArray                 100          N/A  avgt   30    256.012 ±    1.972  ns/op
JNIDataBenchmark.jni_data_toByteArray                1000          N/A  avgt   30    282.767 ±    4.469  ns/op << _nice_

These work because uint8 and jbyte should be really have the same layout AFAIK so we don't need to iterate, but just rebind the memory and use the JNI funcs to initialize the java array. We could do even better by initializing into an existing array, but I didn't do that yet.

I did use the help of Claude (with lots of hand holding) to get some of the boilerplate done here, and especially make sure the FFM and JNI modes are roughly equivalent. I've read and vetted all the code and tests, and most documentation was written by myself.

@madsodgaard
Copy link
Contributor

madsodgaard commented Feb 9, 2026

Very nice!

Could we add the same mechanism for init(fromJNI:)? Such that we also only incur a single copy for Data.fromByteArray?

@ktoso
Copy link
Collaborator Author

ktoso commented Feb 10, 2026

Yep we can do that, following up

// array directly without this extra copy. For now, just map.
self = jniArray.map { Element(fromJNI: $0, in: environment) }
self = result as! Self
} else if Element.self == Int8.self {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Could not nicely pull this off in a single branch... doing some small local func was even more annoying tbh -- since we need the concrete Int8.jniGetArrayRegion

@ktoso ktoso merged commit ba51454 into swiftlang:main Feb 10, 2026
49 checks passed
@ktoso ktoso deleted the wip-data-jni-poc branch February 10, 2026 01:18
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.

2 participants