Dear all,<br><br>First I joined recently the openJDK contributors, and I plan to fix java2D pisces code in my spare time.<br><br>I have a full time job on Aspro2: <a href="http://www.jmmc.fr/aspro">http://www.jmmc.fr/aspro</a>; it is an application to prepare astronomical observations at VLTI / CHARA and is very used in our community (200 users): it provides scientific computations (observability, model images using complex numbers ...) and zoomable plots thanks to jFreeChart.<br>
<br>Aspro2 is known to be very efficient (computation parallelization) and I am often doing profiling using netbeans profiler or visualVM. <br><br>To fix huge memory usages by java2d.pisces, I started implementing an efficient ArrayCache (int[] and float[]) (in thread local to concurrency problems):<br>
- arrays in sizes between 10 and 10000 (more small arrays used than big ones)<br>- resizing support (Arrays.copyOf) without wasting arrays<br>- reentrance i.e. many arrays are used at the same time (java2D Pisces stroke / dash creates many segments to render)<br>
- GC / Heap friendly ie support cache eviction and avoid consuming too much memory<br><br>I know object pooling is known to be not efficient with recent VM (GC is better) but I think it is counter productive to create so many int[] arrays in java2d.pisces and let the GC remove such wasted memory.<br>
<br>Does someone have implemented such (open source) array cache (core-libs) ? <br>Opinions are welcome (but avoid "trolls").<br><br>Moreover, sun.java2d.pisces.Helpers.widenArray() performs a lot of array resizing / copy (Arrays.copyOf) that I want to avoid mostly:<br>
<span style="font-family:courier new,monospace"> // These use a hardcoded factor of 2 for increasing sizes. Perhaps this</span><br style="font-family:courier new,monospace"><span style="font-family:courier new,monospace"> // should be provided as an argument.</span><br style="font-family:courier new,monospace">
<span style="font-family:courier new,monospace"> static float[] widenArray(float[] in, final int cursize, final int numToAdd) {</span><br style="font-family:courier new,monospace"><span style="font-family:courier new,monospace"> if (in.length >= cursize + numToAdd) {</span><br style="font-family:courier new,monospace">
<span style="font-family:courier new,monospace"> return in;</span><br style="font-family:courier new,monospace"><span style="font-family:courier new,monospace"> }</span><br style="font-family:courier new,monospace">
<span style="font-family:courier new,monospace"> return Arrays.copyOf(in, 2 * (cursize + numToAdd));</span><br style="font-family:courier new,monospace"><span style="font-family:courier new,monospace"> }</span><br style="font-family:courier new,monospace">
<br style="font-family:courier new,monospace"><span style="font-family:courier new,monospace"> static int[] widenArray(int[] in, final int cursize, final int numToAdd) {</span><br style="font-family:courier new,monospace">
<span style="font-family:courier new,monospace"> if (in.length >= cursize + numToAdd) {</span><br style="font-family:courier new,monospace"><span style="font-family:courier new,monospace"> return in;</span><br style="font-family:courier new,monospace">
<span style="font-family:courier new,monospace"> }</span><br style="font-family:courier new,monospace"><span style="font-family:courier new,monospace"> return Arrays.copyOf(in, 2 * (cursize + numToAdd));</span><br style="font-family:courier new,monospace">
<span style="font-family:courier new,monospace"> }</span><br style="font-family:courier new,monospace"><br>Thanks to Peter Levart, I use its microbench tool (<a href="https://github.com/plevart/micro-bench/tree/v2">https://github.com/plevart/micro-bench/tree/v2</a>) to benchmark ArrayCache operations... and J2DBench to test java2d performances<br>
<br>What is the fastest way to clear an array (part) ie fill by 0:<br>- public static void fill(int[] a, int fromIndex, int toIndex, int val)<br>- public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);<br>
- unsafe.setMemory(array, Unsafe.ARRAY_INT_BASE_OFFSET, 512 * SIZEOF_INT, (byte) 0)<br><br>Apparently, Arrays.fill is always faster (size in 10 ... 10 000) ! <br>I suspect hotspot to optimize its code and use native functions, isn't it ???<br>
<br>Benchmarks results:<br>>> JVM START: 1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]<br>Testing arrays: int[1]...<br>#<br># ZeroFill: run duration: 5000 ms, #of logical CPUS: 4<br>#<br># Warm up:<br>runTest[class ArrayCacheBenchmark$ZeroFill] on JVM: 1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]<br>
 1 threads, Tavg = 4,47 ns/op ( = 0,00 ns/op) [ 4,47]<br>runTest[class ArrayCacheBenchmark$ZeroFill] on JVM: 1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]<br> 1 threads, Tavg = 4,40 ns/op ( = 0,00 ns/op) [ 4,40]<br>
