Warum wird Arrays.fill () in HashMap.clear () nicht mehr verwendet?


70

Ich habe etwas Seltsames bei der Implementierung von bemerkt HashMap.clear(). So sah es in OpenJDK 7u40 aus :

public void clear() {
    modCount++;
    Arrays.fill(table, null);
    size = 0;
}

Und so sieht es ab OpenJDK 8u40 aus :

public void clear() {
    Node<K,V>[] tab;
    modCount++;
    if ((tab = table) != null && size > 0) {
        size = 0;
        for (int i = 0; i < tab.length; ++i)
            tab[i] = null;
    }
}

Ich verstehe, dass das jetzt tablefür das Leeren einer Karte null sein kann, daher ist die zusätzliche Überprüfung und Zwischenspeicherung in einer lokalen Variablen erforderlich. Aber warum wurde Arrays.fill()durch eine for-Schleife ersetzt?

Es scheint, dass die Änderung in diesem Commit eingeführt wurde . Leider habe ich keine Erklärung dafür gefunden, warum eine Plain-for-Schleife besser sein könnte als Arrays.fill(). Ist es schneller Oder sicherer?


3
Sicherer nicht, aber es könnte schneller sein, wenn der fillAnruf nicht inline und tabkurz ist. Unter HotSpot führen sowohl die Schleife als auch der explizite fillAufruf zu einem schnellen Compiler (in einem Happy-Day-Szenario).
Marko Topolnik

14
Ich denke, dies soll verhindern, dass die Klasse java.util.Arraysals Nebeneffekt dieser Methode geladen wird. Für Anwendungscode ist dies normalerweise kein Problem.
Holger

5
Interessant. Meine Vermutung ist, dass dies ein Fehler ist. Der Überprüfungsthread für dieses Änderungsset befindet sich hier und verweist auf einen früheren Thread , der hier fortgesetzt wird . Die erste Nachricht in diesem früheren Thread verweist auf einen Prototyp von HashMap.java im CVS-Repository von Doug Lea. Ich weiß nicht, woher das kommt. Es scheint mit nichts in der OpenJDK-Geschichte übereinzustimmen.
Stuart Marks

7
... Auf jeden Fall könnte es ein alter Schnappschuss gewesen sein; Die for-Schleife war viele Jahre lang in der clear () -Methode. Der Aufruf von Arrays.fill () wurde von diesem Änderungssatz eingeführt , sodass er nur einige Monate im Baum enthalten war. Beachten Sie auch, dass die durch diesen Änderungssatz eingeführte Zweierpotenzberechnung basierend auf Integer.highestOneBit () zur gleichen Zeit ebenfalls verschwand, obwohl dies bemerkt, aber während der Überprüfung verworfen wurde. Hmmm.
Stuart Marks

6
@EJP, ich bin anderer Meinung. Ich suche keine Meinungen, nur Fakten, die Frage bezieht sich nicht auf den Codestil. Es gibt drei gute Versionen in den Kommentaren, alle können überprüft werden. Die Version von Marko kann durch einen guten Benchmark überprüft werden, der im interpretierten / C1 / C2-Modus ausgeführt wird. Die Holger-Version kann überprüft werden, indem die Ladereihenfolge der Klasse beim Start der JVM untersucht wird. Die Version von Stuart (die wahrscheinlich korrekt ist) kann überprüft werden, indem die Diskussionen zum Festschreibungsbaum und zur Mailingliste untersucht werden. Schließlich kann jeder dies direkt in core-libs-dev fragen. Es gibt Tausende von Fragen, bei denen die richtige Antwort "Es ist ein Fehler" lautet.
Tagir Valeev

Antworten:


29

Ich werde versuchen, drei mehr als vernünftige Versionen zusammenzufassen, die in Kommentaren vorgeschlagen wurden.

@ Holger sagt :

Ich denke, dies soll verhindern, dass die Klasse java.util.Arrays als Nebeneffekt dieser Methode geladen wird. Für Anwendungscode ist dies normalerweise kein Problem.

