2015-09-29 21 views
14

Sto giocando con JMH (http://openjdk.java.net/projects/code-tools/jmh/) e mi sono imbattuto in uno strano risultato.System.arraycopy con lunghezza costante

Sto analisi comparativa modi per fare una copia di un array e posso osservare i risultati attesi (che scorrendo l'array è una cattiva idea e che non v'è alcuna differenza significativa tra #clone(), System#arraycopy() e Arrays#copyOf(), alla performance saggio).

Tranne che lo System#arraycopy() è un quarto più lento quando la lunghezza dell'array è hardcoded ... Aspetta, cosa? Come può essere più lento?

Qualcuno ha un'idea di quale potrebbe essere la causa?

I risultati (di throughput):

# JMH 1.11 (released 17 days ago) 
# VM version: JDK 1.8.0_05, VM 25.5-b02 
# VM invoker: /Library/Java/JavaVirtualMachines/jdk1.8.0_05.jdk/Contents/Home/jre/bin/java 
# VM options: -Dfile.encoding=UTF-8 -Duser.country=FR -Duser.language=fr -Duser.variant 
# Warmup: 20 iterations, 1 s each 
# Measurement: 20 iterations, 1 s each 
# Timeout: 10 min per iteration 
# Threads: 1 thread, will synchronize iterations 
# Benchmark mode: Throughput, ops/time 

Benchmark           Mode Cnt   Score   Error Units 
ArrayCopyBenchmark.ArraysCopyOf      thrpt 20 67100500,319 ± 455252,537 ops/s 
ArrayCopyBenchmark.ArraysCopyOf_Class    thrpt 20 65246374,290 ± 976481,330 ops/s 
ArrayCopyBenchmark.ArraysCopyOf_Class_ConstantSize thrpt 20 65068143,162 ± 1597390,531 ops/s 
ArrayCopyBenchmark.ArraysCopyOf_ConstantSize  thrpt 20 64463603,462 ± 953946,811 ops/s 
ArrayCopyBenchmark.Clone       thrpt 20 64837239,393 ± 834353,404 ops/s 
ArrayCopyBenchmark.Loop        thrpt 20 21070422,097 ± 112595,764 ops/s 
ArrayCopyBenchmark.Loop_ConstantSize    thrpt 20 24458867,274 ± 181486,291 ops/s 
ArrayCopyBenchmark.SystemArrayCopy     thrpt 20 66688368,490 ± 582416,954 ops/s 
ArrayCopyBenchmark.SystemArrayCopy_ConstantSize  thrpt 20 48992312,357 ± 298807,039 ops/s 

e la classe di riferimento:

import java.util.Arrays; 
import java.util.concurrent.TimeUnit; 

import org.openjdk.jmh.annotations.Benchmark; 
import org.openjdk.jmh.annotations.BenchmarkMode; 
import org.openjdk.jmh.annotations.Mode; 
import org.openjdk.jmh.annotations.OutputTimeUnit; 
import org.openjdk.jmh.annotations.Scope; 
import org.openjdk.jmh.annotations.Setup; 
import org.openjdk.jmh.annotations.State; 

@State(Scope.Benchmark) 
@BenchmarkMode(Mode.Throughput) 
@OutputTimeUnit(TimeUnit.SECONDS) 
public class ArrayCopyBenchmark { 

    private static final int LENGTH = 32; 

    private Object[] array; 

    @Setup 
    public void before() { 
     array = new Object[LENGTH]; 
     for (int i = 0; i < LENGTH; i++) { 
      array[i] = new Object(); 
     } 
    } 

    @Benchmark 
    public Object[] Clone() { 
     Object[] src = this.array; 
     return src.clone(); 
    } 

    @Benchmark 
    public Object[] ArraysCopyOf() { 
     Object[] src = this.array; 
     return Arrays.copyOf(src, src.length); 
    } 

    @Benchmark 
    public Object[] ArraysCopyOf_ConstantSize() { 
     Object[] src = this.array; 
     return Arrays.copyOf(src, LENGTH); 
    } 

    @Benchmark 
    public Object[] ArraysCopyOf_Class() { 
     Object[] src = this.array; 
     return Arrays.copyOf(src, src.length, Object[].class); 
    } 

    @Benchmark 
    public Object[] ArraysCopyOf_Class_ConstantSize() { 
     Object[] src = this.array; 
     return Arrays.copyOf(src, LENGTH, Object[].class); 
    } 

    @Benchmark 
    public Object[] SystemArrayCopy() { 
     Object[] src = this.array; 
     int length = src.length; 
     Object[] array = new Object[length]; 
     System.arraycopy(src, 0, array, 0, length); 
     return array; 
    } 

    @Benchmark 
    public Object[] SystemArrayCopy_ConstantSize() { 
     Object[] src = this.array; 
     Object[] array = new Object[LENGTH]; 
     System.arraycopy(src, 0, array, 0, LENGTH); 
     return array; 
    } 

    @Benchmark 
    public Object[] Loop() { 
     Object[] src = this.array; 
     int length = src.length; 
     Object[] array = new Object[length]; 
     for (int i = 0; i < length; i++) { 
      array[i] = src[i]; 
     } 
     return array; 
    } 

    @Benchmark 
    public Object[] Loop_ConstantSize() { 
     Object[] src = this.array; 
     Object[] array = new Object[LENGTH]; 
     for (int i = 0; i < LENGTH; i++) { 
      array[i] = src[i]; 
     } 
     return array; 
    } 
} 
+1

strano, ho pensato che Arrays.copyof internamente chiamato System.arraycopy significa che dovrebbe avere un throughput inferiore per definizione, quante volte hai provato a eseguire questo benchmark? – MahdeTo

+2

Prova ad aumentare il riscaldamento.20 non è sufficiente per avviare JIT. – Andreas

+0

@MahdeTo 5 volte. Non ha cambiato molto i risultati. – omiel

risposta

10

Come al solito, questo tipo di domande sono rapidamente risposta studiando il codice generato. JMH fornisce -prof perfasm su Linux e -prof xperfasm su Windows. Se si esegue il punto di riferimento sul JDK 8u40, poi si vedrà (notare che ho usato per fare -bm avgt -tu ns punteggi più comprensibili):

Benchmark       Mode Cnt Score Error Units 
ACB.SystemArrayCopy    avgt 25 13.294 ± 0.052 ns/op 
ACB.SystemArrayCopy_ConstantSize avgt 25 16.413 ± 0.080 ns/op 

Perché questi parametri di riferimento eseguire in modo diverso? Facciamo prima fare -prof perfnorm di sezionare (ho lasciato cadere le linee che non contano):

Benchmark          Mode Cnt Score Error Units 
ACB.SAC          avgt 25 13.466 ± 0.070 ns/op 
ACB.SAC:·CPI         avgt 5 0.602 ± 0.025 #/op 
ACB.SAC:·L1-dcache-load-misses    avgt 5 2.346 ± 0.239 #/op 
ACB.SAC:·L1-dcache-loads      avgt 5 24.756 ± 1.438 #/op 
ACB.SAC:·L1-dcache-store-misses    avgt 5 2.404 ± 0.129 #/op 
ACB.SAC:·L1-dcache-stores      avgt 5 14.929 ± 0.230 #/op 
ACB.SAC:·LLC-loads       avgt 5 2.151 ± 0.217 #/op 
ACB.SAC:·branches        avgt 5 17.795 ± 1.003 #/op 
ACB.SAC:·cycles        avgt 5 56.677 ± 3.187 #/op 
ACB.SAC:·instructions       avgt 5 94.145 ± 6.442 #/op 

ACB.SAC_ConstantSize       avgt 25 16.447 ± 0.084 ns/op 
ACB.SAC_ConstantSize:·CPI      avgt 5 0.637 ± 0.016 #/op 
ACB.SAC_ConstantSize:·L1-dcache-load-misses avgt 5 2.357 ± 0.206 #/op 
ACB.SAC_ConstantSize:·L1-dcache-loads   avgt 5 25.611 ± 1.482 #/op 
ACB.SAC_ConstantSize:·L1-dcache-store-misses avgt 5 2.368 ± 0.123 #/op 
ACB.SAC_ConstantSize:·L1-dcache-stores  avgt 5 25.593 ± 1.610 #/op 
ACB.SAC_ConstantSize:·LLC-loads    avgt 5 1.050 ± 0.038 #/op 
ACB.SAC_ConstantSize:·branches    avgt 5 17.853 ± 0.697 #/op 
ACB.SAC_ConstantSize:·cycles     avgt 5 66.680 ± 2.049 #/op 
ACB.SAC_ConstantSize:·instructions   avgt 5 104.759 ± 4.831 #/op 

Quindi, ConstantSize fa in qualche modo più L1-dCache-negozi, ma uno in meno LLC carico. Hm, quindi è quello che stiamo cercando, più negozi nel caso costante. -prof perfasm evidenzia comodamente le parti calde in assemblea:

default:

4.32% 6.36% 0x00007f7714bda2dc: movq $0x1,(%rax)   ; alloc 
    0.09% 0.04% 0x00007f7714bda2e3: prefetchnta 0x100(%r9) 
    2.95% 1.48% 0x00007f7714bda2eb: movl $0xf80022a9,0x8(%rax) 
    0.38% 0.18% 0x00007f7714bda2f2: mov %r11d,0xc(%rax) 
    1.56% 3.02% 0x00007f7714bda2f6: prefetchnta 0x140(%r9) 
    4.73% 2.71% 0x00007f7714bda2fe: prefetchnta 0x180(%r9) 

ConstantSize:

0.58% 1.22% 0x00007facf921132b: movq $0x1,(%r14)   ; alloc 
    0.84% 0.72% 0x00007facf9211332: prefetchnta 0xc0(%r10) 
    0.11% 0.13% 0x00007facf921133a: movl $0xf80022a9,0x8(%r14) 
    0.21% 0.68% 0x00007facf9211342: prefetchnta 0x100(%r10) 
    0.50% 0.87% 0x00007facf921134a: movl $0x20,0xc(%r14) 
    0.53% 0.82% 0x00007facf9211352: mov $0x10,%ecx 
    0.04% 0.14% 0x00007facf9211357: xor %rax,%rax 
    0.34% 0.76% 0x00007facf921135a: shl $0x3,%rcx 
    0.50% 1.17% 0x00007facf921135e: rex.W rep stos %al,%es:(%rdi) ; zeroing 
29.49% 52.09% 0x00007facf9211361: prefetchnta 0x140(%r10) 
    1.03% 0.53% 0x00007facf9211369: prefetchnta 0x180(%r10) 

Quindi non v'è che fastidioso rex.W rep stos %al,%es:(%rdi) consumando un tempo significativo. Questo azzera l'array appena assegnato. Nel test ConstantSize, la JVM non ha potuto correlare la sovrascrittura dell'intero array di destinazione, quindi ha dovuto precaricarlo prima di immergersi nell'effettiva copia dell'array.

Se si guarda il codice generato sul JDK 9b82 (l'ultimo disponibile), poi si vedrà si piega entrambi i modelli in copia non azzerato, come si può vedere con -prof perfasm, e può anche confermare con -prof perfnorm:

Benchmark          Mode Cnt Score Error Units 
ACB.SAC          avgt 50 14.156 ± 0.492 ns/op 
ACB.SAC:·CPI         avgt 5 0.612 ± 0.144 #/op 
ACB.SAC:·L1-dcache-load-misses    avgt 5 2.363 ± 0.341 #/op 
ACB.SAC:·L1-dcache-loads      avgt 5 28.350 ± 2.181 #/op 
ACB.SAC:·L1-dcache-store-misses    avgt 5 2.287 ± 0.607 #/op 
ACB.SAC:·L1-dcache-stores      avgt 5 16.922 ± 3.402 #/op 
ACB.SAC:·branches        avgt 5 21.242 ± 5.914 #/op 
ACB.SAC:·cycles        avgt 5 67.168 ± 20.950 #/op 
ACB.SAC:·instructions       avgt 5 109.931 ± 35.905 #/op 

ACB.SAC_ConstantSize       avgt 50 13.763 ± 0.067 ns/op 
ACB.SAC_ConstantSize:·CPI      avgt 5 0.625 ± 0.024 #/op 
ACB.SAC_ConstantSize:·L1-dcache-load-misses avgt 5 2.376 ± 0.214 #/op 
ACB.SAC_ConstantSize:·L1-dcache-loads   avgt 5 28.285 ± 2.127 #/op 
ACB.SAC_ConstantSize:·L1-dcache-store-misses avgt 5 2.335 ± 0.223 #/op 
ACB.SAC_ConstantSize:·L1-dcache-stores  avgt 5 16.926 ± 1.467 #/op 
ACB.SAC_ConstantSize:·branches    avgt 5 19.469 ± 0.869 #/op 
ACB.SAC_ConstantSize:·cycles     avgt 5 62.395 ± 3.898 #/op 
ACB.SAC_ConstantSize:·instructions   avgt 5 99.891 ± 5.435 #/op 

Ovviamente, tutti questi nanobenchmark per arraycopy sono suscettibili di strane differenze di prestazioni indotte dall'allineamento negli stub per la copia vettorizzati, ma questa è un'altra storia (di orrore), che non ho il coraggio di dire.

+1

Grazie per questa grande risposta. Sospettavo che la JVM non potesse dedurre che stavo usando l'intera lunghezza dell'array, ma non sapevo come dimostrarlo. – omiel

+0

Ho provato a duplicare i risultati, ma penso che 'perfasm' /' xperfasm' non sia disponibile per Mac OSX. – omiel

+0

Sì, 'perfasm' è disponibile su Linux,' xperfasm' è disponibile su Windows. Se esiste un modo per ottenere i dati che gli indirizzi dei programmi sono attivi su Mac OS X, possiamo implementare lo stesso lì. Patch benvenuto –

Problemi correlati