# Measure:<br>runTest[class ArrayCacheBenchmark$ZeroFill] on JVM: 1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]<br> 1 threads, Tavg = 4,43 ns/op ( = 0,00 ns/op) [ 4,43]<br>runTest[class ArrayCacheBenchmark$ZeroFill] on JVM: 1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]<br>
 2 threads, Tavg = 5,55 ns/op ( = 0,16 ns/op) [ 5,40, 5,72]<br><br>#<br># FillArraySystemCopy: run duration: 5000 ms, #of logical CPUS: 4<br>#<br># Warm up:<br>runTest[class ArrayCacheBenchmark$FillArraySystemCopy] on JVM: 1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]<br>
 1 threads, Tavg = 6,47 ns/op ( = 0,00 ns/op) [ 6,47]<br>runTest[class ArrayCacheBenchmark$FillArraySystemCopy] on JVM: 1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]<br> 1 threads, Tavg = 6,21 ns/op ( = 0,00 ns/op) [ 6,21]<br>
# Measure:<br>runTest[class ArrayCacheBenchmark$FillArraySystemCopy] on JVM: 1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]<br> 1 threads, Tavg = 6,19 ns/op ( = 0,00 ns/op) [ 6,19]<br>runTest[class ArrayCacheBenchmark$FillArraySystemCopy] on JVM: 1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]<br>
 2 threads, Tavg = 7,80 ns/op ( = 0,10 ns/op) [ 7,90, 7,71]<br><br>#<br># FillArrayUnsafe: run duration: 5000 ms, #of logical CPUS: 4<br>#<br># Warm up:<br>runTest[class ArrayCacheBenchmark$FillArrayUnsafe] on JVM: 1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]<br>
 1 threads, Tavg = 26,82 ns/op ( = 0,00 ns/op) [ 26,82]<br>runTest[class ArrayCacheBenchmark$FillArrayUnsafe] on JVM: 1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]<br> 1 threads, Tavg = 23,48 ns/op ( = 0,00 ns/op) [ 23,48]<br>
# Measure:<br>runTest[class ArrayCacheBenchmark$FillArrayUnsafe] on JVM: 1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]<br> 1 threads, Tavg = 22,42 ns/op ( = 0,00 ns/op) [ 22,42]<br>runTest[class ArrayCacheBenchmark$FillArrayUnsafe] on JVM: 1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]<br>
 2 threads, Tavg = 28,21 ns/op ( = 0,88 ns/op) [ 29,11, 27,36]<br><br>Testing arrays: int[100]...<br>#<br># ZeroFill: run duration: 5000 ms, #of logical CPUS: 4<br>#<br># Warm up:<br>runTest[class ArrayCacheBenchmark$ZeroFill] on JVM: 1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]<br>
 1 threads, Tavg = 16,49 ns/op ( = 0,00 ns/op) [ 16,49]<br>runTest[class ArrayCacheBenchmark$ZeroFill] on JVM: 1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]<br> 1 threads, Tavg = 15,97 ns/op ( = 0,00 ns/op) [ 15,97]<br>