Dies ist am einfachsten zu testen. Lassen Sie uns ein solches Programm kompilieren:

public class HashMapTest {
    public static void main(String[] args) {
        new java.util.HashMap();
    }
}

Führen Sie es mit java -verbose:class HashMapTest. Dadurch werden die Klassenladeereignisse gedruckt, sobald sie auftreten. Mit JDK 1.8.0_60 werden mehr als 400 Klassen geladen:

... 155 lines skipped ...
[Loaded java.util.Set from C:\Program Files\Java\jre1.8.0_60\lib\rt.jar]
[Loaded java.util.AbstractSet from C:\Program Files\Java\jre1.8.0_60\lib\rt.jar]
[Loaded java.util.Collections$EmptySet from C:\Program Files\Java\jre1.8.0_60\lib\rt.jar]
[Loaded java.util.Collections$EmptyList from C:\Program Files\Java\jre1.8.0_60\lib\rt.jar]
[Loaded java.util.Collections$EmptyMap from C:\Program Files\Java\jre1.8.0_60\lib\rt.jar]
[Loaded java.util.Collections$UnmodifiableCollection from C:\Program Files\Java\jre1.8.0_60\lib\rt.jar]
[Loaded java.util.Collections$UnmodifiableList from C:\Program Files\Java\jre1.8.0_60\lib\rt.jar]
[Loaded java.util.Collections$UnmodifiableRandomAccessList from C:\Program Files\Java\jre1.8.0_60\lib\rt.jar]
[Loaded sun.reflect.Reflection from C:\Program Files\Java\jre1.8.0_60\lib\rt.jar]
**[Loaded java.util.HashMap from C:\Program Files\Java\jre1.8.0_60\lib\rt.jar]
[Loaded java.util.HashMap$Node from C:\Program Files\Java\jre1.8.0_60\lib\rt.jar]
[Loaded java.lang.Class$3 from C:\Program Files\Java\jre1.8.0_60\lib\rt.jar]
[Loaded java.lang.Class$ReflectionData from C:\Program Files\Java\jre1.8.0_60\lib\rt.jar]
[Loaded java.lang.Class$Atomic from C:\Program Files\Java\jre1.8.0_60\lib\rt.jar]
[Loaded sun.reflect.generics.repository.AbstractRepository from C:\Program Files\Java\jre1.8.0_60\lib\rt.jar]
[Loaded sun.reflect.generics.repository.GenericDeclRepository from C:\Program Files\Java\jre1.8.0_60\lib\rt.jar]
[Loaded sun.reflect.generics.repository.ClassRepository from C:\Program Files\Java\jre1.8.0_60\lib\rt.jar]
[Loaded java.lang.Class$AnnotationData from C:\Program Files\Java\jre1.8.0_60\lib\rt.jar]
[Loaded sun.reflect.annotation.AnnotationType from C:\Program Files\Java\jre1.8.0_60\lib\rt.jar]
[Loaded java.util.WeakHashMap from C:\Program Files\Java\jre1.8.0_60\lib\rt.jar]
[Loaded java.lang.ClassValue$ClassValueMap from C:\Program Files\Java\jre1.8.0_60\lib\rt.jar]
[Loaded java.lang.reflect.Modifier from C:\Program Files\Java\jre1.8.0_60\lib\rt.jar]
[Loaded sun.reflect.LangReflectAccess from C:\Program Files\Java\jre1.8.0_60\lib\rt.jar]
[Loaded java.lang.reflect.ReflectAccess from C:\Program Files\Java\jre1.8.0_60\lib\rt.jar]
**[Loaded java.util.Arrays from C:\Program Files\Java\jre1.8.0_60\lib\rt.jar]
...

