Ich habe kürzlich
einen Artikel über die Optimierung der Leistung von Java-Code gelesen - insbesondere über die Verkettung von Zeichenfolgen. Die Frage blieb dabei: Warum läuft das Programm bei Verwendung von StringBuilder im Code unter dem Schnitt langsamer als bei einfacher Hinzufügung? In diesem Fall wird + = während der Kompilierung zu StringBuilder.append () -Aufrufen.
Ich hatte sofort den Wunsch, das Problem zu lösen.
Dann kamen alle meine Überlegungen auf die Tatsache an, dass dies unerklärliche Magie innerhalb der JVM ist, und ich gab es auf, zu versuchen, zu erkennen, was geschah. Während der nächsten Diskussion über die Unterschiede zwischen Plattformen in der Geschwindigkeit der Arbeit mit Strings entschied sich mein Freund
yegorf1 jedoch herauszufinden, warum und wie genau diese Magie geschieht.
Oracle Java SE
upd: Tests wurden unter Java 8 durchgeführtDie naheliegende Lösung besteht darin, den Quellcode im Bytecode zu sammeln und dann seinen Inhalt zu überprüfen. Also haben wir es getan. In den Kommentaren gab
es Vorschläge, dass Beschleunigung mit Optimierung zusammenhängt - konstante Linien sollten offensichtlich auf Kompilierungsebene zusammengeklebt werden. Es stellte sich heraus, dass dies nicht so ist. Hier ist ein Teil des mit javap dekompilierten Bytecodes:
public java.lang.String stringAppend(); Code: 0: ldc #2
Möglicherweise stellen Sie fest, dass keine Optimierungen vorgenommen wurden. Seltsam, nicht wahr? Okay, sehen wir uns den Bytecode der zweiten Funktion an.
public java.lang.String stringAppendBuilder(); Code: 0: new #3
Auch hier keine Optimierungen? Schauen wir uns außerdem die Anweisungen zu 8, 14 und 15 Bytes an. Dort passiert etwas Seltsames: Zuerst wird ein Verweis auf ein Objekt der StringBuilder-Klasse auf den Stapel geladen, dann vom Stapel geworfen und neu geladen. Die einfachste Lösung fällt mir ein:
public java.lang.String stringAppendBuilder(); Code: 0: new #41
Wenn wir unnötige Anweisungen wegwerfen, erhalten wir einen Code, der 1,5-mal schneller funktioniert als die stringAppend-Version, in der diese Optimierung bereits durchgeführt wurde. Der Fehler der „Magie“ ist also der unvollständige Bytecode-Compiler, der keine recht einfachen Optimierungen durchführen kann.
Android ART
upd: der code wurde unter sdk 28 mit den release buildtools erstelltEs stellte sich also heraus, dass das Problem mit der Implementierung des Java-Compilers im Bytecode für die Stack-JVM zusammenhängt. Hier erinnerten wir uns an die Existenz von
ART, die Teil des Android Open Source-Projekts ist . Diese virtuelle Maschine, oder besser gesagt der Bytecode-Compiler, in nativen Code, wurde unter den Bedingungen einer
Klage von Oracle geschrieben, was uns allen Grund zu der Annahme gibt, dass die Unterschiede zur Oracle-Implementierung erheblich sind. Aufgrund der Besonderheiten von ARM-Prozessoren ist diese virtuelle Maschine außerdem ein Register und kein Stapel.
Werfen wir einen Blick auf Smali (eine der Bytecode-Darstellungen unter ART):
# virtual methods .method public stringAppend()Ljava/lang/String; .registers 4 .prologue .line 6 const-string/jumbo v0, "foo" .line 7 .local v0, "s":Ljava/lang/String; new-instance v1, Ljava/lang/StringBuilder; invoke-direct {v1}, Ljava/lang/StringBuilder;-><init>()V invoke-virtual {v1, v0}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; move-result-object v1 const-string/jumbo v2, ", bar" invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; move-result-object v1
In dieser Variante stringAppendBuilder gibt es keine Probleme mehr mit dem Stack - die Maschine ist ein Register und sie können im Prinzip nicht auftreten. Dies beeinträchtigt jedoch nicht die Existenz absolut magischer Dinge:
move-result-object v1
Diese Zeichenfolge in stringAppend bewirkt nichts - der Link zum benötigten StringBuilder-Objekt befindet sich bereits im v1-Register. Es wäre logisch anzunehmen, dass stringAppend langsamer arbeitet. Dies wird experimentell bestätigt - das Ergebnis ähnelt dem Ergebnis der "gepatchten" Version des Programms für die Stapel-JVM: StringBuilder arbeitet fast eineinhalb Mal schneller.