# Measure:<br>runTest[class ArrayCacheBenchmark$ZeroFill] on JVM: 1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]<br> 1 threads, Tavg = 16,03 ns/op ( = 0,00 ns/op) [ 16,03]<br>runTest[class ArrayCacheBenchmark$ZeroFill] on JVM: 1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]<br>
 2 threads, Tavg = 19,32 ns/op ( = 0,46 ns/op) [ 18,87, 19,80]<br><br>#<br># FillArraySystemCopy: run duration: 5000 ms, #of logical CPUS: 4<br>#<br># Warm up:<br>runTest[class ArrayCacheBenchmark$FillArraySystemCopy] on JVM: 1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]<br>
 1 threads, Tavg = 14,51 ns/op ( = 0,00 ns/op) [ 14,51]<br>runTest[class ArrayCacheBenchmark$FillArraySystemCopy] on JVM: 1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]<br> 1 threads, Tavg = 14,17 ns/op ( = 0,00 ns/op) [ 14,17]<br>
# Measure:<br>runTest[class ArrayCacheBenchmark$FillArraySystemCopy] on JVM: 1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]<br> 1 threads, Tavg = 14,09 ns/op ( = 0,00 ns/op) [ 14,09]<br>runTest[class ArrayCacheBenchmark$FillArraySystemCopy] on JVM: 1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]<br>
 2 threads, Tavg = 31,15 ns/op ( = 4,04 ns/op) [ 27,65, 35,67]<br><br>#<br># FillArrayUnsafe: run duration: 5000 ms, #of logical CPUS: 4<br>#<br># Warm up:<br>runTest[class ArrayCacheBenchmark$FillArrayUnsafe] on JVM: 1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]<br>
 1 threads, Tavg = 52,32 ns/op ( = 0,00 ns/op) [ 52,32]<br>runTest[class ArrayCacheBenchmark$FillArrayUnsafe] on JVM: 1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]<br> 1 threads, Tavg = 52,82 ns/op ( = 0,00 ns/op) [ 52,82]<br>
# Measure:<br>runTest[class ArrayCacheBenchmark$FillArrayUnsafe] on JVM: 1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]<br> 1 threads, Tavg = 52,19 ns/op ( = 0,00 ns/op) [ 52,19]<br>runTest[class ArrayCacheBenchmark$FillArrayUnsafe] on JVM: 1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]<br>
 2 threads, Tavg = 70,87 ns/op ( = 0,71 ns/op) [ 70,17, 71,59]<br><br>Testing arrays: int[10000]...<br>#<br># ZeroFill: run duration: 5000 ms, #of logical CPUS: 4<br>#<br># Warm up:<br>runTest[class ArrayCacheBenchmark$ZeroFill] on JVM: 1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]<br>
 1 threads, Tavg = 1208,64 ns/op ( = 0,00 ns/op) [ 1208,64]<br>runTest[class ArrayCacheBenchmark$ZeroFill] on JVM: 1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]<br> 1 threads, Tavg = 1238,01 ns/op ( = 0,00 ns/op) [ 1238,01]<br>
# Measure:<br>runTest[class ArrayCacheBenchmark$ZeroFill] on JVM: 1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]<br> 1 threads, Tavg = 1235,81 ns/op ( = 0,00 ns/op) [ 1235,81]<br>runTest[class ArrayCacheBenchmark$ZeroFill] on JVM: 1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]<br>
 2 threads, Tavg = 1325,11 ns/op ( = 7,01 ns/op) [ 1332,16, 1318,14]<br><br>#<br># FillArraySystemCopy: run duration: 5000 ms, #of logical CPUS: 4<br>#<br># Warm up:<br>runTest[class ArrayCacheBenchmark$FillArraySystemCopy] on JVM: 1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]<br>
 1 threads, Tavg = 1930,93 ns/op ( = 0,00 ns/op) [ 1930,93]<br>runTest[class ArrayCacheBenchmark$FillArraySystemCopy] on JVM: 1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]<br> 1 threads, Tavg = 2060,80 ns/op ( = 0,00 ns/op) [ 2060,80]<br>
