CẬP NHẬT ĐẦU TIÊN: Trước khi bạn thử điều này trong môi trường sản xuất (không nên dùng), hãy đọc phần này trước: http://www.javaspecialists.eu/archive/Issue237.html
Bắt đầu từ Java 9, giải pháp như được mô tả sẽ không còn hiệu quả nữa , bởi vì bây giờ Java sẽ lưu trữ các chuỗi dưới dạng byte [] theo mặc định.
CẬP NHẬT THỨ HAI: Kể từ 2016-10-25, trên AMDx64 8core và nguồn 1.8 của tôi, không có sự khác biệt giữa việc sử dụng 'charAt' và truy cập trường. Dường như jvm được tối ưu hóa đủ để nội tuyến và hợp lý hóa mọi cuộc gọi 'string.charAt (n)'.
Tất cả phụ thuộc vào độ dài của việc String
được kiểm tra. Nếu, như câu hỏi nói, đó là đối với các chuỗi dài , cách nhanh nhất để kiểm tra chuỗi là sử dụng sự phản chiếu để truy cập vào mặt sau char[]
của chuỗi.
Một điểm chuẩn ngẫu nhiên hoàn toàn với JDK 8 (win32 và win64) trên lõi 64 AMD Phenom II 4 955 @ 3.2 GHZ (ở cả chế độ máy khách và chế độ máy chủ) với 9 kỹ thuật khác nhau (xem bên dưới!) Cho thấy sử dụng String.charAt(n)
là nhanh nhất các chuỗi và việc sử dụng reflection
để truy cập mảng sao lưu Chuỗi nhanh hơn gần gấp đôi đối với các chuỗi lớn.
CUỘC THÍ NGHIỆM
9 kỹ thuật tối ưu hóa khác nhau được thử.
Tất cả nội dung chuỗi được chọn ngẫu nhiên
Thử nghiệm được thực hiện cho kích thước chuỗi theo bội số của hai bắt đầu bằng 0,1,2,4,8,16, v.v.
Các bài kiểm tra được thực hiện 1.000 lần cho mỗi kích thước chuỗi
Các bài kiểm tra được xáo trộn theo thứ tự ngẫu nhiên mỗi lần. Nói cách khác, các bài kiểm tra được thực hiện theo thứ tự ngẫu nhiên mỗi khi chúng được thực hiện, hơn 1000 lần.
Toàn bộ bộ kiểm thử được thực hiện chuyển tiếp và ngược lại, để hiển thị hiệu quả của việc khởi động JVM đối với tối ưu hóa và thời gian.
Toàn bộ bộ được thực hiện hai lần, một lần trong -client
chế độ và khác trong -server
chế độ.
KẾT LUẬN
chế độ -client (32 bit)
Đối với các chuỗi có độ dài từ 1 đến 256 ký tự , gọi string.charAt(i)
chiến thắng với mức xử lý trung bình từ 13,4 triệu đến 588 triệu ký tự mỗi giây.
Ngoài ra, nó nhanh hơn 5,5% (máy khách) và 13,9% (máy chủ) như thế này:
for (int i = 0; i < data.length(); i++) {
if (data.charAt(i) <= ' ') {
doThrow();
}
}
hơn như thế này với một biến độ dài cuối cùng cục bộ:
final int len = data.length();
for (int i = 0; i < len; i++) {
if (data.charAt(i) <= ' ') {
doThrow();
}
}
Đối với các chuỗi dài , độ dài 512 đến 256K ký tự , sử dụng phản xạ để truy cập mảng sao lưu của Chuỗi là nhanh nhất. Kỹ thuật này nhanh gần gấp đôi so với String.charAt (i) (nhanh hơn 178%). Tốc độ trung bình trên phạm vi này là 1,11 tỷ ký tự mỗi giây.
Trường phải được lấy trước thời hạn và sau đó nó có thể được sử dụng lại trong thư viện trên các chuỗi khác nhau. Thật thú vị, không giống như mã ở trên, với quyền truy cập Trường, có một biến có độ dài cuối cùng cục bộ nhanh hơn 9% so với sử dụng 'chars.length' trong kiểm tra vòng lặp. Đây là cách truy cập Trường có thể được thiết lập nhanh nhất:
final Field field = String.class.getDeclaredField("value");
field.setAccessible(true);
try {
final char[] chars = (char[]) field.get(data);
final int len = chars.length;
for (int i = 0; i < len; i++) {
if (chars[i] <= ' ') {
doThrow();
}
}
return len;
} catch (Exception ex) {
throw new RuntimeException(ex);
}
Nhận xét đặc biệt về chế độ máy chủ
Truy cập trường bắt đầu chiến thắng sau chuỗi dài 32 ký tự ở chế độ máy chủ trên máy Java 64 bit trên máy AMD 64 của tôi. Điều đó đã không được nhìn thấy cho đến khi độ dài 512 ký tự trong chế độ máy khách.
Tôi cũng đáng chú ý, khi tôi đang chạy JDK 8 (bản dựng 32 bit) ở chế độ máy chủ, hiệu suất tổng thể chậm hơn 7% cho cả chuỗi lớn và nhỏ. Điều này là với bản dựng 121 tháng 12 năm 2013 của JDK 8 phát hành sớm. Vì vậy, hiện tại, có vẻ như chế độ máy chủ 32 bit chậm hơn chế độ máy khách 32 bit.
Điều đó đang được nói ... có vẻ như chế độ máy chủ duy nhất đáng gọi là trên máy 64 bit. Nếu không, nó thực sự cản trở hiệu suất.
Đối với bản dựng 32 bit chạy -server mode
trên AMD64, tôi có thể nói điều này:
- String.charAt (i) là người chiến thắng rõ ràng tổng thể. Mặc dù trong khoảng từ 8 đến 512 ký tự, đã có người chiến thắng trong số 'mới' 'tái sử dụng' và 'trường'.
- String.charAt (i) nhanh hơn 45% trong chế độ máy khách
- Truy cập trường nhanh gấp đôi đối với các Chuỗi lớn trong chế độ máy khách.
Cũng đáng nói, String.chars () (Luồng và phiên bản song song) là một bức tượng bán thân. Cách chậm hơn bất kỳ cách nào khác. Các Streams
API là một cách khá chậm chạp trong việc thực hiện các hoạt động chuỗi chung.
Danh sách mong muốn
Chuỗi Java có thể có các vị từ chấp nhận các phương thức được tối ưu hóa như chứa (vị ngữ), forEach (người tiêu dùng), forEachWithIndex (người tiêu dùng). Do đó, không cần người dùng biết độ dài hoặc lặp lại các cuộc gọi đến các phương thức String, chúng có thể giúp phân tích cú pháp các thư việnbeep-beep beep
tốc độ .
Hãy cứ mơ ước :)
Chúc mừng chuỗi!
~ SH
Thử nghiệm đã sử dụng 9 phương pháp kiểm tra chuỗi sau đây cho sự hiện diện của khoảng trắng:
"charAt1" - KIỂM TRA NỘI DUNG CHIẾN LƯỢC CÁCH SỬ DỤNG:
int charAtMethod1(final String data) {
final int len = data.length();
for (int i = 0; i < len; i++) {
if (data.charAt(i) <= ' ') {
doThrow();
}
}
return len;
}
"charAt2" - CÙNG NHƯ VẬY NHƯNG SỬ DỤNG String.length () CÀI ĐẶT TẠO MỘT ĐỊA ĐIỂM CUỐI CÙNG CHO LENGTh
int charAtMethod2(final String data) {
for (int i = 0; i < data.length(); i++) {
if (data.charAt(i) <= ' ') {
doThrow();
}
}
return data.length();
}
"stream" - SỬ DỤNG IntStream MỚI của JAVA-8 String VÀ PASS IT MỘT PREDICATE ĐỂ KIỂM TRA
int streamMethod(final String data, final IntPredicate predicate) {
if (data.chars().anyMatch(predicate)) {
doThrow();
}
return data.length();
}
"StreamPara" - CÙNG NHƯ VẬY, NHƯNG OH-LA-LA - ĐI PARALLEL !!!
// avoid this at all costs
int streamParallelMethod(final String data, IntPredicate predicate) {
if (data.chars().parallel().anyMatch(predicate)) {
doThrow();
}
return data.length();
}
"tái sử dụng" - HOÀN LẠI một char REUSABLE [] VỚI NỘI DUNG CHIẾN LƯỢC
int reuseBuffMethod(final char[] reusable, final String data) {
final int len = data.length();
data.getChars(0, len, reusable, 0);
for (int i = 0; i < len; i++) {
if (reusable[i] <= ' ') {
doThrow();
}
}
return len;
}
"new1" - OBTAIN MỘT BẢN SAO MỚI CỦA char [] TỪ CHUINGI
int newMethod1(final String data) {
final int len = data.length();
final char[] copy = data.toCharArray();
for (int i = 0; i < len; i++) {
if (copy[i] <= ' ') {
doThrow();
}
}
return len;
}
"new2" - CÙNG NHƯ TRÊN, NHƯNG SỬ DỤNG "FOR-EACH"
int newMethod2(final String data) {
for (final char c : data.toCharArray()) {
if (c <= ' ') {
doThrow();
}
}
return data.length();
}
"trường1" - FANCY !! L FINH VỰC OBTAIN ĐỂ TIẾP CẬN VỚI char NỘI BỘ CỦA STRING []
int fieldMethod1(final Field field, final String data) {
try {
final char[] chars = (char[]) field.get(data);
final int len = chars.length;
for (int i = 0; i < len; i++) {
if (chars[i] <= ' ') {
doThrow();
}
}
return len;
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
"field2" - CÙNG NHƯ TRÊN, NHƯNG SỬ DỤNG "FOR-EACH"
int fieldMethod2(final Field field, final String data) {
final char[] chars;
try {
chars = (char[]) field.get(data);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
for (final char c : chars) {
if (c <= ' ') {
doThrow();
}
}
return chars.length;
}
KẾT QUẢ COMPOSITE CHO KHÁCH HÀNG -client
CHẾ ĐỘ (kết hợp kiểm tra tiến và lùi)
Lưu ý: chế độ -client với Java 32 bit và chế độ máy chủ với Java 64 bit giống như bên dưới trên máy AMD64 của tôi.
Size WINNER charAt1 charAt2 stream streamPar reuse new1 new2 field1 field2
1 charAt 77.0 72.0 462.0 584.0 127.5 89.5 86.0 159.5 165.0
2 charAt 38.0 36.5 284.0 32712.5 57.5 48.3 50.3 89.0 91.5
4 charAt 19.5 18.5 458.6 3169.0 33.0 26.8 27.5 54.1 52.6
8 charAt 9.8 9.9 100.5 1370.9 17.3 14.4 15.0 26.9 26.4
16 charAt 6.1 6.5 73.4 857.0 8.4 8.2 8.3 13.6 13.5
32 charAt 3.9 3.7 54.8 428.9 5.0 4.9 4.7 7.0 7.2
64 charAt 2.7 2.6 48.2 232.9 3.0 3.2 3.3 3.9 4.0
128 charAt 2.1 1.9 43.7 138.8 2.1 2.6 2.6 2.4 2.6
256 charAt 1.9 1.6 42.4 90.6 1.7 2.1 2.1 1.7 1.8
512 field1 1.7 1.4 40.6 60.5 1.4 1.9 1.9 1.3 1.4
1,024 field1 1.6 1.4 40.0 45.6 1.2 1.9 2.1 1.0 1.2
2,048 field1 1.6 1.3 40.0 36.2 1.2 1.8 1.7 0.9 1.1
4,096 field1 1.6 1.3 39.7 32.6 1.2 1.8 1.7 0.9 1.0
8,192 field1 1.6 1.3 39.6 30.5 1.2 1.8 1.7 0.9 1.0
16,384 field1 1.6 1.3 39.8 28.4 1.2 1.8 1.7 0.8 1.0
32,768 field1 1.6 1.3 40.0 26.7 1.3 1.8 1.7 0.8 1.0
65,536 field1 1.6 1.3 39.8 26.3 1.3 1.8 1.7 0.8 1.0
131,072 field1 1.6 1.3 40.1 25.4 1.4 1.9 1.8 0.8 1.0
262,144 field1 1.6 1.3 39.6 25.2 1.5 1.9 1.9 0.8 1.0
KẾT QUẢ COMPOSITE CHO MÁY CHỦ -server
CHẾ ĐỘ (kết hợp kiểm tra tiến và lùi)
Lưu ý: đây là thử nghiệm cho Java 32 bit chạy ở chế độ máy chủ trên AMD64. Chế độ máy chủ cho Java 64 bit giống như Java 32 bit ở chế độ máy khách ngoại trừ việc truy cập Trường bắt đầu chiến thắng sau kích thước 32 ký tự.
Size WINNER charAt1 charAt2 stream streamPar reuse new1 new2 field1 field2
1 charAt 74.5 95.5 524.5 783.0 90.5 102.5 90.5 135.0 151.5
2 charAt 48.5 53.0 305.0 30851.3 59.3 57.5 52.0 88.5 91.8
4 charAt 28.8 32.1 132.8 2465.1 37.6 33.9 32.3 49.0 47.0
8 new2 18.0 18.6 63.4 1541.3 18.5 17.9 17.6 25.4 25.8
16 new2 14.0 14.7 129.4 1034.7 12.5 16.2 12.0 16.0 16.6
32 new2 7.8 9.1 19.3 431.5 8.1 7.0 6.7 7.9 8.7
64 reuse 6.1 7.5 11.7 204.7 3.5 3.9 4.3 4.2 4.1
128 reuse 6.8 6.8 9.0 101.0 2.6 3.0 3.0 2.6 2.7
256 field2 6.2 6.5 6.9 57.2 2.4 2.7 2.9 2.3 2.3
512 reuse 4.3 4.9 5.8 28.2 2.0 2.6 2.6 2.1 2.1
1,024 charAt 2.0 1.8 5.3 17.6 2.1 2.5 3.5 2.0 2.0
2,048 charAt 1.9 1.7 5.2 11.9 2.2 3.0 2.6 2.0 2.0
4,096 charAt 1.9 1.7 5.1 8.7 2.1 2.6 2.6 1.9 1.9
8,192 charAt 1.9 1.7 5.1 7.6 2.2 2.5 2.6 1.9 1.9
16,384 charAt 1.9 1.7 5.1 6.9 2.2 2.5 2.5 1.9 1.9
32,768 charAt 1.9 1.7 5.1 6.1 2.2 2.5 2.5 1.9 1.9
65,536 charAt 1.9 1.7 5.1 5.5 2.2 2.4 2.4 1.9 1.9
131,072 charAt 1.9 1.7 5.1 5.4 2.3 2.5 2.5 1.9 1.9
262,144 charAt 1.9 1.7 5.1 5.1 2.3 2.5 2.5 1.9 1.9
MÃ CHƯƠNG TRÌNH RUNNABLE ĐẦY ĐỦ
(để kiểm tra trên Java 7 trở về trước, hãy xóa hai kiểm tra luồng)
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.function.IntPredicate;
/**
* @author Saint Hill <http://stackoverflow.com/users/1584255/saint-hill>
*/
public final class TestStrings {
// we will not test strings longer than 512KM
final int MAX_STRING_SIZE = 1024 * 256;
// for each string size, we will do all the tests
// this many times
final int TRIES_PER_STRING_SIZE = 1000;
public static void main(String[] args) throws Exception {
new TestStrings().run();
}
void run() throws Exception {
// double the length of the data until it reaches MAX chars long
// 0,1,2,4,8,16,32,64,128,256 ...
final List<Integer> sizes = new ArrayList<>();
for (int n = 0; n <= MAX_STRING_SIZE; n = (n == 0 ? 1 : n * 2)) {
sizes.add(n);
}
// CREATE RANDOM (FOR SHUFFLING ORDER OF TESTS)
final Random random = new Random();
System.out.println("Rate in nanoseconds per character inspected.");
System.out.printf("==== FORWARDS (tries per size: %s) ==== \n", TRIES_PER_STRING_SIZE);
printHeadings(TRIES_PER_STRING_SIZE, random);
for (int size : sizes) {
reportResults(size, test(size, TRIES_PER_STRING_SIZE, random));
}
// reverse order or string sizes
Collections.reverse(sizes);
System.out.println("");
System.out.println("Rate in nanoseconds per character inspected.");
System.out.printf("==== BACKWARDS (tries per size: %s) ==== \n", TRIES_PER_STRING_SIZE);
printHeadings(TRIES_PER_STRING_SIZE, random);
for (int size : sizes) {
reportResults(size, test(size, TRIES_PER_STRING_SIZE, random));
}
}
///
///
/// METHODS OF CHECKING THE CONTENTS
/// OF A STRING. ALWAYS CHECKING FOR
/// WHITESPACE (CHAR <=' ')
///
///
// CHECK THE STRING CONTENTS
int charAtMethod1(final String data) {
final int len = data.length();
for (int i = 0; i < len; i++) {
if (data.charAt(i) <= ' ') {
doThrow();
}
}
return len;
}
// SAME AS ABOVE BUT USE String.length()
// instead of making a new final local int
int charAtMethod2(final String data) {
for (int i = 0; i < data.length(); i++) {
if (data.charAt(i) <= ' ') {
doThrow();
}
}
return data.length();
}
// USE new Java-8 String's IntStream
// pass it a PREDICATE to do the checking
int streamMethod(final String data, final IntPredicate predicate) {
if (data.chars().anyMatch(predicate)) {
doThrow();
}
return data.length();
}
// OH LA LA - GO PARALLEL!!!
int streamParallelMethod(final String data, IntPredicate predicate) {
if (data.chars().parallel().anyMatch(predicate)) {
doThrow();
}
return data.length();
}
// Re-fill a resuable char[] with the contents
// of the String's char[]
int reuseBuffMethod(final char[] reusable, final String data) {
final int len = data.length();
data.getChars(0, len, reusable, 0);
for (int i = 0; i < len; i++) {
if (reusable[i] <= ' ') {
doThrow();
}
}
return len;
}
// Obtain a new copy of char[] from String
int newMethod1(final String data) {
final int len = data.length();
final char[] copy = data.toCharArray();
for (int i = 0; i < len; i++) {
if (copy[i] <= ' ') {
doThrow();
}
}
return len;
}
// Obtain a new copy of char[] from String
// but use FOR-EACH
int newMethod2(final String data) {
for (final char c : data.toCharArray()) {
if (c <= ' ') {
doThrow();
}
}
return data.length();
}
// FANCY!
// OBTAIN FIELD FOR ACCESS TO THE STRING'S
// INTERNAL CHAR[]
int fieldMethod1(final Field field, final String data) {
try {
final char[] chars = (char[]) field.get(data);
final int len = chars.length;
for (int i = 0; i < len; i++) {
if (chars[i] <= ' ') {
doThrow();
}
}
return len;
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
// same as above but use FOR-EACH
int fieldMethod2(final Field field, final String data) {
final char[] chars;
try {
chars = (char[]) field.get(data);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
for (final char c : chars) {
if (c <= ' ') {
doThrow();
}
}
return chars.length;
}
/**
*
* Make a list of tests. We will shuffle a copy of this list repeatedly
* while we repeat this test.
*
* @param data
* @return
*/
List<Jobber> makeTests(String data) throws Exception {
// make a list of tests
final List<Jobber> tests = new ArrayList<Jobber>();
tests.add(new Jobber("charAt1") {
int check() {
return charAtMethod1(data);
}
});
tests.add(new Jobber("charAt2") {
int check() {
return charAtMethod2(data);
}
});
tests.add(new Jobber("stream") {
final IntPredicate predicate = new IntPredicate() {
public boolean test(int value) {
return value <= ' ';
}
};
int check() {
return streamMethod(data, predicate);
}
});
tests.add(new Jobber("streamPar") {
final IntPredicate predicate = new IntPredicate() {
public boolean test(int value) {
return value <= ' ';
}
};
int check() {
return streamParallelMethod(data, predicate);
}
});
// Reusable char[] method
tests.add(new Jobber("reuse") {
final char[] cbuff = new char[MAX_STRING_SIZE];
int check() {
return reuseBuffMethod(cbuff, data);
}
});
// New char[] from String
tests.add(new Jobber("new1") {
int check() {
return newMethod1(data);
}
});
// New char[] from String
tests.add(new Jobber("new2") {
int check() {
return newMethod2(data);
}
});
// Use reflection for field access
tests.add(new Jobber("field1") {
final Field field;
{
field = String.class.getDeclaredField("value");
field.setAccessible(true);
}
int check() {
return fieldMethod1(field, data);
}
});
// Use reflection for field access
tests.add(new Jobber("field2") {
final Field field;
{
field = String.class.getDeclaredField("value");
field.setAccessible(true);
}
int check() {
return fieldMethod2(field, data);
}
});
return tests;
}
/**
* We use this class to keep track of test results
*/
abstract class Jobber {
final String name;
long nanos;
long chars;
long runs;
Jobber(String name) {
this.name = name;
}
abstract int check();
final double nanosPerChar() {
double charsPerRun = chars / runs;
long nanosPerRun = nanos / runs;
return charsPerRun == 0 ? nanosPerRun : nanosPerRun / charsPerRun;
}
final void run() {
runs++;
long time = System.nanoTime();
chars += check();
nanos += System.nanoTime() - time;
}
}
// MAKE A TEST STRING OF RANDOM CHARACTERS A-Z
private String makeTestString(int testSize, char start, char end) {
Random r = new Random();
char[] data = new char[testSize];
for (int i = 0; i < data.length; i++) {
data[i] = (char) (start + r.nextInt(end));
}
return new String(data);
}
// WE DO THIS IF WE FIND AN ILLEGAL CHARACTER IN THE STRING
public void doThrow() {
throw new RuntimeException("Bzzzt -- Illegal Character!!");
}
/**
* 1. get random string of correct length 2. get tests (List<Jobber>) 3.
* perform tests repeatedly, shuffling each time
*/
List<Jobber> test(int size, int tries, Random random) throws Exception {
String data = makeTestString(size, 'A', 'Z');
List<Jobber> tests = makeTests(data);
List<Jobber> copy = new ArrayList<>(tests);
while (tries-- > 0) {
Collections.shuffle(copy, random);
for (Jobber ti : copy) {
ti.run();
}
}
// check to make sure all char counts the same
long runs = tests.get(0).runs;
long count = tests.get(0).chars;
for (Jobber ti : tests) {
if (ti.runs != runs && ti.chars != count) {
throw new Exception("Char counts should match if all correct algorithms");
}
}
return tests;
}
private void printHeadings(final int TRIES_PER_STRING_SIZE, final Random random) throws Exception {
System.out.print(" Size");
for (Jobber ti : test(0, TRIES_PER_STRING_SIZE, random)) {
System.out.printf("%9s", ti.name);
}
System.out.println("");
}
private void reportResults(int size, List<Jobber> tests) {
System.out.printf("%6d", size);
for (Jobber ti : tests) {
System.out.printf("%,9.2f", ti.nanosPerChar());
}
System.out.println("");
}
}
for (char c : chars)
?