<div dir="ltr"><div><div><div><div><div><div><div><div><div><div>Phil,<br><br></div>I agree it is a complex issue to improve memory usage while maintaining performance at the JDK level: applications can use java2d pisces in very different contexts: Swing app (client with only EDT thread), server-side application (multi thread headless) ...<br>
<br></div>For the moment, I spent a lot of my time understanding the different classes in java2d.pisces and analyzing memory usage / performance ... using J2DBench (all graphics tests).<br><br></div><div>In my Swing application, pisces produces a lot of waste (GC) but on server side, the GC overhead can be more important if several threads use pisces.<br>
</div><div><br></div>Pisces uses memory differently:<br></div>- fixed arrays (dasher, stroker)<br></div>- dynamic arrays (edges ...) rowAARLE (very big one for big shapes)<br><br></div>For the moment I am trying to avoid memory waste (pooling or kept reference) without any memory constraint (no eviction) but I agree it is an important aspect for server-side applications.<br>
<br></div>To avoid concurrency issues, I use a ThreadLocal context named RendererContext to keep few temporary arrays (float6 and a BIG rowAARLE instance) but there is also dynamic IntArrayCache et FloatArrayCache which have several pools divided in buckets (256, 1024, 4096, 16384, 32768) containing only few instances.<br>
<br></div>To have best performance, I studied pisces code to clear only the used array parts when recycling or using dirty arrays (only clear rowAARLE[...][1]).<br><br></div>I think Andrea's proposal is interesting to maybe put some system properties to give hints (low memory footprint, use cache or not ...).<br>
<br></div><div><div><div><div><div><div><div><div><div><div><div><div><div class="gmail_extra"><div class="gmail_quote">2013/3/28 Phil Race <span dir="ltr"><<a href="mailto:philip.race@oracle.com" target="_blank">philip.race@oracle.com</a>></span><br>
<blockquote class="gmail_quote" style="margin:0pt 0pt 0pt 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">Maintaining a pool of objects might be an appropriate thing for an applications,<br>
but its a lot trickier for the platform as the application's usage pattern or intent<br>
is largely unknown. Weak references or soft references might be of use but<br>
weak references usually go away even at the next incremental GC and soft<br>
references tend to not go away at all until you run out of heap.<br></blockquote><div><br></div><div>Agreed; for the moment, pool eviction policy is not implemented but kept in mind.<br></div><div>FYI: each RendererContext (per thread) has its own array pools (not shared) that could have different caching policies:<br>
</div><div>For instance, AWT / EDT (repaint) could use a large cache although other threads do not use array caching at all.<br></div><div> </div><blockquote class="gmail_quote" style="margin:0pt 0pt 0pt 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">


You may well be right that always doubling the array size may be too simplistic,<br>
but it would need some analysis of the code and its usage to see how much<br>
better we can do.</blockquote><div><br></div><div>There is two part:<br></div><div>- initial array size for dynamic arrays: difficult to estimate but for now set to very low capacity (8 / 50 ...) to avoid memory waste for rectangle / line shapes. In my patch, I have defined MIN_ARRAY_SIZE = 128 (array pool) to avoid too much resizing as I am doing array recycling.<br>
</div><div>- grow: I use x4 instead of x2 to avoid array copies.<br></div><div> <br></div><div>Laurent<br></div><div><br></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div><div class="gmail_extra">
<br><br><div class="gmail_quote">2013/3/28 Phil Race <span dir="ltr"><<a href="mailto:philip.race@oracle.com" target="_blank">philip.race@oracle.com</a>></span><br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
Maintaining a pool of objects might be an appropriate thing for an applications,<br>
but its a lot trickier for the platform as the application's usage pattern or intent<br>
is largely unknown. Weak references or soft references might be of use but<br>
weak references usually go away even at the next incremental GC and soft<br>
references tend to not go away at all until you run out of heap.<br>
<br>
You may well be right that always doubling the array size may be too simplistic,<br>
but it would need some analysis of the code and its usage to see how much<br>
better we can do.<div class="im"><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></div>
I suppose there is some hotspot magic involved to recognise and intrinsify this<br>
method, since the source code looks like a plain old for loop.<span class="HOEnZb"><font color="#888888"><br>
<br>
-phil.</font></span><div class="HOEnZb"><div class="h5"><br>
<br>
<br>
On 3/26/2013 4:00 AM, Laurent Bourgès wrote:<br>
<blockquote class="gmail_quote" style="margin:0pt 0pt 0pt 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
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" target="_blank">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.<u></u>widenArray() performs a lot of array resizing / copy (Arrays.copyOf) that I want to avoid mostly:<br>
    // These use a hardcoded factor of 2 for increasing sizes. Perhaps this<br>
    // should be provided as an argument.<br>
    static float[] widenArray(float[] in, final int cursize, final int numToAdd) {<br>
        if (in.length >= cursize + numToAdd) {<br>
            return in;<br>
        }<br>
        return Arrays.copyOf(in, 2 * (cursize + numToAdd));<br>
    }<br>
<br>
    static int[] widenArray(int[] in, final int cursize, final int numToAdd) {<br>
        if (in.length >= cursize + numToAdd) {<br>
            return in;<br>
        }<br>
        return Arrays.copyOf(in, 2 * (cursize + numToAdd));<br>
    }<br>