Wie Sie sehen können, HashMapwird lange vor dem Anwendungscode Arraysgeladen und nur 14 Klassen danach HashMap. Das HashMapLaden wird durch sun.reflect.ReflectionInitialisierung ausgelöst, da es HashMapstatische Felder enthält. Die ArraysLast wird wahrscheinlich durch die WeakHashMapLast ausgelöst , die tatsächlich Arrays.fillin der clear()Methode vorhanden ist. Die WeakHashMapLast wird ausgelöst, durch java.lang.ClassValue$ClassValueMapdie sich erstreckt WeakHashMap. Das ClassValueMapist in jedem java.lang.ClassFall vorhanden. Mir scheint also, dass Arraysdas JDK ohne Klasse überhaupt nicht initialisiert werden kann. Auch der Arraysstatische Initialisierer ist sehr kurz, er initialisiert nur den Assertionsmechanismus. Dieser Mechanismus wird in vielen anderen Klassen verwendet (einschließlich beispielsweisejava.lang.Throwablewelches sehr früh geladen wird). In werden keine weiteren statischen Initialisierungsschritte ausgeführt java.util.Arrays. Daher scheint mir die @ Holger-Version falsch zu sein.

Hier fanden wir auch sehr interessante Sache. Das WeakHashMap.clear()benutzt noch Arrays.fill. Es ist interessant, als es dort erschien, aber leider geht dies in prähistorische Zeiten (es war bereits im allerersten öffentlichen OpenJDK-Repository vorhanden).

Als nächstes sagt @MarcoTopolnik :

Sicherer nicht, aber es könnte schneller sein, wenn der fillAnruf nicht inline und tabkurz ist. Unter HotSpot führen sowohl die Schleife als auch der explizite fillAufruf zu einem schnellen Compiler (in einem Happy-Day-Szenario).

Es war tatsächlich überraschend für mich, dass Arrays.filles nicht direkt intrinsisch ist (siehe intrinsische Liste, die von @apangin generiert wurde ). Es scheint, dass eine solche Schleife von JVM ohne explizite intrinsische Behandlung erkannt und vektorisiert werden kann. Es ist also richtig, dass ein zusätzlicher Anruf in ganz bestimmten Fällen nicht eingebunden werden kann (z. B. wenn das MaxInlineLevelLimit erreicht ist). Auf der anderen Seite ist es eine sehr seltene Situation und es ist nur ein einzelner Anruf, es ist kein Anruf innerhalb der Schleife und es ist ein statischer, kein virtueller / Schnittstellenaufruf, daher könnte die Leistungsverbesserung nur geringfügig und nur in bestimmten Szenarien sein. Nicht das, was die JVM-Entwickler normalerweise interessieren.

Es sollte auch beachtet werden, dass sogar der C1-Client-Compiler (Tier 1-3) inline Arrays.fillaufgerufen werden kann, beispielsweise in WeakHashMap.clear(), wie inlining log ( -XX:+UnlockDiagnosticVMOptions -XX:+PrintCompilation -XX:+PrintInlining) sagt:

36       3  java.util.WeakHashMap::clear (50 bytes)
     !m        @ 4   java.lang.ref.ReferenceQueue::poll (28 bytes)
                 @ 17   java.lang.ref.ReferenceQueue::reallyPoll (66 bytes)   callee is too large
               @ 28   java.util.Arrays::fill (21 bytes)
     !m        @ 40   java.lang.ref.ReferenceQueue::poll (28 bytes)
                 @ 17   java.lang.ref.ReferenceQueue::reallyPoll (66 bytes)   callee is too large
               @ 1   java.util.AbstractMap::<init> (5 bytes)   inline (hot)
                 @ 1   java.lang.Object::<init> (1 bytes)   inline (hot)
               @ 9   java.lang.ref.ReferenceQueue::<init> (27 bytes)   inline (hot)
                 @ 1   java.lang.Object::<init> (1 bytes)   inline (hot)
                 @ 10   java.lang.ref.ReferenceQueue$Lock::<init> (5 bytes)   unloaded signature classes
               @ 62   java.lang.Float::isNaN (12 bytes)   inline (hot)
               @ 112   java.util.WeakHashMap::newTable (8 bytes)   inline (hot)

