[ImageJ-devel] SCIFIO via Avian

Tobias Pietzsch pietzsch at mpi-cbg.de
Wed Dec 4 18:35:57 CST 2013


Hi guys,

I made a benchmark for ArrayImg backed by
- primitive arrays
- direct buffers
- sun.misc.Unsafe (off-heap memory)
https://github.com/imagej/imglib/blob/2e1929873e2f4bafcfe9d90ad616fedb8b7ed4f0/tests/src/test/java/net/imglib2/img/BufferAndUnsafeBenchmark.java
(in branch "buffer-and-unsafe")

On my computer, the result is that Unsafe is the about the same speed as primitive arrays. Direct buffers are consistently slower:

copy image using class net.imglib2.img.array.ArrayImgFactory
median: 52 ms

copy image using class net.imglib2.img.array.ArrayImgUnsafeFactory
median: 52 ms

copy image using class net.imglib2.img.array.ArrayImgBufferFactory
median: 59 ms

The not-so-good news is that using the same type backed by different accesses will make the calls into the ByteAccess (or whatever the type is) polymorphic, spoiling it for the JIT (at least if both are used from the same call-site).
This is visible in the second part of the benchmark. Here, a constant UnsignedByteType is added to every pixel of the image. Because the new UnsignedByteType(1) is always backed by a primitive array, we hit the bimorphic case for Unsafe and direct buffer, and this results in a big slowdown:

add constant to image using class net.imglib2.img.array.ArrayImgFactory
median: 22 ms

add constant to image using class net.imglib2.img.array.ArrayImgUnsafeFactory
median: 122 ms

add constant to image using class net.imglib2.img.array.ArrayImgBufferFactory
median: 186 ms

We can try some tricks to make UnsignedByteType constants backed by the other ByteAccess types, but in general I see no easy way around this.
Maybe someone has a few months to spare to make imglib3 using ASM or Javassist to manually inline this stuff (that would solve the bottleneck in making a proper bidirectional read/write type hierarchy…) Of course, we can always hope that the JIT will "get it" at some point in the future, but I think this is quite a pathological case.

I think, we should go for the sun.misc.Unsafe for now.
If it really turns out to be a show-stopper, we will have to look into using primitive arrays and pinning. One downside of that would be that then the memory must be owned by Java, so it would be a problem for the Avian integration. Another downside is that JNI implementations are not forced to pin the array, they may just decide to copy the data.

I also made a imglib2-vigra branch that uses Unsafe https://github.com/tpietzsch/vigra-imglib2/commits/unsafe (requires the buffer-and-unsafe branch of imglib2).

best regards,
Tobias

On Nov 30, 2013, at 12:36 AM, Tobias Pietzsch <pietzsch at mpi-cbg.de> wrote:

> Hi Johannes and Ulli,
> 
> with respect to the need to set up the paths in the pom.xml:
> This is an issue with the nar-maven-plugin, which compiles and packages the native code.
> With Johannes' help, I submitted an integration test to the nar-maven-plugin project, that illustrates this problem. So hopefully it will be fixed at some point in the future.
> Ulli, I remember that there was another issue on Windows: The dll was named differently to what the NarHelper class expects. Could you elaborate on that?
> 
> With respect to what we did on the vigra-imglib2 project. I forked your project, Johannes, https://github.com/tpietzsch/vigra-imglib2.
> We worked on the "buffer" branch.
> This is using direct buffers as the underlying data structure for both ImgLib2's ArrayImg and VIGRA's MultiArrayView. At the moment, the direct buffer is allocated on the Java side, but as you pointed out, one can use JNI's NewDirectByteBuffer() from the C++ side. I think this is a good solution, because it leaves the option for both scenarios, Java embedded in C++ and C++ embedded in Java. The ByteBuffer is wrapped in a ImgLib2 Access (e.g. IntAccess for Imgs of IntType. It is easy to provide two constructors there, one which allocates the buffer and one which takes an existing buffer. Responsibility for freeing the memory is not a problem. Note that JNI NewDirectByteBuffer is constructed with already allocated memory. So, if C++ allocated the memory, Java may garbage-collect the ByteBuffer, but will not free the memory block. If creating the ByteBuffer form Java, Java will also free the memory.
> The good thing is that both ImgLib ArrayImgs and VIGRA MultiArrayViews were designed to wrap flat arrays, which is what is happening here.
> 
> The ByteBuffer code came from the ImgLib2 branch "buffer-and-unsafe", where I played with using direct buffers and sun.misc.Unsafe to back ArrayImgs instead of Java primitive arrays.
> sun.misc.Unsafe is the other viable option. In contrast to primitive arrays and direct buffers it does not suffer from the 2G size limit of Java arrays. Of course, if we put it behind an ArrayImg, we cannot make use of that fact yet. So we could have a BigArrayImg at some point in the future (which would be useful in its own right). Otherwise, same advantages as explained above.
> The benchmark that Johannes mentioned was comparing the speed of ArrayImgs backed by primitive arrays, direct buffers, and sun.misc.Unsafe, respectively. I just had a look and I couldn't find it. I'll recreate it next week.
> 
> Note, that I copied the byte buffer stuff from the ImgLib2 branch "buffer-and-unsafe", so that vigra-imglib2 works with the current ImgLib2 beta.
> 
> But wrapping the images back and forth was only part of what we did. An important point is to make it easy to make bindings to VIGRA functions withou writing lots of boilderplate code on either side of JNI. Based on earlier VIGRA-Matlab wrapper we made some macros that allow to write on the C++ side
> 
> JNIEXPORT void JNICALL Java_net_imglib2_vigra_VigraWrapper_gaussianSmoothMultiArray
>   (JNIEnv *env, jclass, jlongArray shape, jint typeId, jobject sourceData, jobject destData, jdouble sigma)
> {
>     using namespace vigra; // to get UInt8 and Int32
>     #define F(T) gaussianSmoothMultiArray<T>(env, shape, typeId, sourceData, destData, sigma)
>     ALLOW_TYPES(typeId, UInt8, Int32, float)
>     #undef F
> }
> 
> This creates a switch statement that instantiates the VIGRA template function for the specified C++ types and dispatches to these according to the corresponding ImgLib type.
> The same is done for supported dimensionalities (because with templates we have to specify that at compile time).
> 
> The plan is to directly pass in the ArrayImg jobject (and maybe even views later) and extract the type and dimension etc directly from that.
> This would mean, that on the Java side, we only have one native method for each exported VIGRA function, basically with the same signature and just replacing MultiArrayView with Img.
> 
> best regards,
> Tobias
> 
> _______________________________________________
> ImageJ-devel mailing list
> ImageJ-devel at imagej.net
> http://imagej.net/mailman/listinfo/imagej-devel

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://imagej.net/pipermail/imagej-devel/attachments/20131205/34d4bdeb/attachment-0001.html>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 455 bytes
Desc: Message signed with OpenPGP using GPGMail
URL: <http://imagej.net/pipermail/imagej-devel/attachments/20131205/34d4bdeb/attachment-0001.pgp>


More information about the ImageJ-devel mailing list