2012-12-08 6 views
8

Ich überlege, einen RFE (Antrag auf Erweiterung) an die Oracle Bug Database zu senden, die die String-Verkettungsleistung erheblich erhöhen soll. Aber bevor ich das tue, möchte ich gerne die Kommentare der Experten hören, ob das Sinn macht.Kann java.lang.String.concat verbessert werden?

Die Idee basiert auf der Tatsache, dass die vorhandene String.concat (String) auf 2 Zeichenfolgen zweimal schneller als StringBuilder funktioniert. Das Problem besteht darin, dass es keine Methode gibt, 3 oder mehr Zeichenfolgen zu verketten. Externe Methoden können das nicht, da String.concat einen privaten Paketkonstruktor String(int offset, int count, char[] value) verwendet, der das char-Array nicht kopiert, sondern direkt verwendet. Dies gewährleistet eine hohe String.concat-Leistung. Im selben Paket kann StringBuilder diesen Konstruktor immer noch nicht verwenden, da dann das char-Array des Strings für Änderungen offen gelegt wird.

Ich schlage vor, die folgenden Methoden zu String

public static String concat(String s1, String s2) 
public static String concat(String s1, String s2, String s3) 
public static String concat(String s1, String s2, String s3, String s4) 
public static String concat(String s1, String s2, String s3, String s4, String s5) 
public static String concat(String s1, String... array) 

Nachricht hinzuzufügen: diese Art von Überlastung ist für Effizienz in EnumSet.of, verwendet.

Dies ist die Implementierung eines des Verfahrens, andere funktionieren auf die gleiche Art und Weise

public final class String { 
    private final char value[]; 
    private final int count; 
    private final int offset; 

    String(int offset, int count, char value[]) { 
     this.value = value; 
     this.offset = offset; 
     this.count = count; 
    } 

    public static String concat(String s1, String s2, String s3) { 
     char buf[] = new char[s1.count + s2.count + s3.count]; 
     System.arraycopy(s1.value, s1.offset, buf, 0, s1.count); 
     System.arraycopy(s2.value, s2.offset, buf, s1.count, s2.count); 
     System.arraycopy(s3.value, s3.offset, buf, s1.count + s2.count, s3.count); 
     return new String(0, buf.length, buf); 
    } 

Auch nach diesen Methoden String hinzugefügt werden, Java-Compiler für

String s = s1 + s2 + s3; 

Lage sein wird, Aufbau effizienter

String s = String.concat(s1, s2, s3); 

statt Strom ineffizient

String s = (new StringBuilder(String.valueOf(s1))).append(s2).append(s3).toString(); 

UPDATE Leistungstest. Ich lief es auf meinem Notebook Intel Celeron 925, Verkettung von 3 Strings, meine String2-Klasse emuliert genau, wie es in echten java.lang.String wäre. Stringlängen werden so gewählt, dass StringBuilder unter den ungünstigsten Bedingungen platziert werden kann, dh wenn die interne Pufferkapazität bei jedem Append erweitert werden muss, während concat immer nur char [] erstellt.

public class String2 { 
    private final char value[]; 
    private final int count; 
    private final int offset; 

    String2(String s) { 
     value = s.toCharArray(); 
     offset = 0; 
     count = value.length; 
    } 

    String2(int offset, int count, char value[]) { 
     this.value = value; 
     this.offset = offset; 
     this.count = count; 
    } 

    public static String2 concat(String2 s1, String2 s2, String2 s3) { 
     char buf[] = new char[s1.count + s2.count + s3.count]; 
     System.arraycopy(s1.value, s1.offset, buf, 0, s1.count); 
     System.arraycopy(s2.value, s2.offset, buf, s1.count, s2.count); 
     System.arraycopy(s3.value, s3.offset, buf, s1.count + s2.count, s3.count); 
     return new String2(0, buf.length, buf); 
    } 

    public static void main(String[] args) { 
     String s1 = "1"; 
     String s2 = "11111111111111111"; 
     String s3 = "11111111111111111111111111111111111111111"; 
     String2 s21 = new String2(s1); 
     String2 s22 = new String2(s2); 
     String2 s23 = new String2(s3); 
     long t0 = System.currentTimeMillis(); 
     for (int i = 0; i < 1000000; i++) { 
      String2 s = String2.concat(s21, s22, s23); 
//   String s = new StringBuilder(s1).append(s2).append(s3).toString(); 
     } 
     System.out.println(System.currentTimeMillis() - t0); 
    } 
} 

auf 1.000.000 Iterationen die Ergebnisse sind:

version 1 = ~200 ms 
version 2 = ~400 ms 
+0

String Buffer kann viel mehr schneller sein, dass Sie –

Antwort

7

Tatsache ist, die Anwendungsfälle, für die die Leistung eines einzelnen Zeichenfolge Verkettung Ausdruck zählt, sind nicht so üblich. In den meisten Fällen, in denen die Performance durch eine String-Verkettung gebunden ist, geschieht dies in einer Schleife, wobei das Endprodukt Schritt für Schritt aufgebaut wird, und in diesem Zusammenhang ist die änderbare StringBuilder ein klarer Gewinner.Das ist, warum ich nicht viele Möglichkeiten für einen Vorschlag sehen, die durch dazwischen in die grundlegenden String Klasse eine Minderheit betreffen optimiert. Aber wie auch immer, so weit wie Leistung zu vergleichen, Ihr Ansatz hat einen bedeutenden Vorsprung:

import com.google.caliper.Runner; 
import com.google.caliper.SimpleBenchmark; 

public class Performance extends SimpleBenchmark 
{ 
    final Random rnd = new Random(); 
    final String as1 = "aoeuaoeuaoeu", as2 = "snthsnthnsth", as3 = "3453409345"; 
    final char[] c1 = as1.toCharArray(), c2 = as2.toCharArray(), c3 = as3.toCharArray(); 