Natürlich kann es auch leicht von einem intelligenten und leistungsstarken C2-Server-Compiler integriert werden. Somit sehe ich hier keine Probleme. Scheint, dass die @ Marco-Version auch falsch ist.

Zum Schluss haben wir noch ein paar Kommentare von @StuartMarks (der JDK-Entwickler ist, also eine offizielle Stimme):

Interessant. Meine Vermutung ist, dass dies ein Fehler ist. Der Überprüfungsthread für dieses Änderungsset befindet sich hier und verweist auf einen früheren Thread , der hier fortgesetzt wird . Die erste Nachricht in diesem früheren Thread verweist auf einen Prototyp von HashMap.java im CVS-Repository von Doug Lea. Ich weiß nicht, woher das kommt. Es scheint mit nichts in der OpenJDK-Geschichte übereinzustimmen.

... Auf jeden Fall könnte es ein alter Schnappschuss gewesen sein; Die for-Schleife war viele Jahre lang in der clear () -Methode. Der Aufruf von Arrays.fill () wurde von diesem Änderungssatz eingeführt , sodass er nur einige Monate im Baum enthalten war. Beachten Sie auch, dass die durch diesen Änderungssatz eingeführte Zweierpotenzberechnung basierend auf Integer.highestOneBit () zur gleichen Zeit ebenfalls verschwand, obwohl dies bemerkt, aber während der Überprüfung verworfen wurde. Hmmm.

Tatsächlich ist die HashMap.clear()die Schleife vieler Jahre enthielt, wurde ersetzt mit Arrays.fillam 10. April 2013 und blieb weniger ein ein halbes Jahr bis zum 4. September , wenn die diskutiert commit eingeführt wurde. Das besprochene Commit war tatsächlich eine wichtige Neufassung der HashMapInterna, um das JDK-8023463- Problem zu beheben . Es war eine lange Geschichte über die Möglichkeit, die HashMapmit Schlüsseln zu vergiften, die doppelte Hashcodes haben, wodurch die Suchgeschwindigkeit HashMapauf linear reduziert wird , was sie für DoS-Angriffe anfällig macht. Die Versuche, dies zu lösen, wurden in JDK-7 durchgeführt, einschließlich einer Randomisierung des String-HashCodes. So scheint dasHashMap Die Implementierung wurde aus dem früheren Commit herausgearbeitet, unabhängig entwickelt und dann in den Hauptzweig integriert, wobei mehrere dazwischen eingeführte Änderungen überschrieben wurden.

Wir können diese Hypothese unterstützen, indem wir einen Diff durchführen. Nehmen Sie die Version, in Arrays.fillder sie entfernt wurde (04.09.2013), und vergleichen Sie sie mit der vorherigen Version (30.07.2013). Der diff -U0Ausgang hat 4341 Zeilen. Lassen Sie uns nun gegen die Version vor einer Version unterscheiden , die Arrays.fillhinzugefügt wurde (2013-04-01). diff -U0Enthält jetzt nur noch 2680 Zeilen. Somit ähnelt die neuere Version tatsächlich eher der älteren als der unmittelbaren Eltern.

Fazit

Abschließend würde ich Stuart Marks zustimmen. Es gab keinen konkreten Grund zum Entfernen Arrays.fill, nur weil die dazwischen liegende Änderung versehentlich überschrieben wurde. Die Verwendung Arrays.fillist sowohl im JDK-Code als auch in Benutzeranwendungen vollkommen in Ordnung und wird beispielsweise in verwendet WeakHashMap. DasArrays Klasse wird sowieso ziemlich früh während der JDK-Initialisierung geladen, verfügt über einen sehr einfachen statischen Initialisierer und die Arrays.fillMethode kann auch vom Client-Compiler problemlos integriert werden, sodass kein Leistungsnachteil zu beachten ist.


