Dear all,
First I joined recently the openJDK contributors, and I plan to fix
java2D pisces code in my spare time.
I have a full time job on Aspro2: http://www.jmmc.fr/aspro; 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.
Aspro2 is known to be very efficient (computation parallelization) and I
am often doing profiling using netbeans profiler or visualVM.
To fix huge memory usages by java2d.pisces, I started implementing an
efficient ArrayCache (int[] and float[]) (in thread local to concurrency
problems):
- arrays in sizes between 10 and 10000 (more small arrays used than big
ones)
- resizing support (Arrays.copyOf) without wasting arrays
- reentrance i.e. many arrays are used at the same time (java2D Pisces
stroke / dash creates many segments to render)
- GC / Heap friendly ie support cache eviction and avoid consuming too
much memory
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.
Does someone have implemented such (open source) array cache (core-libs) ?
Opinions are welcome (but avoid "trolls").
Moreover, sun.java2d.pisces.Helpers.**widenArray() performs a lot of
array resizing / copy (Arrays.copyOf) that I want to avoid mostly:
// These use a hardcoded factor of 2 for increasing sizes. Perhaps
this
// should be provided as an argument.
static float[] widenArray(float[] in, final int cursize, final int
numToAdd) {
if (in.length >= cursize + numToAdd) {
return in;
}
return Arrays.copyOf(in, 2 * (cursize + numToAdd));
}
static int[] widenArray(int[] in, final int cursize, final int
numToAdd) {
if (in.length >= cursize + numToAdd) {
return in;
}
return Arrays.copyOf(in, 2 * (cursize + numToAdd));
}
Thanks to Peter Levart, I use its microbench tool (
https://github.com/plevart/**micro-bench/tree/v2<https://github.com/plevart/micro-bench/tree/v2>)
to benchmark ArrayCache operations... and J2DBench to test java2d
performances
What is the fastest way to clear an array (part) ie fill by 0:
- public static void fill(int[] a, int fromIndex, int toIndex, int val)
- public static native void arraycopy(Object src, int srcPos, Object
dest, int destPos, int length);
- unsafe.setMemory(array, Unsafe.ARRAY_INT_BASE_OFFSET, 512 * SIZEOF_INT,
(byte) 0)
Apparently, Arrays.fill is always faster (size in 10 ... 10 000) !
I suspect hotspot to optimize its code and use native functions, isn't it
???
Benchmarks results:
JVM START: 1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]
Testing arrays: int[1]...
#
# ZeroFill: run duration: 5 000 ms, #of logical CPUS: 4
#
# Warm up:
runTest[class ArrayCacheBenchmark$ZeroFill] on JVM: 1.8.0-internal
[OpenJDK 64-Bit Server VM 25.0-b22]
1 threads, Tavg = 4,47 ns/op (σ = 0,00 ns/op) [
4,47]
runTest[class ArrayCacheBenchmark$ZeroFill] on JVM: 1.8.0-internal
[OpenJDK 64-Bit Server VM 25.0-b22]
1 threads, Tavg = 4,40 ns/op (σ = 0,00 ns/op) [
4,40]
# Measure:
runTest[class ArrayCacheBenchmark$ZeroFill] on JVM: 1.8.0-internal
[OpenJDK 64-Bit Server VM 25.0-b22]
1 threads, Tavg = 4,43 ns/op (σ = 0,00 ns/op) [
4,43]
runTest[class ArrayCacheBenchmark$ZeroFill] on JVM: 1.8.0-internal
[OpenJDK 64-Bit Server VM 25.0-b22]
2 threads, Tavg = 5,55 ns/op (σ = 0,16 ns/op) [
5,40, 5,72]
#
# FillArraySystemCopy: run duration: 5 000 ms, #of logical CPUS: 4
#
# Warm up:
runTest[class ArrayCacheBenchmark$**FillArraySystemCopy] on JVM:
1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]
1 threads, Tavg = 6,47 ns/op (σ = 0,00 ns/op) [
6,47]
runTest[class ArrayCacheBenchmark$**FillArraySystemCopy] on JVM:
1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]
1 threads, Tavg = 6,21 ns/op (σ = 0,00 ns/op) [
6,21]
# Measure:
runTest[class ArrayCacheBenchmark$**FillArraySystemCopy] on JVM:
1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]
1 threads, Tavg = 6,19 ns/op (σ = 0,00 ns/op) [
6,19]
runTest[class ArrayCacheBenchmark$**FillArraySystemCopy] on JVM:
1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]
2 threads, Tavg = 7,80 ns/op (σ = 0,10 ns/op) [
7,90, 7,71]
#
# FillArrayUnsafe: run duration: 5 000 ms, #of logical CPUS: 4
#
# Warm up:
runTest[class ArrayCacheBenchmark$**FillArrayUnsafe] on JVM:
1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]
1 threads, Tavg = 26,82 ns/op (σ = 0,00 ns/op) [
26,82]
runTest[class ArrayCacheBenchmark$**FillArrayUnsafe] on JVM:
1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]
1 threads, Tavg = 23,48 ns/op (σ = 0,00 ns/op) [
23,48]
# Measure:
runTest[class ArrayCacheBenchmark$**FillArrayUnsafe] on JVM:
1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]
1 threads, Tavg = 22,42 ns/op (σ = 0,00 ns/op) [
22,42]
runTest[class ArrayCacheBenchmark$**FillArrayUnsafe] on JVM:
1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]
2 threads, Tavg = 28,21 ns/op (σ = 0,88 ns/op) [
29,11, 27,36]
Testing arrays: int[100]...
#
# ZeroFill: run duration: 5 000 ms, #of logical CPUS: 4
#
# Warm up:
runTest[class ArrayCacheBenchmark$ZeroFill] on JVM: 1.8.0-internal
[OpenJDK 64-Bit Server VM 25.0-b22]
1 threads, Tavg = 16,49 ns/op (σ = 0,00 ns/op) [
16,49]
runTest[class ArrayCacheBenchmark$ZeroFill] on JVM: 1.8.0-internal
[OpenJDK 64-Bit Server VM 25.0-b22]
1 threads, Tavg = 15,97 ns/op (σ = 0,00 ns/op) [
15,97]
# Measure:
runTest[class ArrayCacheBenchmark$ZeroFill] on JVM: 1.8.0-internal
[OpenJDK 64-Bit Server VM 25.0-b22]
1 threads, Tavg = 16,03 ns/op (σ = 0,00 ns/op) [
16,03]
runTest[class ArrayCacheBenchmark$ZeroFill] on JVM: 1.8.0-internal
[OpenJDK 64-Bit Server VM 25.0-b22]
2 threads, Tavg = 19,32 ns/op (σ = 0,46 ns/op) [
18,87, 19,80]
#
# FillArraySystemCopy: run duration: 5 000 ms, #of logical CPUS: 4
#
# Warm up:
runTest[class ArrayCacheBenchmark$**FillArraySystemCopy] on JVM:
1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]
1 threads, Tavg = 14,51 ns/op (σ = 0,00 ns/op) [
14,51]
runTest[class ArrayCacheBenchmark$**FillArraySystemCopy] on JVM:
1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]
1 threads, Tavg = 14,17 ns/op (σ = 0,00 ns/op) [
14,17]
# Measure:
runTest[class ArrayCacheBenchmark$**FillArraySystemCopy] on JVM:
1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]
1 threads, Tavg = 14,09 ns/op (σ = 0,00 ns/op) [
14,09]
runTest[class ArrayCacheBenchmark$**FillArraySystemCopy] on JVM:
1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]
2 threads, Tavg = 31,15 ns/op (σ = 4,04 ns/op) [
27,65, 35,67]
#
# FillArrayUnsafe: run duration: 5 000 ms, #of logical CPUS: 4
#
# Warm up:
runTest[class ArrayCacheBenchmark$**FillArrayUnsafe] on JVM:
1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]
1 threads, Tavg = 52,32 ns/op (σ = 0,00 ns/op) [
52,32]
runTest[class ArrayCacheBenchmark$**FillArrayUnsafe] on JVM:
1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]
1 threads, Tavg = 52,82 ns/op (σ = 0,00 ns/op) [
52,82]
# Measure:
runTest[class ArrayCacheBenchmark$**FillArrayUnsafe] on JVM:
1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]
1 threads, Tavg = 52,19 ns/op (σ = 0,00 ns/op) [
52,19]
runTest[class ArrayCacheBenchmark$**FillArrayUnsafe] on JVM:
1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]
2 threads, Tavg = 70,87 ns/op (σ = 0,71 ns/op) [
70,17, 71,59]
Testing arrays: int[10000]...
#
# ZeroFill: run duration: 5 000 ms, #of logical CPUS: 4
#
# Warm up:
runTest[class ArrayCacheBenchmark$ZeroFill] on JVM: 1.8.0-internal
[OpenJDK 64-Bit Server VM 25.0-b22]
1 threads, Tavg = 1 208,64 ns/op (σ = 0,00 ns/op) [ 1
208,64]
runTest[class ArrayCacheBenchmark$ZeroFill] on JVM: 1.8.0-internal
[OpenJDK 64-Bit Server VM 25.0-b22]
1 threads, Tavg = 1 238,01 ns/op (σ = 0,00 ns/op) [ 1
238,01]
# Measure:
runTest[class ArrayCacheBenchmark$ZeroFill] on JVM: 1.8.0-internal
[OpenJDK 64-Bit Server VM 25.0-b22]
1 threads, Tavg = 1 235,81 ns/op (σ = 0,00 ns/op) [ 1
235,81]
runTest[class ArrayCacheBenchmark$ZeroFill] on JVM: 1.8.0-internal
[OpenJDK 64-Bit Server VM 25.0-b22]
2 threads, Tavg = 1 325,11 ns/op (σ = 7,01 ns/op) [ 1
332,16, 1 318,14]
#
# FillArraySystemCopy: run duration: 5 000 ms, #of logical CPUS: 4
#
# Warm up:
runTest[class ArrayCacheBenchmark$**FillArraySystemCopy] on JVM:
1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]
1 threads, Tavg = 1 930,93 ns/op (σ = 0,00 ns/op) [ 1
930,93]
runTest[class ArrayCacheBenchmark$**FillArraySystemCopy] on JVM:
1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]
1 threads, Tavg = 2 060,80 ns/op (σ = 0,00 ns/op) [ 2
060,80]
# Measure:
runTest[class ArrayCacheBenchmark$**FillArraySystemCopy] on JVM:
1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]
1 threads, Tavg = 2 105,21 ns/op (σ = 0,00 ns/op) [ 2
105,21]
runTest[class ArrayCacheBenchmark$**FillArraySystemCopy] on JVM:
1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]
2 threads, Tavg = 2 160,33 ns/op (σ = 13,74 ns/op) [ 2
146,68, 2 174,15]
#
# FillArrayUnsafe: run duration: 5 000 ms, #of logical CPUS: 4
#
# Warm up:
runTest[class ArrayCacheBenchmark$**FillArrayUnsafe] on JVM:
1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]
1 threads, Tavg = 3 099,50 ns/op (σ = 0,00 ns/op) [ 3
099,50]
runTest[class ArrayCacheBenchmark$**FillArrayUnsafe] on JVM:
1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]
1 threads, Tavg = 3 041,81 ns/op (σ = 0,00 ns/op) [ 3
041,81]
# Measure:
runTest[class ArrayCacheBenchmark$**FillArrayUnsafe] on JVM:
1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]
1 threads, Tavg = 3 068,34 ns/op (σ = 0,00 ns/op) [ 3
068,34]
runTest[class ArrayCacheBenchmark$**FillArrayUnsafe] on JVM:
1.8.0-internal [OpenJDK 64-Bit Server VM 25.0-b22]
2 threads, Tavg = 3 296,13 ns/op (σ = 34,97 ns/op) [ 3
331,47, 3 261,53]
PS: java.awt.geom.Path2D has also memory allocation issues:
void needRoom(boolean needMove, int newCoords) {
if (needMove && numTypes == 0) {
throw new IllegalPathStateException("**missing initial moveto "+
"in path definition");
}
int size = pointTypes.length;
if (numTypes >= size) {
int grow = size;
if (grow > EXPAND_MAX) {
grow = EXPAND_MAX;
}
pointTypes = Arrays.copyOf(pointTypes, size+grow);
}
size = floatCoords.length;
if (numCoords + newCoords > size) {
int grow = size;
if (grow > EXPAND_MAX * 2) {
grow = EXPAND_MAX * 2;
}
if (grow < newCoords) {
grow = newCoords;
}
floatCoords = Arrays.copyOf(floatCoords, size+grow);
}
}
Best regards,
Laurent