2015-08-31 8 views
19

Für ein Experiment habe ich dieses kleine Programm gemacht. Es erzeugt nur 10 Millionen zufällige Zeichenfolgen und fügt sie einer Arraylist hinzu. Beachten Sie, dass die Arraylist nicht eine Anfangskapazität hat.Warum ist das langsamer, wenn ArrayList eine Anfangskapazität erhält?

// editors note: added the necessary boilerplate to run, 
// and take initial capacity as an optional cmdline arg for easier testing 
import java.util.ArrayList; 
import java.util.List; 
import java.util.Random; 

class ArrayListTest { 
    public static void main(String[] args) 
    { 
     int initsize = -1; 
     if (args.length > 0) { 
      initsize = Integer.parseInt(args[0]); 
     } 

     long startTime = System.currentTimeMillis(); 

     List<String> randNums = initsize>=0 ? new ArrayList<>(initsize) : new ArrayList<>(); 
     // final List<String> randNums = initsize>=0 ? new ArrayList<String>(initsize) : new ArrayList<String>(); 

     Random r = new Random(1); 

     for (int i = 0; i < 10_000_000; i++) { 
      final int randomNum = r.nextInt(); 
      randNums.add(Integer.toString(randomNum)); 
     } 

     System.out.println(System.currentTimeMillis() - startTime); 
    } 
} 

Ich lief es 5 Mal, und die Ergebnisse in ms waren:

8917 
8720 
7814 
8768 
8867 

ich dann das Programm geändert, um die Arraylist eine Anfangskapazität zu geben:

List<String> randNums = new ArrayList<>(10_000_000); 

wieder lief ich es 5 mal, und das waren die Ergebnisse:

11562 
10454 
10499 
10481 
10415 

Es wurde definitiv immer langsamer. Ich nahm an, dass das Gegenteil der Fall gewesen wäre, da ich durch das Deklarieren einer ausreichend großen Anfangsgröße den gesamten Overhead der ArrayList weggenommen und die eigene Kapazität erhöht hätte. Warum war es langsamer?

Weitere Informationen: Jre 1.8.051, 64-Bit (Auf Windows 10);

+0

Ich sehe dieses Verhalten nicht, wenn ich es auf meinem Laptop versuche, aber das ist wahrscheinlich plattformabhängig. Das einzige, was ich mir vorstellen kann, ist, dass wenn Sie Platz für das gesamte Array auf einmal reservieren, es am Ende den Speicher auslagern könnte, was die Dinge verlangsamen könnte. – ajb

+0

welche JVM? 64bit java verwendet standardmäßig den 'server' vm, der JIT-kompiliert aggressiver als der Standard-vm für 32bit Java. So sehen Sie manchmal seltsame Perf Unterschiede mit 32bit Java für Dinge, die ein anständiger JIT-Compiler weg optimieren würde. –

+0

Was Sie sehen, ist wahrscheinlich Lärm. Versuchen Sie, jedes Mal "1" hinzuzufügen, anstatt die Zufallsgenerierung durchzuführen. –

Antwort

19

Sie würden denken, es war anders herum, aber es hat mit Garbage Collection zu tun.

Ich habe nicht den großen Unterschied gesehen, den Sie gemacht haben (3900 vs 5100), aber da dies GC-bezogen ist, laufen Sie wahrscheinlich mit geringerem Speicher. Ich lief mit 64-Bit und -Xmx4g.

Einschalten des GC log (-Xloggc:path\to\file.log), erhalte ich diese ohne Größe:

Java HotSpot(TM) 64-Bit Server VM (25.51-b03) for windows-amd64 JRE (1.8.0_51-b16), built on Jun 8 2015 18:03:07 by "java_re" with MS VC++ 10.0 (VS2010) 
Memory: 4k page, physical 33478384k(25822824k free), swap 33476532k(26121788k free) 
CommandLine flags: -XX:InitialHeapSize=535654144 -XX:MaxHeapSize=4294967296 -XX:+PrintGC -XX:+PrintGCTimeStamps -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC 
0.188: [GC (Allocation Failure) 131584K->114906K(502784K), 0.0795857 secs] 
0.358: [GC (Allocation Failure) 246490K->229080K(634368K), 0.0794026 secs] 
0.631: [GC (Allocation Failure) 492248K->452871K(716288K), 0.1389293 secs] 
0.770: [Full GC (Ergonomics)  452871K->451407K(1188864K), 3.3224726 secs] 
4.270: [GC (Allocation Failure) 714575K->714179K(1271808K), 0.2607092 secs] 
4.531: [Full GC (Ergonomics)  714179K->814K(1050624K), 0.0070693 secs] 