Mein Kommentar hat nur eine Absicht erraten und nicht behauptet, dass er angesichts der aktuellen Implementierung tatsächlich nützlich ist. Ein einfacherer Test besteht darin, nur einen Haltepunkt festzulegen HashMap.clear(), um den allerersten (JVM-internen) Aufruf abzufangen, und ja, zu diesem Zeitpunkt java.util.Arrayswurde bereits geladen. Für mich hat Stuart Marks die Antwort bereits vor zwei Tagen gegeben. Es gibt tatsächlich die Tendenz, das Gegenteil zu tun, manuelle Schleifen Arrays.fillaus guten Gründen durch zu ersetzen, und hier haben wir nur zusammenstoßende Updates. Übrigens. Arrays.fillkönnte intrinsisch werden, so wie es Arrays.copyOfbereits ist ...
Holger

Sie sagten: - Das Laden von Arrays wird wahrscheinlich durch das Laden von WeakHashMap ausgelöst, das tatsächlich Arrays.fill in der Methode clear () enthält. . Eine Klasse wird nur geladen, wenn diese Klasse beim Laden der aktuellen Klasse benötigt wird. So Arrayswird nur dann geladen werden beim Laden , WeakHashMapwenn WeakHashMapeine statische Initialisierung von hat Arrays, nur mit Arrays.fill()in clearMethode nicht das Laden von auslösen Arrays. Ich denke, eine andere Klasse löst die Last von aus Arrays.
Hagrawal

Ich stimme der Schlussfolgerung nicht zu. Es ist unwahrscheinlich, dass es sich um einen Fehler beim Zusammenführen von Zweigen handelte. Für mich, der Entwickler einfach gedacht , dass die Schleife direkt statt Aufruf schreiben Arrays.fill könnte schneller sein (wegen der Gründen im Zusammenhang mit Läden von Klassen und Verfahren inlining an anderer Stelle erwähnt), und da die Schleife ganz einfach und klein ist, ging es zu tun.
Rogério

@hagrawal: Sie haben Recht, dass eine statische Referenz das Laden / Initialisieren von Klassen nicht automatisch auslöst. Es ist jedoch nicht so schwer, den tatsächlichen Trigger zu finden, indem der ArraysKlasse ein Initialisierer hinzugefügt wird, der den Call-Stack-Trace aufzeichnet. In der von mir getesteten JDK ist es der Konstruktor, String(char[])der aufruft Arrays.copy. Es wurde von angerufen sun.nio.cs.FastCharsetProvider, aber ich denke, es gibt viele andere Orte, die es initialisiert haben, wenn dieser Anrufer nicht der erste war ...
Holger

@ Holger Ok. Für mich sieht es so aus ( ich denke du meinst es auch so ), dass Arrayses nicht geladen wird, sun.nio.cs.FastCharsetProviderweil Arrayses viel vorher geladen wird. Wie pro meinem Wissen wird eine Klasse bald nach der Klasse geladen werden , die es verweist, so von dieser Logik geht, Arrayssoll wegen geladen wurde sun.reflect.misc.ReflectUtiloder java.util.concurrent.atomic.AtomicReferenceFieldUpdaterweil diese Klassen geladen werden, kurz bevorArrays
hagrawal

3

Weil es viel schneller ist!

Ich habe einige gründliche Benchmarking-Tests für reduzierte Versionen der beiden Methoden durchgeführt:

void jdk7clear() {
    Arrays.fill(table, null);
}

void jdk8clear() {
    Object[] tab;
    if ((tab = table) != null) {
        for (int i = 0; i < tab.length; ++i)
            tab[i] = null;
    }
}

Arbeiten an Arrays verschiedener Größen, die zufällige Werte enthalten. Hier sind die (typischen) Ergebnisse:

Map size |  JDK 7 (sd)|  JDK 8 (sd)| JDK 8 vs 7
       16|   2267 (36)|   1521 (22)| 67%
       64|   3781 (63)|   1434 ( 8)| 38%
      256|   3092 (72)|   1620 (24)| 52%
     1024|   4009 (38)|   2182 (19)| 54%
     4096|   8622 (11)|   4732 (26)| 55%
    16384|  27478 ( 7)|  12186 ( 8)| 44%
    65536| 104587 ( 9)|  46158 ( 6)| 44%
   262144| 445302 ( 7)| 183970 ( 8)| 41%

Und hier sind die Ergebnisse, wenn Sie über ein Array arbeiten, das mit Nullen gefüllt ist (damit Probleme mit der Speicherbereinigung behoben werden):

Map size |  JDK 7 (sd)|  JDK 8 (sd)| JDK 8 vs 7
       16|     75 (15)|     65 (10)|  87%
       64|    116 (34)|     90 (15)|  78%
      256|    246 (36)|    191 (20)|  78%
     1024|    751 (40)|    562 (20)|  75%
     4096|   2857 (44)|   2105 (21)|  74%
    16384|  13086 (51)|   8837 (19)|  68%
    65536|  52940 (53)|  36080 (16)|  68%
   262144| 225727 (48)| 155981 (12)|  69%

Die Zahlen sind in Nanosekunden (sd)angegeben. Dies ist 1 Standardabweichung, ausgedrückt als Prozentsatz des Ergebnisses (zu Ihrer Information, eine "normalverteilte" Population hat eine SD von 68).vs ist das JDK 8-Timing relativ zu JDK 7.

Es ist interessant, dass es nicht nur deutlich schneller ist, sondern auch die Abweichung etwas geringer ist, was bedeutet, dass die JDK 8-Implementierung etwas konsistenter ist Leistung .

Die Tests wurden auf jdk 1.8.0_45 über eine große (Millionen) Anzahl von Malen auf Arrays ausgeführt, die mit zufälligen IntegerObjekten gefüllt waren . Um herausliegende Zahlen zu entfernen, wurden bei jedem Ergebnissatz die schnellsten und langsamsten 3% der Timings verworfen. Die Speicherbereinigung wurde angefordert, und der Thread gab unmittelbar vor jedem Aufruf der Methode nach und schlief ein. Das Aufwärmen der JVM wurde bei den ersten 20% der Arbeit durchgeführt und diese Ergebnisse wurden verworfen.


2
Könnten Sie bitte die Details des von Ihnen verwendeten Benchmarking-Frameworks mitteilen? Wann haben Sie eine explizite Speicherbereinigung durchgeführt? Haben Sie separate Tests in separaten JVMs durchgeführt? Die Ausgabe sieht nicht wie JMH aus. Auch wenn Sie SD 16% haben (ziemlich hohe SD für diesen Fall, wahrscheinlich Probleme mit der Testmethode?), Können Sie nicht sagen, dass ein um 3% schnelleres Ergebnis statistisch signifikant ist. Es kann zufällige Schwankungen sein.
Tagir Valeev

@TagirValeev Ich habe meinen eigenen Test geschrieben. Zum Aufwärmen erlaubt. Man kann sagen, dass 3% aufgrund des Testvolumens (Millionen) statistisch signifikant sind und sogar so konstant laufen. Ich habe GC nicht zugelassen (das könnte große SD erklären). Ich werde es erneut ausführen und GC steuern. Beachten Sie, dass SD ist , dass große - eine Standardnormalverteilung hat SD = 68%, aber ich bin mit dir - ich würde der gleiche Code erwartet eine ganz enge Varianz hat.
Bohemian

4
Könnten Sie bitte den gesamten Benchmarking-Code teilen? Wenn Phänomene wie Neutrinos entdeckt werden, die sich schneller als Licht bewegen , ist dies normalerweise eher ein Messfehler als eine Revolution in der Physik.
Tagir Valeev

