imglib / imglyb

Connecting Java/ImgLib2 + Python/NumPy
https://pypi.org/project/imglyb/
BSD 2-Clause "Simplified" License
30 stars 5 forks source link

Use jpype.nio.convertToDirectBuffer to wrap Python memory to Java, instead of Unsafe #21

Open ctrueden opened 2 years ago

ctrueden commented 2 years ago

Using JNI's NewDirectByteBuffer function, one can wrap an existing memory address and length into an off-heap ByteBuffer. Now that ImgLib2 supports ByteBuffer-backed data, we could make use of this to have zero-copy access to NumPy arrays from Java without the Unsafe hacks currently used. This would hopefully make the imglib2-unsafe library obsolete.

We need to determine the best way to call that JNI function. It is used in the JPype project in a few places; perhaps there is an existing JPype call we can use, without needing to resort to our own Cython code or use of JNA... Edit: It should be as simple as calling jpype.nio.convertToDirectBuffer(narr) on the numpy array and feeding the resultant DirectByteBuffer to ImgLib2. After verifying this indeed does not copy the data, of course.

ctrueden commented 2 years ago

See also this discussion on Zulip.

ctrueden commented 2 years ago

One potential problem is that the java.nio.ByteBuffer API uses int for indexing, ugh. I think @hanslovsky might have told me this years ago. So then this approach may be dead in the water compared to Unsafe for large numpy arrays bigger than 2 GB...

ctrueden commented 2 years ago

For >2GB I guess we need the newer foreign memory API. https://www.baeldung.com/java-foreign-memory-access#motivation

mkitti commented 2 years ago

Yes, that is an issue, although we may be able to work around with a CellImg on the ImgLib2 side.

I need to double check because the C API does accept a jlong which should be an unsigned 64-bit integer. https://docs.oracle.com/javase/8/docs/technotes/guides/jni/jni-14.html

jobject NewDirectByteBuffer(JNIEnv* env, void* address, jlong capacity);
Thrameos commented 2 years ago

It seems like jpype should have a "toMemorySegment" or something similar that can operate on very large buffers to make this workable for you.

mkitti commented 2 years ago

Would that force the adoption of Java 19 then to use the latest version of jpype?

Thrameos commented 2 years ago

Not exactly. If it only appears in a particular version then I will place error checking such that it checks for the required class and if not present it will produce an exception when the user tried to use it. You can compile JPype against any version of the JVM but you will only be able to use that feature on Java 19 or later.

The easiest way for me to handle this is to make an adapter which converts a memoryview or some predefined memory space into a MemoryAddress in which the adapter class is specified as a string. In older versions as you can't import a non-existent symbol the adapter would be inactive, but when it goes get successfully loaded the adapter class will be loaded with it. Thus the feature automatically appears when the corresponding version of Java is available and invisible otherwise.

I do the same thing currently with Java 7, Java 8 and Java 9+. The org.jpype.jar file is cross version compiled with some internals calling stuff purely by reflection so that the Java that compiled it could be Java 7 and it will still work on Java 19 using features available in Java 11 version. Mind you it isn't easy to do as writing code for future features via reflect in a pain, but if that is required to support large memory addressing or improving sharing of numpy arrays I can likely pull it off. (For example, the way that call sensitive methods works changed and I had to make the code work on all which required symbols that only appear in certain versions.)

It may be more of a problem for the imagej library. Unless they are set up with adapters (such that the core can work with many memory concepts) and the ability to build multi-version jars, then they will end up tied to Java 19.

Is there a released Java version of this memory address code?

ctrueden commented 2 years ago

Is there a released Java version of this memory address code?

Apologies if I'm misunderstanding your question, but yeah, Java 19 was released on September 20, 2022. I like Azul's Zulu flavor, although I haven't personally tried the Java 19 version yet.

The foreign function and memory API is now a preview feature as of Java 19, meaning it's fully specified, fully implemented, and yet impermanent, intended to gather feedback from widespread community use.

Thrameos commented 2 years ago

Hmm. Preview unfortunately means that it will move or change in specification. So it sounds like Java 20 will be when I could implement this as a feature. Not that we can't test it early, I just can't make use of it until it is finalized.