2012-12-03 9 views
6

Il seguente misura di codice il tempo necessario per 100 invocazioni del metodo maniglia (Object o) dall'interfaccia Handler (Sì, è la profilazione di cattiva qualità):Java - ListaLinkata - Prestazioni diminuisce con il numero di classi differenti in esso

package test; 

import java.util.LinkedList; 

public class Test { 

    static int i = 0; 

    private interface Handler { 
     public void handle(Object o); 
    } 
    private static class SuperHandler implements Handler { 
     public void handle(Object o) { i += 1; } 
    } 
    private static class NoSuperHandler implements Handler { 
     public void handle(Object o) { i += 1; } 
    } 
    private static class LulSuperHandler implements Handler { 
     public void handle(Object o) { i += 1; } 
    } 
    private static class LilSuperHandler implements Handler { 
     public void handle(Object o) { i += 1; } 
    } 
    private static class LolSuperHandler implements Handler { 
     public void handle(Object o) { i += 1; } 
    } 
    private static class LalSuperHandler implements Handler { 
     public void handle(Object o) { i += 1; } 
    } 
    private static class LylSuperHandler implements Handler { 
     public void handle(Object o) { i += 1; } 
    } 
    private static class LzlSuperHandler implements Handler { 
     public void handle(Object o) { i += 1; } 
    } 

    public static void main(String[] args) { 
     LinkedList<Handler> ll = new LinkedList<Handler>(); 
     for(int j = 0; j < 100; j++) { 
      if((j % 8) == 0) ll.add(new SuperHandler()); 
      if((j % 8) == 1) ll.add(new NoSuperHandler()); 
      if((j % 8) == 2) ll.add(new LulSuperHandler()); 
      if((j % 8) == 3) ll.add(new LilSuperHandler()); 
      if((j % 8) == 4) ll.add(new LolSuperHandler()); 
      if((j % 8) == 5) ll.add(new LalSuperHandler()); 
      if((j % 8) == 6) ll.add(new LylSuperHandler()); 
      if((j % 8) == 7) ll.add(new LzlSuperHandler()); 
     } 
     long begin = System.currentTimeMillis(); 
     for(int j = 0; j < 1000000; j++) for(Handler h: ll) h.handle(null); 
     System.out.println("time in ms: " + (System.currentTimeMillis() - begin)); 
     System.out.println("i: " + i); 
    } 
} 

il fatto è che se il ListaLinkata contiene un solo tipo di Handler, per esempio SuperHandler, il tempo di esecuzione è inferiore se fossero 2, 3, ecc differe nt tipi di Handler. E ogni volta che aggiungo un nuovo tipo di gestore nell'elenco, le prestazioni diminuiscono.

Per esempio quando cambio solo questa parte ricevo prestazioni migliori rispetto a sopra:

for(int j = 0; j < 100; j++) { 
    if((j % 2) == 0) ll.add(new SuperHandler()); 
    if((j % 2) == 1) ll.add(new NoSuperHandler()); 
} 

c'è un'ottimizzazione speciale che opera qui? Cosa nell'architettura JAVA diminuisce le prestazioni? Il mio test è sbagliato perché il gestore non utilizzato viene "rimosso" o "nascosto" dal compilatore? (Sto usando Linux Ubuntu - JAVA 1.7, da Oracle)

risposta

10

Esiste un ottimizzazione speciale che opera qui?

Sì. Hotspot è molto intelligente su come gestisce i metodi virtuali. Se ci sono solo un paio di implementazioni di un'interfaccia e quelle implementazioni sono piccole, si può evitare una ricerca completa di vtable semplicemente controllando il tipo giusto e incorporando il codice.

Nel momento in cui si hanno diverse implementazioni, si torna all'implementazione di vtable. (Hotspot è intelligente abbastanza per annullare ottimizzazioni che non sono più pratici. Sono scioccato che tutto pende insieme, ma a quanto pare lo fa.)

Si noti che questa non è una questione di quante classi diverse sono nella lista - c'è di più in corso qui. Vedi la risposta di Peter per i dettagli.

+0

AFAIK, il limite per i metodi virtuali inline è 2 in HotSpot. Non sono sicuro che il solo caricamento delle classi sia sufficiente per innescare una de-ottimizzazione. –

+0

@PeterLawrey: Ah - grazie per il chiarimento extra. Modificherà. –

+0

Vedere la mia risposta per un esempio. Ho la stessa lista in cui 8 tipi diversi sono chiamati in un posto e un altro in cui sono allineate 8 chiamate diverse per chiamare solo un tipo (ma dalla stessa lista) La differenza di prestazioni è drammatica. –

4