3
Ich habe letzte Nacht mein eigenes Benchmarking durchgeführt, aber ich konnte nur winzige Leistungsunterschiede zwischen den beiden clear()Methodenversionen feststellen (unter JDK 1.8.0_45 32-Bit). Diese Antwort muss also den vollständigen Quellcode für den Benchmark anzeigen, sonst kann man ihm einfach nicht vertrauen.
Rogério

1
Wenn die Antworten auf SO geschlossen werden könnten, würde ich dafür stimmen, diese Antwort zu schließen.
ZhekaKozlov

1

Für mich ist der Grund eine wahrscheinliche Leistungsverbesserung zu vernachlässigbaren Kosten in Bezug auf die Codeklarheit.

Beachten Sie, dass die Implementierung der fillMethode trivial ist. Eine einfache for-Schleife setzt jedes Array-Element auf null. Das Ersetzen eines Aufrufs durch die tatsächliche Implementierung führt also nicht zu einer signifikanten Verschlechterung der Klarheit / Prägnanz der Aufrufermethode.

Die potenziellen Leistungsvorteile sind nicht so unbedeutend, wenn Sie alles berücksichtigen, was dazu gehört:

  1. Die JVM muss die ArraysKlasse nicht auflösen und bei Bedarf laden und initialisieren. Dies ist ein nicht trivialer Prozess, bei dem die JVM mehrere Schritte ausführt. Zunächst wird der Klassenlader überprüft, um festzustellen, ob die Klasse bereits geladen ist. Dies geschieht jedes Mal, wenn eine Methode aufgerufen wird. Natürlich sind hier Optimierungen erforderlich, aber es sind noch einige Anstrengungen erforderlich. Wenn die Klasse nicht geladen ist, muss die JVM den teuren Prozess des Ladens durchlaufen, den Bytecode überprüfen, andere erforderliche Abhängigkeiten auflösen und schließlich eine statische Initialisierung der Klasse durchführen (was beliebig teuer sein kann). Vorausgesetzt, das HashMapist eine solche Kernklasse, und dasArrays solche Kernklasse handelt es sich um eine so große Klasse handelt (mehr als 3600 Zeilen), kann die Vermeidung dieser Kosten zu spürbaren Einsparungen führen.

  2. Da es keinen Arrays.fill(...)Methodenaufruf gibt, muss die JVM nicht entscheiden, ob / wann die Methode in den Körper des Aufrufers eingefügt werden soll. Da HashMap#clear()die JVM häufig aufgerufen wird, führt sie schließlich das Inlining durch, was eine JIT-Neukompilierung der clearMethode erfordert . Ohne Methodenaufrufe,clearWird immer mit Höchstgeschwindigkeit ausgeführt (einmal anfänglich JITed).

Ein weiterer Vorteil des Nichtaufrufens von Methoden Arraysbesteht darin, dass das Abhängigkeitsdiagramm im java.utilPaket vereinfacht wird , da eine Abhängigkeit entfernt wird.


1

Ich werde hier im Dunkeln schießen ...

Ich vermute, dass es geändert wurde, um den Grundstein für die Spezialisierung zu legen (auch bekannt als Generika über primitive Typen). Vielleicht (und ich bestehe vielleicht darauf ) soll diese Änderung den Übergang zu Java 10 erleichtern, falls die Spezialisierung Teil des JDK ist.

Wenn man sich den Blick Status des Dokuments Spezialisierung , Sprache Einschränkungen Abschnitt, sagt er folgendes:

Da alle Typvariablen sowohl Werttypen als auch Referenztypen annehmen können, gelten für die Typüberprüfungsregeln solche Typvariablen (im Folgenden "avars"). Zum Beispiel für ein avar T:

  • Null kann nicht in eine Variable konvertiert werden, deren Typ T ist
  • T kann nicht mit null verglichen werden
  • T kann nicht in Objekt konvertiert werden
  • T [] kann nicht in Objekt [] konvertiert werden
  • ...

(Der Schwerpunkt liegt bei mir).

Und weiter oben im Abschnitt " Specializer-Transformationen " heißt es:

