2012-12-08 3 views
8

문자열 연결 성능을 크게 향상시킬 것으로 예상되는 Oracle Bug Database에 RFE (개선 요청)를 제출할 것을 고려 중입니다. 그러나 그것을하기 전에 전문가의 의견을 듣고 싶습니다. 기존 String.concat (문자열)의 StringBuilder보다 2 개 문자열에 두 배 빠르게 작동java.lang.String.concat를 개선 할 수 있습니까?

아이디어는 사실에 근거한다. 문제는 3 개 이상의 문자열을 연결할 방법이 없다는 것입니다. String.concat가 문자 배열을 복사하지 않고 직접 사용하는 패키지 개인 생성자 String(int offset, int count, char[] value)을 사용하기 때문에 외부 메서드에서이 작업을 수행 할 수 없습니다. 이것은 높은 String.concat 성능을 보장합니다. StringBuilder는 문자열의 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에 사용됩니다.

String s = s1 + s2 + s3; 

은 수있을 것입니다 이러한 방법은 문자열, 자바 컴파일러에 추가 된 후에

이 방법 중 하나의 구현이고, 다른 사람은,

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 s = String.concat(s1, s2, s3); 

대신 현재 비효율적 인

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

업데이트 성능 테스트. 내 노트북 ​​인텔 셀러론 925, 3 문자열의 연결에서 그것을 실행, 내 String2 클래스는 정확히 어떻게 진짜 java.lang.String에있을 것이라고 에뮬레이트합니다. 문자열 길이는 StringBuilder를 가장 불리한 조건, 즉 각 추가시 내부 버퍼 용량을 확장해야하는 경우에 선택되며, concat은 항상 char [] 만 한 번만 생성합니다.

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 
+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 컴파일러의 변경 사항이 포함되어 메소드를 사용하기위한 바이트 코드를 방출합니다.

결과를 기록하고 오픈 JDK 7 또는 8로 패치로 코드 변경 사항을 제출

내 인상은 자바 개발자가이 같은 최적화에 대한 투기 아이디어를 시도하는 자원이 없다는 것입니다 하나. 결과 및 코드 패치를 벤치마킹없이 RFE는 사용 사례가있는 하나의 문자열 연결 표현 문제의 성능이 일반적이지입니다 ...

+0

맞습니다. 이미 버그 데이터베이스 (또는 버그라고 생각하는 것)를 버그 데이터베이스에 제출하려고했습니다. 현재 Deque의 Javadoc 버그는 http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7178639의 성공을 거두었습니다. 그 하나는 거부 할 수 없었다. –

1

그것은 그들에게 항상 괜찮아, 걱정하지 마세요 어쨌든, 지금까지의 성능을 비교로, 당신의 접근 방식은 상당한 우위를 가지고있다.

너무 많은 오버로드 된 버전이 없습니다. EnumSet에서는 저장이 중요 할 수 있습니다. String에서는 그렇지 않습니다.

는 사실은 내가 인수의 수는 컴파일 타임에 알 수 있기 때문에 인수의 수를 허용 정적 방법은 더 나은

public static String join(String... strings) 

생각합니다.

+0

다중 오버로드 된 메소드에 대한 아이디어는 Josh Bloch에 속합니다. "n args보다 작 으면 배열 할당 비용을 피할 수 있습니다." 나는. join ("1", "2")은 효과적으로 조인을 의미합니다 (new String [] { "1", "2"}). 전체 주제가 성과에 관한 것이기 때문에이 Josh Block의 관용어는 관련성이있는 것 같습니다. –

+0

Enumset에서 args는 단순한 원자입니다. String에서는 args가 복사되므로 vararg의 오버 헤드는 상대적으로 무시할 수 있습니다. – irreputable