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

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

Open
ctrueden opened this issue Nov 2, 2022 · 9 comments
Labels
enhancement New feature or request
Milestone

Comments

@ctrueden
Copy link
Member

ctrueden commented Nov 2, 2022

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 ctrueden added the enhancement New feature or request label Nov 2, 2022
@ctrueden
Copy link
Member Author

ctrueden commented Nov 2, 2022

See also this discussion on Zulip.

@ctrueden ctrueden changed the title Explore using java.nio direct byte buffer to wrap Python memory to Java, instead of Unsafe Use jpype.nio.convertToDirectBuffer to wrap Python memory to Java, instead of Unsafe Nov 2, 2022
@ctrueden
Copy link
Member Author

ctrueden commented Nov 2, 2022

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
Copy link
Member Author

ctrueden commented Nov 2, 2022

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

@ctrueden ctrueden added this to the unscheduled milestone Nov 2, 2022
@mkitti
Copy link
Member

mkitti commented Nov 2, 2022

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
Copy link

Thrameos commented Nov 2, 2022

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
Copy link
Member

mkitti commented Nov 2, 2022

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

@Thrameos
Copy link

Thrameos commented Nov 2, 2022

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
Copy link
Member Author

ctrueden commented Nov 2, 2022

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
Copy link

Thrameos commented Nov 2, 2022

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants