2012-12-08 9 views
8

文字列連結のパフォーマンスを大幅に向上させる予定のOracle Bug DatabaseにRFE(拡張要求)を提出することを検討しています。しかし、私がそれをする前に、それが意味を成すかどうかについて専門家の意見を聞きたいと思います。java.lang.String.concatを改善できますか?

このアイデアは、既存のString.concat(String)がStringBuilderより2つの文字列で2倍速く動作するという事実に基づいています。問題は、3つ以上の文字列を連結する方法がないことです。 String.concatはcharプライベートコンストラクタString(int offset, int count, char[] value)を使用するため、char配列はコピーされずに直接使用されるため、外部メソッドではこれを行うことはできません。これにより、高いString.concatパフォーマンスが保証されます。同じパッケージ内にあれば、StringBuilderはこのコンストラクターを使用できません。これは、Stringのchar配列が変更のために公開されるためです。

が、私は文字列

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) 

に次のメソッドを追加することをお勧め:オーバーロードのこの種のは、効率のために、EnumSet.ofに使用されています。

これは方法の一つの実装である、他の人が

同じように動作し
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); 
    } 

また、これらのメソッドはStringに追加された後、

String s = s1 + s2 + s3; 

のためのJavaコンパイラのことができるようになります効率的に構築する

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

現在非効率ではなく

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

UPDATEパフォーマンステスト。私は私のノートブックのIntel Celeron 925で実行しました.3つの文字列を連結して、私のString2クラスは実際のjava.lang.Stringにどうなるか正確にエミュレートします。ストリングの長さは、StringBuilderを最も不利な条件に置くように選択されます。つまり、concatは常にchar []を一度しか作成しませんが、それぞれの追加時に内部バッファー容量を拡張する必要があります。 1,000,000回の反復で

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); 
    } 
} 

結果は以下のとおりです。

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

文字列バッファーをもっと速くすることができます。 –

答えて

7

事実、単一文字列連結式のパフォーマンスはそれほど共通しない使用例です。パフォーマンスが文字列連結によって縛られるほとんどの場合、それはループで発生し、最終製品をステップバイステップで構築します。そのコンテキストでは、可変性StringBuilderは明らかな勝者です。これが、基本的なStringクラスに介入することによって、少数派の心配を最適化する提案に対する多くの視点が見えない理由です。しかし、いずれにせよ、限りのパフォーマンスを比較するなど、あなたのアプローチは、重要なエッジを持っている:

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); 
    } 
} 

結果:

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

多くのありがとう。分析し、私のポストにいくつかのベンチマークを追加します。 –

4

あなたは彼らが真剣にあなたを取るしたい場合は、あなたが完全に、実装テストと徹底的に提案された変更をベンチマークのハードワークを行う必要があります。完全な実装には、メソッドを使用するバイトコードを発行するJavaコンパイラの変更が含まれます。

結果を記述し、OpenJDKの7または8にパッチとして、コードの変更を提出

私の印象は、Java開発者は、このような最適化のための投機的なアイデアを試すためのリソースを持っていないということです1。ベンチマーク結果とコードパッチのないRFEは注意を払うことはほとんどありません。

+0

私は既にいくつかのバグ(バグと思われるもの)をバグデータベースに提出しようとしました。今のところ、DequeのJavadocバグは、http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7178639に引き継がれています。それは拒絶することが不可能でした –

1

それは心配しないで、それらを求めることは常にOKです。

多くのオーバーロードされたバージョンはありません。 EnumSetでは、節約が重要です。ストリングのようではありません。

実際に私は、引数の数がコンパイル時に未知の可能性があるので引数の任意の数を許可する静的メソッドが

public static String join(String... strings) 

優れていると思います。

+0

複数のオーバーロードされたメソッドのアイデアはJosh Blochに属しています。「n args未満であれば配列の割り当てにかかるコストを避けることができます。私。 join( "1"、 "2")は効果的に結合を意味します(新しいString [] {"1"、 "2"})、余分な配列が作成されます。このジョシュブロックのイディオムは関連性が高いようです。 –

+0

Enumsetでは、argsは単純なアトムです。 Stringでは、引数はコピーされるので、varargのオーバーヘッドは比較的無視できます。 – irreputable

関連する問題