# Measure:<br>runTest[class ArrayCacheBenchmark$FillArraySystemCopy] on JVM: 1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]<br> 1 threads, Tavg = 2105,21 ns/op ( = 0,00 ns/op) [ 2105,21]<br>runTest[class ArrayCacheBenchmark$FillArraySystemCopy] on JVM: 1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]<br>
 2 threads, Tavg = 2160,33 ns/op ( = 13,74 ns/op) [ 2146,68, 2174,15]<br><br>#<br># FillArrayUnsafe: run duration: 5000 ms, #of logical CPUS: 4<br>#<br># Warm up:<br>runTest[class ArrayCacheBenchmark$FillArrayUnsafe] on JVM: 1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]<br>
 1 threads, Tavg = 3099,50 ns/op ( = 0,00 ns/op) [ 3099,50]<br>runTest[class ArrayCacheBenchmark$FillArrayUnsafe] on JVM: 1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]<br> 1 threads, Tavg = 3041,81 ns/op ( = 0,00 ns/op) [ 3041,81]<br>
# Measure:<br>runTest[class ArrayCacheBenchmark$FillArrayUnsafe] on JVM: 1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]<br> 1 threads, Tavg = 3068,34 ns/op ( = 0,00 ns/op) [ 3068,34]<br>runTest[class ArrayCacheBenchmark$FillArrayUnsafe] on JVM: 1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]<br>
 2 threads, Tavg = 3296,13 ns/op ( = 34,97 ns/op) [ 3331,47, 3261,53]<br><br><br>PS: java.awt.geom.Path2D has also memory allocation issues:<br><span style="font-family:courier new,monospace"> void needRoom(boolean needMove, int newCoords) {</span><br style="font-family:courier new,monospace">
<span style="font-family:courier new,monospace"> if (needMove && numTypes == 0) {</span><br style="font-family:courier new,monospace"><span style="font-family:courier new,monospace"> throw new IllegalPathStateException("missing initial moveto "+</span><br style="font-family:courier new,monospace">
<span style="font-family:courier new,monospace"> "in path definition");</span><br style="font-family:courier new,monospace"><span style="font-family:courier new,monospace"> }</span><br style="font-family:courier new,monospace">
<span style="font-family:courier new,monospace"> int size = pointTypes.length;</span><br style="font-family:courier new,monospace"><span style="font-family:courier new,monospace"> if (numTypes >= size) {</span><br style="font-family:courier new,monospace">
<span style="font-family:courier new,monospace"> int grow = size;</span><br style="font-family:courier new,monospace"><span style="font-family:courier new,monospace"> if (grow > EXPAND_MAX) {</span><br style="font-family:courier new,monospace">
<span style="font-family:courier new,monospace"> grow = EXPAND_MAX;</span><br style="font-family:courier new,monospace"><span style="font-family:courier new,monospace"> }</span><br style="font-family:courier new,monospace">
<span style="font-family:courier new,monospace"> pointTypes = Arrays.copyOf(pointTypes, size+grow);</span><br style="font-family:courier new,monospace"><span style="font-family:courier new,monospace"> }</span><br style="font-family:courier new,monospace">
<span style="font-family:courier new,monospace"> size = floatCoords.length;</span><br style="font-family:courier new,monospace"><span style="font-family:courier new,monospace"> if (numCoords + newCoords > size) {</span><br style="font-family:courier new,monospace">
<span style="font-family:courier new,monospace"> int grow = size;</span><br style="font-family:courier new,monospace"><span style="font-family:courier new,monospace"> if (grow > EXPAND_MAX * 2) {</span><br style="font-family:courier new,monospace">
<span style="font-family:courier new,monospace"> grow = EXPAND_MAX * 2;</span><br style="font-family:courier new,monospace"><span style="font-family:courier new,monospace"> }</span><br style="font-family:courier new,monospace">
<span style="font-family:courier new,monospace"> if (grow < newCoords) {</span><br style="font-family:courier new,monospace"><span style="font-family:courier new,monospace"> grow = newCoords;</span><br style="font-family:courier new,monospace">
<span style="font-family:courier new,monospace"> }</span><br style="font-family:courier new,monospace"><span style="font-family:courier new,monospace"> floatCoords = Arrays.copyOf(floatCoords, size+grow);</span><br style="font-family:courier new,monospace">
<span style="font-family:courier new,monospace"> }</span><br style="font-family:courier new,monospace"><span style="font-family:courier new,monospace"> }</span><br style="font-family:courier new,monospace">
<br>Best regards,<br>Laurent<br>