<br>
Thanks to Peter Levart, I use its microbench tool (<a href="https://github.com/plevart/micro-bench/tree/v2" target="_blank">https://github.com/plevart/<u></u>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:  5 000 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:  5 000 ms, #of logical CPUS: 4<br>
#<br>
# Warm up:<br>
runTest[class ArrayCacheBenchmark$<u></u>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$<u></u>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$<u></u>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$<u></u>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:  5 000 ms, #of logical CPUS: 4<br>
#<br>
# Warm up:<br>
runTest[class ArrayCacheBenchmark$<u></u>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$<u></u>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$<u></u>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$<u></u>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:  5 000 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:  5 000 ms, #of logical CPUS: 4<br>
#<br>
# Warm up:<br>
runTest[class ArrayCacheBenchmark$<u></u>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$<u></u>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$<u></u>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$<u></u>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:  5 000 ms, #of logical CPUS: 4<br>
#<br>
# Warm up:<br>
runTest[class ArrayCacheBenchmark$<u></u>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$<u></u>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$<u></u>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$<u></u>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:  5 000 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 =  1 208,64 ns/op (σ =   0,00 ns/op) [ 1 208,64]<br>
runTest[class ArrayCacheBenchmark$ZeroFill] on JVM: 1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]<br>
           1 threads, Tavg =  1 238,01 ns/op (σ =   0,00 ns/op) [ 1 238,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 =  1 235,81 ns/op (σ =   0,00 ns/op) [ 1 235,81]<br>
runTest[class ArrayCacheBenchmark$ZeroFill] on JVM: 1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]<br>
           2 threads, Tavg =  1 325,11 ns/op (σ =   7,01 ns/op) [ 1 332,16,  1 318,14]<br>
<br>
#<br>
# FillArraySystemCopy: run duration:  5 000 ms, #of logical CPUS: 4<br>
#<br>
# Warm up:<br>
runTest[class ArrayCacheBenchmark$<u></u>FillArraySystemCopy] on JVM: 1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]<br>
           1 threads, Tavg =  1 930,93 ns/op (σ =   0,00 ns/op) [ 1 930,93]<br>
runTest[class ArrayCacheBenchmark$<u></u>FillArraySystemCopy] on JVM: 1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]<br>
           1 threads, Tavg =  2 060,80 ns/op (σ =   0,00 ns/op) [ 2 060,80]<br>
# Measure:<br>
runTest[class ArrayCacheBenchmark$<u></u>FillArraySystemCopy] on JVM: 1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]<br>
           1 threads, Tavg =  2 105,21 ns/op (σ =   0,00 ns/op) [ 2 105,21]<br>
runTest[class ArrayCacheBenchmark$<u></u>FillArraySystemCopy] on JVM: 1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]<br>
           2 threads, Tavg =  2 160,33 ns/op (σ =  13,74 ns/op) [ 2 146,68,  2 174,15]<br>
<br>
#<br>
# FillArrayUnsafe: run duration:  5 000 ms, #of logical CPUS: 4<br>
#<br>
# Warm up:<br>
runTest[class ArrayCacheBenchmark$<u></u>FillArrayUnsafe] on JVM: 1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]<br>
           1 threads, Tavg =  3 099,50 ns/op (σ =   0,00 ns/op) [ 3 099,50]<br>
runTest[class ArrayCacheBenchmark$<u></u>FillArrayUnsafe] on JVM: 1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]<br>
           1 threads, Tavg =  3 041,81 ns/op (σ =   0,00 ns/op) [ 3 041,81]<br>
# Measure:<br>
runTest[class ArrayCacheBenchmark$<u></u>FillArrayUnsafe] on JVM: 1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]<br>
           1 threads, Tavg =  3 068,34 ns/op (σ =   0,00 ns/op) [ 3 068,34]<br>
runTest[class ArrayCacheBenchmark$<u></u>FillArrayUnsafe] on JVM: 1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]<br>
           2 threads, Tavg =  3 296,13 ns/op (σ =  34,97 ns/op) [ 3 331,47,  3 261,53]<br>
<br>
<br>
PS: java.awt.geom.Path2D has also memory allocation issues:<br>
        void needRoom(boolean needMove, int newCoords) {<br>
            if (needMove && numTypes == 0) {<br>
throw new IllegalPathStateException("<u></u>missing initial moveto "+<br>
"in path definition");<br>
            }<br>
            int size = pointTypes.length;<br>
            if (numTypes >= size) {<br>
int grow = size;<br>
                if (grow > EXPAND_MAX) {<br>
grow = EXPAND_MAX;<br>
                }<br>
pointTypes = Arrays.copyOf(pointTypes, size+grow);<br>
            }<br>
            size = floatCoords.length;<br>
            if (numCoords + newCoords > size) {<br>
int grow = size;<br>
                if (grow > EXPAND_MAX * 2) {<br>
grow = EXPAND_MAX * 2;<br>
                }<br>
                if (grow < newCoords) {<br>
grow = newCoords;<br>
                }<br>
floatCoords = Arrays.copyOf(floatCoords, size+grow);<br>
            }<br>
        }<br>
<br>
Best regards,<br>
Laurent<br>
</blockquote>
<br>
</div></div></blockquote></div><br></div>