Und ich dies mit der Größe:

Java HotSpot(TM) 64-Bit Server VM (25.51-b03) for windows-amd64 JRE (1.8.0_51-b16), built on Jun 8 2015 18:03:07 by "java_re" with MS VC++ 10.0 (VS2010) 
Memory: 4k page, physical 33478384k(25818308k free), swap 33476532k(26123684k free) 
CommandLine flags: -XX:InitialHeapSize=535654144 -XX:MaxHeapSize=4294967296 -XX:+PrintGC -XX:+PrintGCTimeStamps -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC 
0.171: [GC (Allocation Failure) 131584K->129070K(502784K), 0.0919490 secs] 
0.370: [GC (Allocation Failure) 260654K->261166K(634368K), 0.4433150 secs] 
0.813: [Full GC (Ergonomics)  261166K->260351K(899072K), 1.4135289 secs] 
2.460: [GC (Allocation Failure) 523519K->524487K(899072K), 0.7901689 secs] 
3.250: [Full GC (Ergonomics)  524487K->523517K(1421312K), 2.3177831 secs] 

Da der zweite Lauf so viel mehr Speicher zugewiesen Anfangs läuft der GC langsamer. Das überwiegt offenbar das zusätzliche Array-Kopieren, wenn die Größe der Liste geändert wird.

+0

Die Verwendung von '-Xms2g', um die Größe des Startspeichers zu erhöhen, beschleunigt dies enorm mit oder ohne große Anfangskapazität. Es macht auch die Echtzeit ~ = Benutzer-CPU-Zeit (keine GC-Threads verrückt). Keine zusätzlichen JVM-Argumente: real = 5.5s, user = 12s, sys = 3.3s. Mit '-Xms2g': real = 1.2s, user = 1.6s, sys = 0.6s. Intel SnB i5-2500k (3,8 GHz Turbo), Dual-Channel-DDR3 1600 MHz, Linux 3.19, 'OpenJDK 64-Bit-Server-VM (Build 25.45-b02, gemischter Modus)'. –

+1

@PeterCordes Ja, mit '-Xms' ordnen Sie den Speicher vorher zu und eliminieren die 'GC (Allocation Failure)' Läufe, wodurch wahrscheinlich alle GC-Läufe eliminiert werden. Dies zeigt den Irrtum kleiner Tests, bei denen die VM nicht "aufgewärmt" ist. --- Das war der Kommentar von "Apangin" (zu Frage). – Andreas

+0

Warum kümmert sich der GC um die Menge an Speicher? Sollte es sich nicht nur um Objekte kümmern? Es muss in beiden Fällen die gleiche Anzahl an Objekten prüfen, also sollte es nicht langsamer sein. –

2

Liste RandNums mit 10 Millionen Elementen benötigt ca. 700 MB Speicherplatz. Um die Auswirkungen von GC zu vermeiden (Sicher, es viel in diesem Test bedeutet), stelle ich die Hotspot VM Argumente wie folgt aus:

-XX:+PrintGC 
-XX:+PrintGCTimeStamps 
-XX:+UseParNewGC 
-XX:+UseConcMarkSweepGC 
-Xmx1000m 
-Xms1000m 
-Xmn999m 
-XX:SurvivorRatio=65535 

Junge Generation groß genug, um alle Elemente zu speichern und keine GC machen während der Elementzuordnung. Ich mache Eden Region of Young Generation größer, um zu vermeiden, dass Elemente in Young Generation kopiert werden.
Das Ergebnis ist überraschend!. Die gesamte Ausführungszeit verringert sich von 8s auf 0,6s!