Sono d'accordo con la risposta di Jon tranne che credo che sia il numero di tipi chiamati al punto del codice che fa la differenza. Nell'esempio seguente, tutte e 8 le classi vengono caricate e lo stesso codice viene eseguito per lo stesso numero di elementi nell'elenco, tranne un elenco ha 8 e l'altro ha 2 tipi diversi.

import java.util.ArrayList; 
import java.util.LinkedList; 
import java.util.List; 

public class Test { 

    static int i = 0; 

    private interface Handler { 
     public void handle(); 
    } 

    public static void main(String[] args) { 
     List<Handler> ll8 = new ArrayList<Handler>(); 
     for (int j = 0; j < 128; j += 8) { 
      ll8.add(new SuperHandler()); 
      ll8.add(new NoSuperHandler()); 
      ll8.add(new LulSuperHandler()); 
      ll8.add(new LilSuperHandler()); 
      ll8.add(new LolSuperHandler()); 
      ll8.add(new LalSuperHandler()); 
      ll8.add(new LylSuperHandler()); 
      ll8.add(new LzlSuperHandler()); 
     } 
     List<Handler> ll2 = new ArrayList<Handler>(); 
     for (int j = 0; j < 128; j += 2) { 
      ll2.add(new SuperHandler()); 
      ll2.add(new NoSuperHandler()); 
     } 
     for (int j = 0; j < 5; j++) { 
      test8(ll8); 
      test8a(ll8); 
      test2(ll2); 
     } 
     System.out.println("i: " + i); 
    } 

    private static void test8(List<Handler> ll8) { 
     long begin = System.nanoTime(); 
     for (int j = 0; j < 1000000; j++) for (Handler h : ll8) h.handle(); 
     System.out.println("8 classes, time in ms: " + (System.nanoTime() - begin)/100000/10.0); 
    } 

    private static void test8a(List<Handler> ll8) { 
     long begin = System.nanoTime(); 
     for (int j = 0; j < 1000000; j++) 
      for (int k = 0; k < ll8.size(); k += 8) { 
       ll8.get(k + 0).handle(); 
       ll8.get(k + 1).handle(); 
       ll8.get(k + 2).handle(); 
       ll8.get(k + 3).handle(); 
       ll8.get(k + 4).handle(); 
       ll8.get(k + 5).handle(); 
       ll8.get(k + 6).handle(); 
       ll8.get(k + 7).handle(); 
      } 
     System.out.println("8 classes unrolled, time in ms: " + (System.nanoTime() - begin)/100000/10.0); 
    } 

    private static void test2(List<Handler> ll2) { 
     long begin = System.nanoTime(); 
     for (int j = 0; j < 1000000; j++) for (Handler h : ll2) h.handle(); 
     System.out.println("2 classes, time in ms: " + (System.nanoTime() - begin)/100000/10.0); 
    } 

    private static class SuperHandler implements Handler { 
     public void handle() { i += 1; } 
    } 

    private static class NoSuperHandler implements Handler { 
     public void handle() { i += 1; } 
    } 

    private static class LulSuperHandler implements Handler { 
     public void handle() { i += 1; } 
    } 

    private static class LilSuperHandler implements Handler { 
     public void handle() { i += 1; } 
    } 

    private static class LolSuperHandler implements Handler { 
     public void handle() { i += 1; } 
    } 

    private static class LalSuperHandler implements Handler { 
     public void handle() { i += 1; } 
    } 

    private static class LylSuperHandler implements Handler { 
     public void handle() { i += 1; } 
    } 

    private static class LzlSuperHandler implements Handler { 
     public void handle() { i += 1; } 
    } 
} 

stampe

8 classes, time in ms: 1467.9 
8 classes unrolled, time in ms: 144.7 
2 classes, time in ms: 515.8 
8 classes, time in ms: 1455.1 
8 classes unrolled, time in ms: 126.2 
2 classes, time in ms: 509.6 
8 classes, time in ms: 1234.1 
8 classes unrolled, time in ms: 107.8 
2 classes, time in ms: 274.3 
8 classes, time in ms: 1212.0 
8 classes unrolled, time in ms: 108.1 
2 classes, time in ms: 273.0 
8 classes, time in ms: 1208.8 
8 classes unrolled, time in ms: 107.8 
2 classes, time in ms: 274.5 
i: 1920000000 
+0

Risposta molto interessante, e codice pertinente, grazie (ottenuto 2 anche per limite). Devo dire che sono molto impressionato da queste ottimizzazioni. Il tuo codice legge l'array 8 per 8. Sembra che ci sarebbe stata un'ottimizzazione se avessi usato 4 o qualsiasi multiplo di 8. – Badabada125

+0

Nel caso srotolato, il tipo di ogni chiamata è sempre lo stesso (ma diverso per ciascuno) –

Problemi correlati