BİRİNCİ GÜNCELLEME: Bunu bir üretim ortamında denemeden önce (tavsiye edilmez), önce bunu okuyun: http://www.javaspecialists.eu/archive/Issue237.html
Java 9'dan başlayarak, açıklandığı gibi çözüm artık çalışmaz çünkü Java şimdi dizeleri varsayılan olarak byte [] olarak saklayacaktır.
İKİNCİ GÜNCELLEME: 2016-10-25 itibariyle, AMDx64 8core ve kaynak 1.8'imde 'charAt' kullanımı ile saha erişimi arasında bir fark yok. Jvm herhangi bir 'string.charAt (n)' çağrısını satır içi ve düzene sokmak için yeterince optimize edilmiştir.
Her String
şey denetlenmekte olan uzunluğa bağlıdır . Sorunun söylediği gibi, uzun dizeler içinse , dizeyi incelemenin en hızlı yolu dizenin desteğine erişmek için yansıma kullanmaktır char[]
.
9 farklı teknikle (aşağıya bakın!) 64 AMD Phenom II 4 çekirdekli 955 @ 3.2 GHZ'de (hem istemci modunda hem de sunucu modunda) JDK 8 (win32 ve win64) ile tamamen rastgele karşılaştırmalı değerlendirme, String.charAt(n)
kullanımın küçükler için en hızlı olduğunu gösterir dizeleri ve reflection
String destek dizisine erişmek için kullanılanlar büyük dizelerin neredeyse iki katıdır.
DENEY
9 farklı optimizasyon tekniği denenmiştir.
Tüm dize içerikleri rastgele
Test, 0,1,2,4,8,16 vb. İle başlayan iki katın dize boyutları için yapılır.
Testler dize boyutu başına 1.000 kez yapılır
Testler her seferinde rastgele sırayla karıştırılır. Diğer bir deyişle, testler her yapılışlarında 1000 defadan fazla rastgele sırada yapılır.
Tüm test takımı, JVM ısınmasının optimizasyon ve zamanlar üzerindeki etkisini göstermek için ileri ve geri yapılır.
Tüm süit iki kez yapılır, biri -client
modda diğeri -server
modda.
SONUÇLAR
istemci modu (32 bit)
1 ila 256 karakter uzunluğunda dizeler için , çağrı string.charAt(i)
saniyede ortalama 13,4 milyon ila 588 milyon karakter işleme ile kazanır.
Ayrıca, genel olarak% 5.5 daha hızlı (istemci) ve% 13.9 (sunucu) şöyle:
for (int i = 0; i < data.length(); i++) {
if (data.charAt(i) <= ' ') {
doThrow();
}
}
yerel bir son uzunluk değişkeni ile bundan daha çok:
final int len = data.length();
for (int i = 0; i < len; i++) {
if (data.charAt(i) <= ' ') {
doThrow();
}
}
Uzun dizelerde, 512 - 256K karakter uzunluğunda , Dizenin destek dizisine erişmek için yansıma kullanmak en hızlısıdır. Bu teknik String.charAt (i) 'den neredeyse iki kat daha hızlıdır (% 178 daha hızlı). Bu aralıktaki ortalama hız saniyede 1.111 milyar karakterdi.
Alan önceden elde edilmeli ve daha sonra kütüphanede farklı dizgiler üzerinde tekrar kullanılabilir. İlginç bir şekilde, yukarıdaki koddan farklı olarak, Alan erişimi ile, döngü kontrolünde 'chars.length' kullanmaktan yerel bir son uzunluk değişkenine sahip olmak% 9 daha hızlıdır. Alan erişimi en hızlı şekilde nasıl kurulabilir:
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);
}
-Server modunda özel yorumlar
AMD 64 makinemdeki 64 bit Java makinesinde sunucu modunda 32 karakter uzunluğundaki dizelerden sonra alan erişimi kazanmaya başlar. İstemci modunda 512 karakter uzunluğa kadar bu görülmedi.
Ayrıca sunucu modunda JDK 8 (32 bit derleme) çalıştırırken genel performansın hem büyük hem de küçük dizeler için% 7 daha yavaş olduğunu düşünüyorum. Bu JDK 8'in erken sürümünün 121 Aralık 2013'teki derlemesiyle oldu. Şimdilik, 32 bit sunucu modunun 32 bit istemci modundan daha yavaş olduğu görülüyor.
Olduğu söyleniyor ... 64 bit makinede çağırmaya değer tek sunucu modu gibi görünüyor. Aksi takdirde aslında performansı engeller.
-server mode
Bir AMD64 üzerinde çalışan 32 bit yapı için şunu söyleyebilirim:
- String.charAt (i) genel olarak açık kazanır. Her ne kadar 8 ila 512 karakter arasında olsa da 'yeni' yeniden 've' alan 'arasında kazananlar vardı.
- String.charAt (i) istemci modunda% 45 daha hızlı
- Alan erişimi, istemci modunda büyük Dizeler için iki kat daha hızlıdır.
Ayrıca, String.chars () (Stream ve paralel sürüm) bir büst. Diğer yollardan çok daha yavaş. Streams
API genel dize işlemleri gerçekleştirmek için oldukça yavaş bir yoldur.
İstek Listesi
Java String, içerme (yüklem), forEach (tüketici), forEachWithIndex (tüketici) gibi optimize edilmiş yöntemleri kabul etme yüklemine sahip olabilir. Bu nedenle, kullanıcının uzunluğu bilmesine veya Dize yöntemlerine yapılan çağrıları tekrarlamasına gerek kalmadan, bunlar kitaplıkların beep-beep beep
hızlandırmasının ayrıştırılmasına yardımcı olabilir .
Hayal kurmaya devam et :)
Mutlu Dizeler!
~ SH
Testte, dizeyi boşluk olup olmadığını test etmek için aşağıdaki 9 yöntem kullanılmıştır:
"charAt1" - KULLANIM YOLUNDA YAYLI İÇERİKLERİ KONTROL EDİN:
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" - ÜZERİNE AYNI AMA AMA KULLANIMI String.length () UZUNLUK İÇİN NİHAİ BİR YEREL YAPMA KURULUŞU
int charAtMethod2(final String data) {
for (int i = 0; i < data.length(); i++) {
if (data.charAt(i) <= ' ') {
doThrow();
}
}
return data.length();
}
"stream" - YENİ JAVA-8 String'in Akışını KULLANIN VE KONTROL YAPMAK İÇİN BİR TAHMİN EDİN
int streamMethod(final String data, final IntPredicate predicate) {
if (data.chars().anyMatch(predicate)) {
doThrow();
}
return data.length();
}
"streamPara" - ÜZERİNDE AYNI, AMA OH-LA-LA - PARALEL GİT !!!
// avoid this at all costs
int streamParallelMethod(final String data, IntPredicate predicate) {
if (data.chars().parallel().anyMatch(predicate)) {
doThrow();
}
return data.length();
}
"yeniden kullan" - STRINGS İÇERİKLERİ İLE TEKRAR KULLANILABİLİR bir char [] DOLDUR
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" - STRING'DEN char [] karakterinin YENİ BİR KOPYASINI ALIN
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" - ÜZERİNDE AYNI, AMA "HER ŞEY İÇİN"
int newMethod2(final String data) {
for (final char c : data.toCharArray()) {
if (c <= ' ') {
doThrow();
}
}
return data.length();
}
"field1" - FANTEZİ !! STRING'İN DAHİLİ İÇERİĞİNE ERİŞİM ALANI ALIN []
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" - ÜZERİNDE AYNI, AMA "HER ŞEY İÇİN"
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;
}
MÜŞTERİ -client
MODU İÇİN KOMPOZİT SONUÇLARI (ileri ve geri testler birleştirilmiş)
Not: Java 32 bit ile -client mod ve Java 64 bit ile -server modunun AMD64 makinemdeki ile aynı olduğunu unutmayın.
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
SUNUCU -server
MODU İÇİN KOMPOZİT SONUÇLARI (ileri ve geri testler birleştirilmiş)
Not: Bu, AMD64'te sunucu modunda çalışan Java 32 bit testi. Java 64 bit sunucu modu istemci modunda Java 32 bit ile aynıydı, ancak Alan erişimi 32 karakterden sonra kazanmaya başlıyor.
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
TAM ÇALIŞABİLİR PROGRAM KODU
(Java 7 ve önceki sürümlerde test etmek için iki akış testini kaldırın)
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)
?