Trong khi điều tra một cuộc tranh luận nhỏ về việc sử dụng "" + n
và Integer.toString(int)
chuyển đổi một số nguyên nguyên thủy thành một chuỗi, tôi đã viết microbenchmark JMH này :
@Fork(1)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@State(Scope.Benchmark)
public class IntStr {
protected int counter;
@GenerateMicroBenchmark
public String integerToString() {
return Integer.toString(this.counter++);
}
@GenerateMicroBenchmark
public String stringBuilder0() {
return new StringBuilder().append(this.counter++).toString();
}
@GenerateMicroBenchmark
public String stringBuilder1() {
return new StringBuilder().append("").append(this.counter++).toString();
}
@GenerateMicroBenchmark
public String stringBuilder2() {
return new StringBuilder().append("").append(Integer.toString(this.counter++)).toString();
}
@GenerateMicroBenchmark
public String stringFormat() {
return String.format("%d", this.counter++);
}
@Setup(Level.Iteration)
public void prepareIteration() {
this.counter = 0;
}
}
Tôi đã chạy nó với các tùy chọn JMH mặc định với cả máy ảo Java tồn tại trên máy Linux của tôi (Mageia 4 64-bit cập nhật, CPU Intel i7-3770, RAM 32 GB). JVM đầu tiên là JVM được cung cấp với Oracle JDK 8u5 64-bit:
java version "1.8.0_05"
Java(TM) SE Runtime Environment (build 1.8.0_05-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.5-b02, mixed mode)
Với JVM này, tôi đã nhận được khá nhiều thứ mà tôi mong đợi:
Benchmark Mode Samples Mean Mean error Units
b.IntStr.integerToString thrpt 20 32317.048 698.703 ops/ms
b.IntStr.stringBuilder0 thrpt 20 28129.499 421.520 ops/ms
b.IntStr.stringBuilder1 thrpt 20 28106.692 1117.958 ops/ms
b.IntStr.stringBuilder2 thrpt 20 20066.939 1052.937 ops/ms
b.IntStr.stringFormat thrpt 20 2346.452 37.422 ops/ms
Tức là sử dụng StringBuilder
lớp chậm hơn do chi phí tạo StringBuilder
đối tượng và nối thêm một chuỗi trống. Việc sử dụng String.format(String, ...)
thậm chí còn chậm hơn, theo thứ tự độ lớn hoặc hơn.
Mặt khác, trình biên dịch do phân phối cung cấp dựa trên OpenJDK 1.7:
java version "1.7.0_55"
OpenJDK Runtime Environment (mageia-2.4.7.1.mga4-x86_64 u55-b13)
OpenJDK 64-Bit Server VM (build 24.51-b03, mixed mode)
Kết quả ở đây thật thú vị :
Benchmark Mode Samples Mean Mean error Units
b.IntStr.integerToString thrpt 20 31249.306 881.125 ops/ms
b.IntStr.stringBuilder0 thrpt 20 39486.857 663.766 ops/ms
b.IntStr.stringBuilder1 thrpt 20 41072.058 484.353 ops/ms
b.IntStr.stringBuilder2 thrpt 20 20513.913 466.130 ops/ms
b.IntStr.stringFormat thrpt 20 2068.471 44.964 ops/ms
Tại sao lại StringBuilder.append(int)
xuất hiện nhanh hơn nhiều với JVM này? Nhìn vào StringBuilder
mã nguồn của lớp không có gì đặc biệt thú vị - phương thức được đề cập gần như giống hệt Integer#toString(int)
. Điều thú vị là, việc bổ sung kết quả của Integer.toString(int)
( stringBuilder2
microbenchmark) dường như không nhanh hơn.
Sự khác biệt về hiệu suất này có phải là một vấn đề với bộ khai thác thử nghiệm không? Hay OpenJDK JVM của tôi có chứa các tối ưu hóa có thể ảnh hưởng đến mã (anti) -pattern cụ thể này?
BIÊN TẬP:
Để so sánh rõ hơn, tôi đã cài đặt Oracle JDK 1.7u55:
java version "1.7.0_55"
Java(TM) SE Runtime Environment (build 1.7.0_55-b13)
Java HotSpot(TM) 64-Bit Server VM (build 24.55-b03, mixed mode)
Kết quả tương tự như của OpenJDK:
Benchmark Mode Samples Mean Mean error Units
b.IntStr.integerToString thrpt 20 32502.493 501.928 ops/ms
b.IntStr.stringBuilder0 thrpt 20 39592.174 428.967 ops/ms
b.IntStr.stringBuilder1 thrpt 20 40978.633 544.236 ops/ms
Có vẻ như đây là vấn đề Java 7 so với Java 8 chung chung hơn. Có lẽ Java 7 đã tối ưu hóa chuỗi tích cực hơn?
CHỈNH SỬA 2 :
Để hoàn thiện, đây là các tùy chọn máy ảo liên quan đến chuỗi cho cả hai JVM này:
Đối với Oracle JDK 8u5:
$ /usr/java/default/bin/java -XX:+PrintFlagsFinal 2>/dev/null | grep String
bool OptimizeStringConcat = true {C2 product}
intx PerfMaxStringConstLength = 1024 {product}
bool PrintStringTableStatistics = false {product}
uintx StringTableSize = 60013 {product}
Đối với OpenJDK 1.7:
$ java -XX:+PrintFlagsFinal 2>/dev/null | grep String
bool OptimizeStringConcat = true {C2 product}
intx PerfMaxStringConstLength = 1024 {product}
bool PrintStringTableStatistics = false {product}
uintx StringTableSize = 60013 {product}
bool UseStringCache = false {product}
Các UseStringCache
tùy chọn được loại bỏ trong Java 8 không có thay thế, vì vậy tôi nghi ngờ mà làm cho bất kỳ sự khác biệt. Phần còn lại của các tùy chọn dường như có cùng cài đặt.
CHỈNH SỬA 3:
Một so sánh side-by-side của mã nguồn của AbstractStringBuilder
, StringBuilder
và Integer
các lớp học từ src.zip
tập tin của tiết lộ gì noteworty. Ngoài rất nhiều thay đổi về thẩm mỹ và tài liệu, Integer
giờ đây đã có một số hỗ trợ cho các số nguyên không dấu và StringBuilder
đã được cấu trúc lại một chút để chia sẻ nhiều mã hơn StringBuffer
. Không có thay đổi nào trong số này dường như ảnh hưởng đến các đường dẫn mã được sử dụng StringBuilder#append(int)
, mặc dù tôi có thể đã bỏ lỡ điều gì đó.
So sánh mã lắp ráp được tạo cho IntStr#integerToString()
và IntStr#stringBuilder0()
thú vị hơn nhiều. Bố cục cơ bản của mã được tạo cho IntStr#integerToString()
là tương tự cho cả hai JVM, mặc dù Oracle JDK 8u5 có vẻ tích cực hơn trong nội tuyến một số lệnh gọi trong Integer#toString(int)
mã. Có một sự tương ứng rõ ràng với mã nguồn Java, ngay cả đối với một người có kinh nghiệm lắp ráp tối thiểu.
IntStr#stringBuilder0()
Tuy nhiên, mã lắp ráp cho hoàn toàn khác nhau. Mã được tạo bởi Oracle JDK 8u5 một lần nữa liên quan trực tiếp đến mã nguồn Java - tôi có thể dễ dàng nhận ra cùng một bố cục. Ngược lại, mã do OpenJDK 7 tạo ra hầu như không thể nhận ra đối với mắt chưa qua đào tạo (như của tôi). Lời new StringBuilder()
gọi dường như đã bị loại bỏ, cũng như việc tạo mảng trong hàm StringBuilder
tạo. Thêm vào đó, plugin trình tháo gỡ không thể cung cấp nhiều tham chiếu đến mã nguồn như trong JDK 8.
Tôi giả định rằng đây là kết quả của một lần vượt qua tối ưu hóa tích cực hơn nhiều trong OpenJDK 7, hoặc nhiều hơn có thể là kết quả của việc chèn mã cấp thấp viết tay cho các StringBuilder
hoạt động nhất định . Tôi không chắc tại sao tối ưu hóa này không xảy ra trong quá trình triển khai JVM 8 của tôi hoặc tại sao các tối ưu hóa tương tự không được triển khai Integer#toString(int)
trong JVM 7. Tôi đoán ai đó quen thuộc với các phần liên quan của mã nguồn JRE sẽ phải trả lời những câu hỏi này ...
new StringBuilder().append(this.counter++).toString();
và thử nghiệm thứ ba vớireturn "" + this.counter++;
?