Ho notato qualcosa di strano mentre eseguivo il profiling del nostro codice base. Sembrava che l'ordinamento con un comparatore tipizzato (ad esempio Comparator<MyClass>
) chiamasse sempre prima un metodo Comparator<MyClass>.compare(Object,Object)
che poi chiamava il metodo Comparator<MyClass>.compare(MyClass,MyClass)
. Inoltre, la maggior parte del tempo è stata spesa nel Comparator<MyClass>.compare(Object,Object)
. Per esplorare ulteriormente, ho fatto un piccolo programma di test:Ordinamento Java tramite Comparatore <T> trascorre la maggior parte del tempo a confronto (Oggetto, Oggetto)
public class Sandbox {
public static void main(String argv[]) {
for(int j=0; j<100; j++) {
int n = 10000;
SortMe[] sortMes = new SortMe[n];
for (int i=0; i<n; i++) {
sortMes[i] = new SortMe(Math.random());
}
Arrays.sort(sortMes, new SortMeComp());
System.out.println(Arrays.toString(sortMes));
}
for(int j=0; j<100; j++) {
int n = 10000;
SortMe[] sortMes = new SortMe[n];
for (int i=0; i<n; i++) {
sortMes[i] = new SortMe(Math.random());
}
Arrays.sort(sortMes, new SortMeCompTypeless());
System.out.println(Arrays.toString(sortMes));
}
}
}
Il comparatore digitato:
public class SortMeComp implements Comparator<SortMe>{
public int compare(SortMe one, SortMe two) {
if(one.getValue()>two.getValue()) {
return -1;
} else if (one.getValue()<two.getValue()) {
return 1;
} else {
return 0;
}
}
}
Il comparatore non tipizzata che ho fatto per il confronto:
public class SortMeCompTypeless implements Comparator{
public int compare(Object oneObj, Object twoObj) {
SortMe one = (SortMe) oneObj;
SortMe two = (SortMe) twoObj;
if(one.getValue()>two.getValue()) {
return -1;
} else if (one.getValue()<two.getValue()) {
return 1;
} else {
return 0;
}
}
}
Ecco i risultati (da YourKit profiler; fammi sapere se preferisci avere uno screenshot):
+----------------------------------------------------+-----------------+-----------------+--------------------+
| Name | Time (ms) | Own Time (ms) | Invocation Count |
+----------------------------------------------------+-----------------+-----------------+--------------------+
| +---java.util.Arrays.sort(Object[], Comparator) | 23,604 100 % | 8,096 | 200 |
| | | | | |
| +---SortMeComp.compare(Object, Object) | 11,395 48 % | 7,430 | 12,352,936 |
| | | | | | |
| | +---SortMeComp.compare(SortMe, SortMe) | 3,965 17 % | 3,965 | 12,352,936 |
| | | | | |
| +---SortMeCompTypeless.compare(Object, Object) | 4,113 17 % | 4,113 | 12,354,388 |
+----------------------------------------------------+-----------------+-----------------+--------------------+
Ho eseguito il profilo senza filtrare e vengono visualizzate le chiamate ricorsive a mergeSort (che rendono difficile la lettura), ma non di interesse.
Quindi cosa sta succedendo qui? Da dove proviene il metodo SortMeComp.compare (Object, Object)? Abbiamo pensato che fosse qualcosa che Java crea internamente per trattare con i generici, ma cosa potrebbe richiedere così tanto tempo? Penserei che jvm tratterà semplicemente un metodo generico come un metodo "non tipizzato"/Object. Come puoi vedere, un cast semplice è molto più veloce. Oltre a ciò, penserei che questo sia esattamente il tipo di cosa che sarebbe JIT via anche se il jvm avesse bisogno di fare cose di tipo upfront. Cosa sta succedendo qui?
A proposito:
$ java -version
java version "1.6.0_26"
Java(TM) SE Runtime Environment (build 1.6.0_26-b03)
Java HotSpot(TM) 64-Bit Server VM (build 20.1-b02, mixed mode)
Edit:
In risposta a savinos risposta, ho provato simulando la chiamata di metodo in più con un comparatore 'non tipizzata' che semplicemente gettato in un dattiloscritto confrontare:
public class SortMeCompMethodCalls implements Comparator{
public int compare(Object oneObj, Object twoObj) {
return compare((SortMe)oneObj, (SortMe)twoObj);
}
public int compare(SortMe one, SortMe two) {
if(one.getValue()>two.getValue()) {
return -1;
} else if (one.getValue()<two.getValue()) {
return 1;
} else {
return 0;
}
}
}
Ecco i risultati:
+---------------------------------------------------------+-----------------+-----------------+--------------------+
| Name | Time (ms) | Own Time (ms) | Invocation Count |
+---------------------------------------------------------+-----------------+-----------------+--------------------+
| +---java.util.Arrays.sort(Object[], Comparator) | 31,044 100 % | 8,061 | 200 |
| | | | | |
| +---SortMeComp.compare(Object, Object) | 11,554 37 % | 7,617 | 12,354,392 |
| | | | | | |
| | +---SortMeComp.compare(SortMe, SortMe) | 3,936 13 % | 3,936 | 12,354,392 |
| | | | | |
| +---SortMeCompMethodCalls.compare(Object, Object) | 11,427 37 % | 7,613 | 12,352,146 |
| | | | | |
| +---SortMeCompMethodCalls.compare(SortMe, SortMe) | 3,814 12 % | 3,814 | 12,352,146 |
+---------------------------------------------------------+-----------------+-----------------+--------------------+
Quindi sembra che il savino abbia ragione! Il tempo extra è solo la chiamata al metodo extra (più un po 'per il cast). Mi sembra pazzesco; penseresti che sarebbe JITed via? Ah bene.
Ho rimosso la modifica 2 e l'ho aggiunta come risposta come avrebbe dovuto essere originariamente.
hmm non ho familiarità con il tuo kit ma in qualche modo l'output sembra essere incasinato. Se il montepremi è al 100%, significa che i bambini devono essere inclusi nei tempi dei genitori. Tuttavia, i tempi non si sommano davvero. Potrebbe essere che il secondo abbia appena attivato il codice in modo tale che quegli 11 secondi per il secondo tipo siano contati in sort() invece di confrontare? –
@ b.buchhold: Gran parte del tempo di ordinamento verrà speso in mergeSort (eseguendo la vera manipolazione dell'array, le chiamate ricorsive, ecc.). Se girassi il filtro, vedresti tutto questo (e sarebbe pari al 100%) ma è praticamente illeggibile (soprattutto a causa della ricorsione). Di default, yourkit prova a condensare le chiamate nei metodi della libreria java. –
cosa stai usando per calcolare i risultati? punto di riferimento? quale programma? – DarthVader