Je veux corriger et de compléter les réponses précédentes.
- Objet.clone utilise pas cochée Système.arraycopy mise en œuvre pour les tableaux;
- La principale amélioration de la performance de l'Objet.clone, c'est l'initialisation de la mémoire BRUTE directement. Dans le cas de Système.arraycopy il tente également de combiner initialisation de tableau avec l'opération de copie, comme nous pouvons le voir dans le code source, mais aussi les différentes vérifications supplémentaires, pour cela, à la différence de l'Objet.clone. Si vous venez de désactiver cette fonction (voir ci-dessous), le rendement serait très proche (en particulier sur mon matériel).
- Une chose plus intéressante est sur de Jeunes vs Vieux Général. Dans le cas où la source de la matrice de alignées et à l'intérieur de Old Gen, les deux méthodes ont fermer la performance.
- Quand on copie primitive des matrices du Système.arraycopy utilise toujours generate_unchecked_arraycopy.
- Il dépend de matériel/OS dépendante des implémentations, donc ne faites pas confiance à des repères et des hypothèses, vérifier sur que vous possédez.
Explication
Tout d'abord clone de la méthode et du Système.arraycopy sont intrinsèques.
Objet.le clone et le Système.arraycopy utilisation generate_unchecked_arraycopy.
Et si on aller plus loin, nous pouvions voir qu'après un HotSpot sélectionnez la mise en œuvre concrète, dépendante de l'OS, etc.
Longly.
Voyons le code de Hotspot.
Tout d'abord nous allons voir que l'Objet.clone (LibraryCallKit::inline_native_clone) utilise generate_arraycopy, qui est utilisé pour le Système.arraycopy en cas d' -XX:-ReduceInitialCardMarks. Sinon, il ne LibraryCallKit::copy_to_clone, qui initialiser le tableau dans la mémoire BRUTE (si l'option-XX:+ReduceBulkZeroing, qui est activé par défaut).
En revanche Système.arraycopy utilise generate_arraycopy directement, essayez de vérifier ReduceBulkZeroing (et de nombreux autres cas) et d'éliminer la matrice de mise à zéro de trop, avec que mentionné, d'autres vérifications et aussi il serait faire des vérifications pour s'assurer que tous les éléments sont initialisés, à la différence de l'Objet.clone. Enfin, dans le meilleur des cas, ils utilisent tous les deux generate_unchecked_arraycopy.
Ci-dessous, je montre quelques repères pour voir cet effet sur la pratique:
- La première est une simple référence, la seule différence par rapport à la réponse précédente, que les tableaux ne sont pas triées; Nous voyons que arraycopy est plus lent (mais pas deux fois), les résultats https://pastebin.com/ny56Ag1z;
- Deuxièmement, j'ai ajouter l'option-XX:-ReduceBulkZeroing et maintenant je vois que les performances des deux méthodes est très proche. Les résultats https://pastebin.com/ZDAeQWwx;
- J'ai aussi supposer que nous aurons la différence entre les Anciens et les Jeunes, en raison de l'alignement des tableaux (c'est une fonctionnalité de Java GC, quand nous appelons GC, l'alignement des matrices est changé, il est facile d'observer à l'aide de JOL). J'ai été surpris de voir que les performances de devenir le même, en général, et de la décote pour les deux méthodes. Les résultats https://pastebin.com/bTt5SJ8r. Pour celui qui croit en chiffres concrets, le débit du Système.arraycopy est mieux que de l'Objet.clone.
Premier indice:
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
@State(Scope.Benchmark)
@BenchmarkMode(Mode.All)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public class CloneVsArraycopy {
@Param({"10", "1000", "100000"})
int size;
int[] source;
@Setup(Level.Invocation)
public void setup() {
source = create(size);
}
@Benchmark
public int[] clone(CloneVsArraycopy cloneVsArraycopy) {
return cloneVsArraycopy.source.clone();
}
@Benchmark
public int[] arraycopy(CloneVsArraycopy cloneVsArraycopy) {
int[] dest = new int[cloneVsArraycopy.size];
System.arraycopy(cloneVsArraycopy.source, 0, dest, 0, dest.length);
return dest;
}
public static void main(String[] args) throws Exception {
new Runner(new OptionsBuilder()
.include(CloneVsArraycopy.class.getSimpleName())
.warmupIterations(20)
.measurementIterations(20)
.forks(20)
.build()).run();
}
private static int[] create(int size) {
int[] a = new int[size];
for (int i = 0; i < a.length; i++) {
a[i] = ThreadLocalRandom.current().nextInt();
}
return a;
}
}
L'exécution de ce test sur mon PC, j'ai eu ce - https://pastebin.com/ny56Ag1z.
La différence n'est pas si grand, mais existe toujours.
Le deuxième point de référence, je seulement ajouter un paramètre -XX:-ReduceBulkZeroing et a obtenu ce résultats https://pastebin.com/ZDAeQWwx. Non, nous voyons que pour les Jeunes Gen la différence est beaucoup moins aussi.
Dans le troisième test, j'ai changé seulement la méthode de configuration et activer ReduceBulkZeroing option arrière:
@Setup(Level.Invocation)
public void setup() {
source = create(size);
// try to move to old gen/align array
for (int i = 0; i < 10; ++i) {
System.gc();
}
}
La différence est beaucoup moins (peut-être dans l'erreur d'intervalle) - https://pastebin.com/bTt5SJ8r.
Avertissement
C'est aussi peut-être erroné. Vous devez vérifier sur votre propre.
En plus
Je pense qu'il est intéressant de se pencher sur les critères de référence du processus:
# Benchmark: org.egorlitvinenko.arrays.CloneVsArraycopy.arraycopy
# Parameters: (size = 50000)
# Run progress: 0,00% complete, ETA 00:07:30
# Fork: 1 of 5
# Warmup Iteration 1: 8,870 ops/ms
# Warmup Iteration 2: 10,912 ops/ms
# Warmup Iteration 3: 16,417 ops/ms <- Hooray!
# Warmup Iteration 4: 17,924 ops/ms <- Hooray!
# Warmup Iteration 5: 17,321 ops/ms <- Hooray!
# Warmup Iteration 6: 16,628 ops/ms <- What!
# Warmup Iteration 7: 14,286 ops/ms <- No, stop, why!
# Warmup Iteration 8: 13,928 ops/ms <- Are you kidding me?
# Warmup Iteration 9: 13,337 ops/ms <- pff
# Warmup Iteration 10: 13,499 ops/ms
Iteration 1: 13,873 ops/ms
Iteration 2: 16,177 ops/ms
Iteration 3: 14,265 ops/ms
Iteration 4: 13,338 ops/ms
Iteration 5: 15,496 ops/ms
Pour L'Objet.clone
# Benchmark: org.egorlitvinenko.arrays.CloneVsArraycopy.clone
# Parameters: (size = 50000)
# Run progress: 0,00% complete, ETA 00:03:45
# Fork: 1 of 5
# Warmup Iteration 1: 8,761 ops/ms
# Warmup Iteration 2: 12,673 ops/ms
# Warmup Iteration 3: 20,008 ops/ms
# Warmup Iteration 4: 20,340 ops/ms
# Warmup Iteration 5: 20,112 ops/ms
# Warmup Iteration 6: 20,061 ops/ms
# Warmup Iteration 7: 19,492 ops/ms
# Warmup Iteration 8: 18,862 ops/ms
# Warmup Iteration 9: 19,562 ops/ms
# Warmup Iteration 10: 18,786 ops/ms
Nous pouvons observer la performance downgrade ici pour le Système.arraycopy. J'ai vu des image similaire pour les cours d'eau et il y avait un bug dans les compilateurs.
Je suppose que cela pourrait être un bug dans les compilateurs trop. De toute façon, il est étrange qu'au bout de 3 de chauffe de la performance des décotes.
Mise à JOUR
Ce qui est sur le typage
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
@State(Scope.Benchmark)
@BenchmarkMode(Mode.All)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public class CloneVsArraycopyObject {
@Param({"100"})
int size;
AtomicLong[] source;
@Setup(Level.Invocation)
public void setup() {
source = create(size);
}
@Benchmark
@CompilerControl(CompilerControl.Mode.DONT_INLINE)
public AtomicLong[] clone(CloneVsArraycopyObject cloneVsArraycopy) {
return cloneVsArraycopy.source.clone();
}
@Benchmark
@CompilerControl(CompilerControl.Mode.DONT_INLINE)
public AtomicLong[] arraycopy(CloneVsArraycopyObject cloneVsArraycopy) {
AtomicLong[] dest = new AtomicLong[cloneVsArraycopy.size];
System.arraycopy(cloneVsArraycopy.source, 0, dest, 0, dest.length);
return dest;
}
public static void main(String[] args) throws Exception {
new Runner(new OptionsBuilder()
.include(CloneVsArraycopyObject.class.getSimpleName())
.jvmArgs("-XX:+UnlockDiagnosticVMOptions", "-XX:+PrintInlining", "-XX:-ReduceBulkZeroing")
.warmupIterations(10)
.measurementIterations(5)
.forks(5)
.build())
.run();
}
private static AtomicLong[] create(int size) {
AtomicLong[] a = new AtomicLong[size];
for (int i = 0; i < a.length; i++) {
a[i] = new AtomicLong(ThreadLocalRandom.current().nextLong());
}
return a;
}
}
La différence n'est pas observée - https://pastebin.com/ufxCZVaC.
Je suppose que l'explication est simple, en tant que Système.arraycopy est chaud intrinsèque dans ce cas, la mise en œuvre réelle serait juste inline sans typecheking, etc.
Note
Je suis d'accord avec Radiodef vous pourriez trouver intéressant de lire les post de blog, l'auteur de ce blog en est le créateur (ou l'un des créateurs) de la JMH.