    public static char[] concat(char[] s1, char[] s2, char[] s3) { 
    char buf[] = new char[s1.length + s2.length + s3.length]; 
    System.arraycopy(s1, 0, buf, 0, s1.length); 
    System.arraycopy(s2, 0, buf, s1.length, s2.length); 
    System.arraycopy(s3, 0, buf, s1.length + s2.length, s3.length); 
    return buf; 
    } 

    public static String build(String s1, String s2, String s3) { 
    final StringBuilder b = new StringBuilder(s1.length() + s2.length() + s3.length()); 
    b.append(s1).append(s2).append(s3); 
    return b.toString(); 
    } 

    public static String plus(String s1, String s2, String s3) { 
    return s1 + s2 + s3; 
    } 

    public int timeConcat(int reps) { 
    int tot = rnd.nextInt(); 
    for (int i = 0; i < reps; i++) tot += concat(c1, c2, c3).length; 
    return tot; 
    } 

    public int timeBuild(int reps) { 
    int tot = rnd.nextInt(); 
    for (int i = 0; i < reps; i++) tot += build(as1, as2, as3).length(); 
    return tot; 
    } 

    public int timePlus(int reps) { 
    int tot = rnd.nextInt(); 
    for (int i = 0; i < reps; i++) tot += plus(as1, as2, as3).length(); 
    return tot; 
    } 

    public static void main(String... args) { 
    Runner.main(Performance.class, args); 
    } 
} 

Ergebnis:

0% Scenario{vm=java, trial=0, benchmark=Concat} 65.81 ns; σ=2.56 ns @ 10 trials 
33% Scenario{vm=java, trial=0, benchmark=Build} 102.94 ns; σ=2.27 ns @ 10 trials 
67% Scenario{vm=java, trial=0, benchmark=Plus} 160.14 ns; σ=2.94 ns @ 10 trials 

benchmark ns linear runtime 
    Concat 65.8 ============ 
    Build 102.9 =================== 
    Plus 160.1 ============================== 
+1

Vielen Dank. Analysiere und füge meinem Beitrag einige Benchmarks hinzu. –

4

Wenn Sie wollen, dass sie Sie ernst nehmen, müssen Sie die harte Arbeit voll tun Implementierung, Test und Ihre vorgeschlagene Änderung gründlich Benchmarking. Und eine vollständige Implementierung würde die Änderungen an dem Java-Compiler beinhalten, um Bytecodes zu emittieren, um Ihre Methoden zu verwenden.

die Ergebnisse schreiben, und dann als Patch zu OpenJDK die Code-Änderungen einreichen 7 oder 8.

Mein Eindruck ist, dass die Java-Entwickler haben nicht die Ressourcen für Optimierungen spekulative Ideen auszuprobieren, wie diese ein. Ein RFE ohne Benchmarking-Ergebnisse und Code-Patches ist unwahrscheinlich, Aufmerksamkeit zu erhalten ...

+0

Recht auf achive wollen, habe ich versucht, schon einige Bugs einreichen (oder was ich denke, um Fehler zu sein) zu Bug-Datenbank. Ab jetzt nur noch einen Versuch, Javadoc Bug des Deque hat http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7178639 gelungen. Das war unmöglich zu verwerfen –

1

Es ist immer in Ordnung, sie zu fragen, keine Sorge.

Ich würde nicht so viele überladene Versionen haben. In EnumSet kann das Speichern erheblich sein; nicht so in String.

Eigentlich denke ich, eine statische Methode eine beliebige Anzahl von args ermöglicht besser ist

public static String join(String... strings) 

da die Anzahl der Argumente bei der Kompilierung unbekannt sein kann.

+0

Die Idee der mehrfach überladenen Methoden gehört Josh Bloch, es "vermeidet Kosten der Array-Zuweisung, wenn weniger als n Args". I.e. Join ("1", "2") bedeutet effektiv join (new String [] {"1", "2"}), ein zusätzliches Array wird erstellt. Da das ganze Thema von Performance handelt, scheint Josh Blocks Idiom relevant zu sein. –

+0

In Enumset sind die args einfache Atome. In String sind die args kopiert werden, so dass der Aufwand für Vararg ist relativ gering. – irreputable

Verwandte Themen