2012-12-08 31 views
8

Tôi đang xem xét việc gửi một RFE (yêu cầu nâng cao) lên cơ sở dữ liệu Oracle Bug được cho là tăng đáng kể hiệu năng nối chuỗi. Nhưng trước khi tôi làm điều đó tôi muốn nghe ý kiến ​​của các chuyên gia về việc liệu nó có ý nghĩa hay không.Có thể cải thiện java.lang.String.concat không?

Ý tưởng được dựa trên thực tế là String.concat (String) hiện có hoạt động nhanh hơn hai lần trên 2 chuỗi so với StringBuilder. Vấn đề là không có phương pháp để nối 3 hoặc nhiều chuỗi. Các phương thức bên ngoài không thể làm điều này vì String.concat sử dụng một hàm tạo riêng tư gói String(int offset, int count, char[] value) mà không sao chép mảng char mà sử dụng nó trực tiếp. Điều này đảm bảo hiệu suất String.concat cao. Trong cùng một gói StringBuilder vẫn không thể sử dụng hàm tạo này vì sau đó mảng char của String sẽ được hiển thị để sửa đổi.

Tôi đề nghị bổ sung các phương pháp sau để 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) 

Lưu ý: hình thức này quá tải được sử dụng trong EnumSet.of, cho hiệu quả.

Đây là việc thực hiện một trong những phương pháp, những người khác làm việc cùng một cách

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

Ngoài ra, sau khi các phương pháp này được bổ sung vào String, trình biên dịch Java cho

String s = s1 + s2 + s3; 

sẽ có thể xây dựng hiệu quả

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

thay vì hiện tại không hiệu quả

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

CẬP NHẬT Kiểm tra hiệu suất. Tôi chạy nó trên máy tính xách tay Intel Celeron 925, nối 3 chuỗi, lớp String2 của tôi mô phỏng chính xác nó sẽ nằm trong java.lang.String thực sự như thế nào. Độ dài chuỗi được chọn sao cho đặt StringBuilder trong điều kiện không thuận lợi nhất, đó là khi cần mở rộng dung lượng bộ đệm bên trong của nó trên mỗi chắp thêm, trong khi concat luôn tạo char [] chỉ một lần.

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

trên 1.000.000 lặp kết quả là:

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

Bộ đệm chuỗi có thể nhanh hơn rất nhiều mà bạn muốn đạt được –

Trả lời

7

Sự thật là các trường hợp sử dụng biểu thức nối chuỗi đơn không phải là vấn đề chung. Trong hầu hết các trường hợp mà hiệu năng bị ràng buộc bởi chuỗi nối, nó xảy ra trong một vòng lặp, xây dựng bước sản phẩm cuối cùng theo từng bước và trong ngữ cảnh đó, StringBuilder có thể biến đổi là người chiến thắng rõ ràng.Đây là lý do tại sao tôi không thấy nhiều quan điểm cho một đề xuất tối ưu hóa mối quan tâm thiểu số bằng cách can thiệp vào lớp học cơ bản String. Nhưng dù sao, theo như so sánh hiệu suất, cách tiếp cận của bạn không có một lợi thế cạnh quan trọng:

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

Kết quả:

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

Rất cám ơn. Sẽ analize và thêm một số điểm chuẩn vào bài viết của tôi. –

4

Nếu bạn muốn họ thực hiện nghiêm túc bạn, bạn cần phải làm những công việc khó khăn của việc thực hiện đầy đủ, kiểm tra và benchmark triệt để thay đổi đề xuất của bạn. Và việc triển khai đầy đủ sẽ bao gồm các thay đổi đối với trình biên dịch Java để phát ra bytecode để sử dụng các phương thức của bạn.

Viết lên các kết quả, và sau đó gửi những thay đổi mã như một bản vá để OpenJDK 7 hoặc 8.

ấn tượng của tôi là các nhà phát triển Java không có các nguồn lực để thử những ý tưởng đầu cơ cho việc tối ưu như thế này một. RFE không có kết quả điểm chuẩn và các bản vá mã không có khả năng nhận được sự chú ý ...

+0

Phải, tôi đã cố gắng gửi một số lỗi (hoặc những gì tôi nghĩ là lỗi) đến Cơ sở dữ liệu lỗi. Hiện tại chỉ có một lần thử, lỗi Javadoc của Deque, đã thành công http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7178639. Điều đó là không thể từ chối –

1

Nó luôn luôn ok để yêu cầu họ, đừng lo lắng.

Tôi sẽ không có quá nhiều phiên bản quá tải. Trong EnumSet tiết kiệm có thể là đáng kể; không có khả năng như vậy trong String.

Thật sự tôi nghĩ rằng một phương pháp tĩnh cho phép bất kỳ số args là tốt hơn

public static String join(String... strings) 

kể từ khi số args có thể chưa biết tại thời gian biên dịch.

+0

Ý tưởng của nhiều phương thức quá tải thuộc về Josh Bloch, nó "tránh chi phí phân bổ mảng nếu ít hơn n args". I E. tham gia ("1", "2") có nghĩa là tham gia (chuỗi mới [] {"1", "2"}), một mảng phụ được tạo. Vì toàn bộ chủ đề là về hiệu suất, thành ngữ của Josh Block này dường như có liên quan. –

+0

Trong Enumset, arg là các nguyên tử đơn giản. Trong String, các arg sẽ được sao chép, vì vậy chi phí của vararg là tương đối không đáng kể. – irreputable

Các vấn đề liên quan