8

文字列連結のパフォーマンスを大幅に向上させると思われるRFE(拡張要求)をOracleBugDatabaseに送信することを検討しています。しかし、それを行う前に、それが理にかなっているかどうかについての専門家のコメントを聞きたいと思います。

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

次のメソッドを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) 

注:この種のオーバーロードは、効率を上げるためにEnumSet.ofで使用されます。

これは、メソッドの1つの実装であり、他のメソッドは同じように機能します

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に追加された後、Javaコンパイラは

String s = s1 + s2 + s3;

効率的に構築できるようになります

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

現在の非効率の代わりに

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

UPDATEパフォーマンステスト。私はそれをノートブックのIntelCeleron925で実行し、3つの文字列を連結しました。私のString2クラスは、実際のjava.lang.Stringでの動作を正確にエミュレートします。文字列の長さは、StringBuilderを最も不利な条件に置くように選択されます。つまり、各追加で内部バッファ容量を拡張する必要がある場合ですが、concatは常にchar[]を1回だけ作成します。

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

1,000,000回の反復で、結果は次のようになります。

version 1 = ~200 ms
version 2 = ~400 ms
4

3 に答える 3

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 ==============================
于 2012-12-08T15:51:58.437 に答える
4

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

結果を書き留めてから、コードの変更を OpenJDK 7 または 8 へのパッチとして送信します。

私の印象では、Java 開発者には、このような最適化のための投機的なアイデアを試すためのリソースがありません。ベンチマーク結果とコード パッチのない RFE は、注目される可能性は低いです...

于 2012-12-08T14:44:39.720 に答える
1

彼らに尋ねるのはいつでも大丈夫です、心配しないでください。

オーバーロードされたバージョンはそれほど多くありません。EnumSet では、大幅な節約が可能です。String ではそうではないでしょう。

実際には、任意の数の引数を許可する静的メソッドの方が優れていると思います

    public static String join(String... strings)

引数の数はコンパイル時に不明な場合があるためです。

于 2012-12-08T17:04:29.410 に答える