Wenn Sie eine beliebige generische Klasse spezialisieren, führt der Spezialisierer eine Reihe von Transformationen durch, von denen die meisten lokalisiert sind, einige jedoch eine globale Ansicht einer Klasse oder Methode erfordern, einschließlich:

  • ...
  • Die Substitution von Typvariablen und das Mangeln von Namen werden für die Signaturen aller Methoden durchgeführt
  • ...

Später, gegen Ende des Dokuments, heißt es im Abschnitt Weitere Untersuchungen :

Während unsere Experimente gezeigt haben, dass eine Spezialisierung auf diese Weise praktisch ist, sind viel mehr Untersuchungen erforderlich. Insbesondere müssen wir eine Reihe gezielter Experimente durchführen, die auf beliebige JDK-Kernbibliotheken abzielen, insbesondere Sammlungen und Streams.


Nun zur Änderung ...

Wenn die Arrays.fill(Object[] array, Object value)Methode spezialisiert werden soll, sollte sich ihre Signatur in ändern Arrays.fill(T[] array, T value). Dieser Fall ist jedoch speziell im Abschnitt ( bereits erwähnte) Sprachbeschränkungen aufgeführt (dies würde die hervorgehobenen Elemente verletzen). Also vielleicht jemand entschieden , dass es besser wäre, nicht aus der Verwendung HashMap.clear()Methode, vor allem , wenn valueist null.


0

Es gibt keinen tatsächlichen Unterschied in der Funktionalität zwischen der Schleife der 2-Version. Arrays.fillmacht genau das gleiche.

Die Entscheidung, ob Sie es verwenden oder nicht, muss also nicht unbedingt als Fehler angesehen werden. Es ist dem Entwickler überlassen, zu entscheiden, wann es um diese Art von Mikromanagement geht.

Für jeden Ansatz gibt es zwei separate Bedenken:

  • Durch die Verwendung von Arrays.fillwird der Code weniger ausführlich und lesbarer.
  • HashMapEs ist tatsächlich eine bessere Option, die Leistung des Codes (wie in Version 8) direkt zu wiederholen. Während der Aufwand für das Einfügen der ArraysKlasse vernachlässigbar ist, kann er geringer werden, wenn es um etwas geht, das so weit verbreitet ist, wie HashMapwenn jede Leistungsverbesserung einen großen Effekt hat (stellen Sie sich die geringste Reduzierung des Platzbedarfs einer HashMap in einer vollständigen Webanwendung vor). Berücksichtigen Sie die Tatsache, dass die Arrays-Klasse nur für diese eine Schleife verwendet wurde. Die Änderung ist klein genug, um die klare Methode nicht weniger lesbar zu machen.

Der genaue Grund kann nicht herausgefunden werden, ohne den Entwickler zu fragen, der dies tatsächlich getan hat. Ich vermute jedoch, dass es sich entweder um einen Fehler oder um eine kleine Verbesserung handelt. bessere Option.

Meiner Meinung nach kann es als Verbesserung angesehen werden, wenn auch nur durch Zufall.


Ihr Benchmark ist interessant, kann aber das Ergebnis der Verwendung einer neueren JVM sein. Versuchen Sie, Kopien der Quell-Snippets in Java 8 JVM auszuführen.
Bohemian

7
Ihr Benchmark sieht nicht richtig aus (wo ist das Aufwärmen? Wie viele Läufe haben Sie durchgeführt usw.) - und 11 oder 18 ms liegen auf einigen Systemen nahe an der Auflösung von currentTimeMillis. Also würde ich diesen Ergebnissen nicht sehr vertrauen ...
Assylias

In der Tat ist es ein bisschen skizzenhaft und muss nicht unbedingt genau auf diese Änderung hinweisen. Wird mit den genauen Codefragmenten aktualisiert. Bei den Läufen waren es ungefähr 20 mit einer Variation von + -1 ms.
Laurentiu L.
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.