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 <[email protected]> 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
> [email protected]
> http://imagej.net/mailman/listinfo/imagej-devel

Attachment: signature.asc
Description: Message signed with OpenPGP using GPGMail

_______________________________________________
ImageJ-devel mailing list
[email protected]
http://imagej.net/mailman/listinfo/imagej-devel

Reply via email to