Hier habe ich etwas mehr Arbeit für den Fragesteller getan, das testet, ob die Vor-Zuweisung von ArrayList Zeit spart und wie viel es hilft.
Hier ist mein Code:

 long startTime; 
     List<String> randNums; 
     Random r = new Random(1); 

     System.out.println("-----------------------------ArrayList With Enough Capacity Allocated:----------"); 
     for(int loop=0;loop<5;loop++) { 
      startTime = System.currentTimeMillis(); 
      randNums = new ArrayList<String>(SIZE); 
      for (int i = 0; i <SIZE ; i++) { 
       int randomNum = r.nextInt(); 
       randNums.add(Integer.toString(randomNum)); 
      } 
      System.out.println(System.currentTimeMillis() - startTime); //print time of this loop 
      randNums.clear(); 
      System.gc(); 
     } 

     System.out.println("\n-----------------------------ArrayList Auto-Capacity:----------"); 
     for(int loop=0;loop<5;loop++) { 
      startTime = System.currentTimeMillis(); 
      randNums = new ArrayList<String>(); 
      for (int i = 0; i <SIZE ; i++) { 
       int randomNum = r.nextInt(); 
       randNums.add(Integer.toString(randomNum)); 
      } 
      System.out.println(System.currentTimeMillis() - startTime); //print time of this loop 
      randNums.clear(); 
      System.gc(); 
     } 

Die Ausgabe lautet:

-----------------------------ArrayList With Enough Capacity Allocated:---------- 
625 
0.712: [Full GC (System.gc()) 714143K->39628K(1023936K), 0.1450449 secs] 
0.863: [GC (CMS Initial Mark) 98268K(1023936K), 0.0549729 secs] 
545 
1.413: [Full GC (System.gc()) 705185K->564K(1023936K), 0.1239084 secs] 
483 
2.031: [Full GC (System.gc()) 679570K->564K(1023936K), 0.1256323 secs] 
2.160: [GC (CMS Initial Mark) 59357K(1023936K), 0.0274108 secs] 
523 
2.688: [Full GC (System.gc()) 670987K->564K(1023936K), 0.1222910 secs] 
482 
3.302: [Full GC (System.gc()) 673223K->564K(1023936K), 0.1299938 secs] 

-----------------------------ArrayList Auto-Capacity:---------- 
3.432: [GC (CMS Initial Mark) 40961K(1023936K), 0.0003740 secs] 
3.907: [GC (CMS Final Remark) 698381K(1023936K), 0.1091347 secs] 
796 
4.240: [Full GC (System.gc()) 911984K->56183K(1023936K), 0.1719540 secs] 
4.412: [GC (CMS Initial Mark) 56183K(1023936K), 0.0394210 secs] 
4.770: [GC (CMS Final Remark) 528894K(1023936K), 0.0726012 secs] 
690 
5.111: [Full GC (System.gc()) 893818K->2060K(1023936K), 0.1364215 secs] 
5.248: [GC (CMS Initial Mark) 20769K(1023936K), 0.0008902 secs] 
5.750: [GC (CMS Final Remark) 694113K(1023936K), 0.1124856 secs] 
704 
5.962: [Full GC (System.gc()) 808646K->2081K(1023936K), 0.1338328 secs] 
6.096: [GC (CMS Initial Mark) 21137K(1023936K), 0.0010118 secs] 
6.599: [GC (CMS Final Remark) 688155K(1023936K), 0.0899929 secs] 
661 
6.767: [Full GC (System.gc()) 810872K->2081K(1023936K), 0.1287272 secs] 
6.896: [GC (CMS Initial Mark) 21512K(1023936K), 0.0010619 secs] 
7.398: [GC (CMS Final Remark) 691216K(1023936K), 0.1083076 secs] 
681 
7.586: [Full GC (System.gc()) 803590K->2081K(1023936K), 0.1269813 secs] 
7.714: [GC (CMS Initial Mark) 2081K(1023936K), 0.0008965 secs] 

Striping GC Info, es ist:

-----------------------------ArrayList With Enough Capacity Allocated:---------- 
615 
540 
480 
511 
480 

-----------------------------ArrayList Auto-Capacity:---------- 
680 
708 
640 
644 
663 

Wir verwendet letzten drei Daten jeder Gruppe die Optimierung berechnen (um JIT- und VM-Optimierung zu vermeiden). Die Antwort kommt so:

Verwandte Themen