Bir String'deki tüm karakterleri yinelemenin en hızlı yolu


163

Java'da, bir String'deki tüm karakterleri yinelemenin en hızlı yolu budur:

String str = "a really, really long string";
for (int i = 0, n = str.length(); i < n; i++) {
    char c = str.charAt(i);
}

Veya bu:

char[] chars = str.toCharArray();
for (int i = 0, n = chars.length; i < n; i++) {
    char c = chars[i];
}

DÜZENLE :

Ne bilmek istiyorum, charAtuzun bir yineleme sırasında tekrar tekrar yöntem çağırma maliyeti toCharArraybaşlangıçta tek bir çağrı yapma ve daha sonra doğrudan yineleme sırasında diziye erişim maliyetinden daha az ya da daha fazla biter olmasıdır .

Birinin sadece iki çağrı arasındaki farkı değil, JIT ısınma süresi, JVM başlangıç ​​zamanı, vb. Göz önünde bulundurarak farklı dize uzunlukları için sağlam bir kıyaslama yapabilmesi harika olurdu System.currentTimeMillis().


18
Ne oldu for (char c : chars)?
dasblinkenlight

İlki daha hızlı olmalı ve yine de teorik olarak bir char dizisi dizisi olmalıdır.
Keagan Ladds

Google genellikle iyi bir kaynaktır: mkyong.com/java/…
Johan Sjöberg

2
Soru, yineleyicilerin kullanımının performansını önermez. Bilmek istediğim şey, tekrar tekrar arama charAtmaliyetinin tek bir arama yapma maliyetinden daha az veya daha fazla olmasıtoCharArray
Lópezscar López

1
Herkes StringCharacterIterator ile analiz yaptı ?
bdrx

Yanıtlar:


352

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 reflectionString 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 -clientmodda diğeri -servermodda.

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 modeBir AMD64 üzerinde çalışan 32 bit yapı için şunu söyleyebilirim:

  1. 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ı.
  2. String.charAt (i) istemci modunda% 45 daha hızlı
  3. 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ş. StreamsAPI 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 beephı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İ -clientMODU İÇİ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 -serverMODU İÇİ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("");
    }
}

1
Bu test sunucu JVM veya istemci JVM'de mi çalıştırıldı? En iyi optimizasyonlar sadece sunucu JVM'de yapılır. Varsayılan 32 bit JVM'yi kullanarak ve bağımsız değişken kullanmadıysanız, istemci modunda koştunuz.
ceklock

2
Destek arabelleğini almak alt dizede veya String (char [], int, int) kullanılarak oluşturulan dizelerde, tüm arabelleği aldığınız zaman (en azından Android'de) sorunludur, ancak indekslemeniz sıfır temelli olacaktır. Ancak, bir alt dizeye sahip olmadığınızı biliyorsanız, düzgün çalışacaktır.
prewett

5
"For (int i = 0; i <data.length (); i ++)" neden data.length () öğesini son yerel değişken olarak tanımlamaktan daha hızlı?
skyin

2
Bir değişkenin tanımlanması, yöntem bayt kodunda bir yığın işlemi gerektirir. Ancak, algoritmanızı tanımaktan kaynaklanan optimizasyonlar, değişken makine yükü olmadan, gerçek makine kodunda tekrarlanan işlemi hızlı bir şekilde izleyebilir. Bu tür optimizasyonlar bazen bayt kodu derleyicilerinde bulunur, bazen olmaz. Her şey jvm yeterince akıllı olup olmadığına bağlıdır :-)
Koordinatör

2
@DavidS sayıları, incelenen karakter başına orandır (nanosaniye cinsinden). Daha küçük daha iyidir.
Koordinatör

14

Bu sadece endişelenmemeniz gereken mikro optimizasyon.

char[] chars = str.toCharArray();

size strkarakter dizilerinin bir kopyasını döndürür (JDK'da arayarak karakterlerin bir kopyasını döndürür System.arrayCopy).

Bunun dışında, str.charAt()yalnızca dizinin gerçekten sınırlar içinde olup olmadığını kontrol eder ve dizi dizini içindeki bir karakteri döndürür.

Birincisi JVM'de ek bellek oluşturmaz.


Soruya cevap vermiyor. Bu soru performansla ilgili. Tüm bildiğiniz için, OP dizeler üzerinden yinelemenin uygulamalarında büyük bir maliyet olduğunu keşfetmiş olabilir.
rghome

9

Sadece merak ve Saint Hill'in cevabı ile karşılaştırmak için.

Ağır verileri işlemeniz gerekiyorsa, JVM'yi istemci modunda kullanmamalısınız. Optimizasyon için istemci modu yapılmaz.

@Saint Hill kıyaslamalarının sonuçlarını İstemci modunda ve Sunucu modunda bir JVM kullanarak karşılaştıralım.

Core2Quad Q6600 G0 @ 2.4GHz
JavaSE 1.7.0_40

Ayrıca bakınız: "java -server" ve "java -client" arasındaki gerçek farklılıklar?


MÜŞTERİ MODU:

len =      2:    111k charAt(i),  105k cbuff[i],   62k new[i],   17k field access.   (chars/ms) 
len =      4:    285k charAt(i),  166k cbuff[i],  114k new[i],   43k field access.   (chars/ms) 
len =      6:    315k charAt(i),  230k cbuff[i],  162k new[i],   69k field access.   (chars/ms) 
len =      8:    333k charAt(i),  275k cbuff[i],  181k new[i],   85k field access.   (chars/ms) 
len =     12:    342k charAt(i),  342k cbuff[i],  222k new[i],  117k field access.   (chars/ms) 
len =     16:    363k charAt(i),  347k cbuff[i],  275k new[i],  152k field access.   (chars/ms) 
len =     20:    363k charAt(i),  392k cbuff[i],  289k new[i],  180k field access.   (chars/ms) 
len =     24:    375k charAt(i),  428k cbuff[i],  311k new[i],  205k field access.   (chars/ms) 
len =     28:    378k charAt(i),  474k cbuff[i],  341k new[i],  233k field access.   (chars/ms) 
len =     32:    376k charAt(i),  492k cbuff[i],  340k new[i],  251k field access.   (chars/ms) 
len =     64:    374k charAt(i),  551k cbuff[i],  374k new[i],  367k field access.   (chars/ms) 
len =    128:    385k charAt(i),  624k cbuff[i],  415k new[i],  509k field access.   (chars/ms) 
len =    256:    390k charAt(i),  675k cbuff[i],  436k new[i],  619k field access.   (chars/ms) 
len =    512:    394k charAt(i),  703k cbuff[i],  439k new[i],  695k field access.   (chars/ms) 
len =   1024:    395k charAt(i),  718k cbuff[i],  462k new[i],  742k field access.   (chars/ms) 
len =   2048:    396k charAt(i),  725k cbuff[i],  471k new[i],  767k field access.   (chars/ms) 
len =   4096:    396k charAt(i),  727k cbuff[i],  459k new[i],  780k field access.   (chars/ms) 
len =   8192:    397k charAt(i),  712k cbuff[i],  446k new[i],  772k field access.   (chars/ms) 

SUNUCU MODU:

len =      2:     86k charAt(i),   41k cbuff[i],   46k new[i],   80k field access.   (chars/ms) 
len =      4:    571k charAt(i),  250k cbuff[i],   97k new[i],  222k field access.   (chars/ms) 
len =      6:    666k charAt(i),  333k cbuff[i],  125k new[i],  315k field access.   (chars/ms) 
len =      8:    800k charAt(i),  400k cbuff[i],  181k new[i],  380k field access.   (chars/ms) 
len =     12:    800k charAt(i),  521k cbuff[i],  260k new[i],  545k field access.   (chars/ms) 
len =     16:    800k charAt(i),  592k cbuff[i],  296k new[i],  640k field access.   (chars/ms) 
len =     20:    800k charAt(i),  666k cbuff[i],  408k new[i],  800k field access.   (chars/ms) 
len =     24:    800k charAt(i),  705k cbuff[i],  452k new[i],  800k field access.   (chars/ms) 
len =     28:    777k charAt(i),  736k cbuff[i],  368k new[i],  933k field access.   (chars/ms) 
len =     32:    800k charAt(i),  780k cbuff[i],  571k new[i],  969k field access.   (chars/ms) 
len =     64:    800k charAt(i),  901k cbuff[i],  800k new[i],  1306k field access.   (chars/ms) 
len =    128:    1084k charAt(i),  888k cbuff[i],  633k new[i],  1620k field access.   (chars/ms) 
len =    256:    1122k charAt(i),  966k cbuff[i],  729k new[i],  1790k field access.   (chars/ms) 
len =    512:    1163k charAt(i),  1007k cbuff[i],  676k new[i],  1910k field access.   (chars/ms) 
len =   1024:    1179k charAt(i),  1027k cbuff[i],  698k new[i],  1954k field access.   (chars/ms) 
len =   2048:    1184k charAt(i),  1043k cbuff[i],  732k new[i],  2007k field access.   (chars/ms) 
len =   4096:    1188k charAt(i),  1049k cbuff[i],  742k new[i],  2031k field access.   (chars/ms) 
len =   8192:    1157k charAt(i),  1032k cbuff[i],  723k new[i],  2048k field access.   (chars/ms) 

SONUÇ:

Gördüğünüz gibi, sunucu modu çok daha hızlı.


2
Gönderdiğiniz için teşekkürler. Bu nedenle, büyük dizeler için alan erişimi hala charAt () 'dan 2 kat daha hızlıdır. Aslında, 28 erişim dizesinden (çılgın !!) sonra alan erişimi genel olarak daha da hızlı hale geldi Yani sunucu modu her şeyi daha hızlı hale getiriyor. Çok ilginç!
Koordinatör

1
Evet, yansıtıcı yöntem gerçekten daha hızlı. İlginç.
ceklock

2
btw: daha yeni JVM'ler otomatik olarak hangisinin en iyi (genellikle) çalıştığı -server veya -client değerini bulur: docs.oracle.com/javase/7/docs/technotes/guides/vm/…
jontejj

2
@jontejj pratikte o kadar basit değil. Windows'ta 32 bit JVM çalıştırıyorsanız, JVM her zaman varsayılan olarak istemciye atanır.
ceklock

7

İlk kullanan str.charAtdaha hızlı olmalıdır.

StringSınıfın kaynak kodunun içini kazarsanız , charAtbunun aşağıdaki gibi uygulandığını görebiliriz :

public char charAt(int index) {
    if ((index < 0) || (index >= count)) {
        throw new StringIndexOutOfBoundsException(index);
    }
    return value[index + offset];
}

Burada tek yaptığı bir dizini indekslemek ve değeri döndürmektir.

Şimdi, uygulanmasını toCharArraygörürsek, aşağıdakileri bulacağız:

public char[] toCharArray() {
    char result[] = new char[count];
    getChars(0, count, result, 0);
    return result;
}

public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
    if (srcBegin < 0) {
        throw new StringIndexOutOfBoundsException(srcBegin);
    }
    if (srcEnd > count) {
        throw new StringIndexOutOfBoundsException(srcEnd);
    }
    if (srcBegin > srcEnd) {
        throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
    }
    System.arraycopy(value, offset + srcBegin, dst, dstBegin,
         srcEnd - srcBegin);
}

Gördüğünüz gibi System.arraycopy, kesinlikle yapmamaktan biraz daha yavaş olacak bir şey yapıyor.


2
Dizin, dizi erişiminde yine de işaretlendiğinde, String # charAt'ın ek bir dizin denetimi yapması saçmadır.
Ingo

1
8 yaşındaki bir iş parçacığının canlandırılması riski vardır ... Bir dizenin arkasındaki karakter dizisi dizenin kendisinden daha büyük olabilir. Yani, "abcde" dizginiz varsa ve sonra "bcd" yi yeni bir dizeye ayıklamak için alt dizeyi kullandıysanız, yeni dizgi ilk dizeyle tam olarak aynı karakter dizisiyle desteklenecektir. Bu nedenle dize sınıfı bir ofset ve bir sayı tutar - bu nedenle dizideki hangi karakterlerin bu dizeyi temsil edenler olduğunu bilir. Bu nedenle aralık kontrolü önemlidir, aksi takdirde bu dizenin uçlarının ötesindeki karakterlere erişmek mümkün olur.
dty

3

@Saint Hill'in str.toCharArray () ' in zaman karmaşıklığını düşünürseniz ,

ilki çok büyük teller için bile daha hızlıdır. Kendiniz görmek için aşağıdaki kodu çalıştırabilirsiniz.

        char [] ch = new char[1_000_000_00];
    String str = new String(ch); // to create a large string

    // ---> from here
    long currentTime = System.nanoTime();
    for (int i = 0, n = str.length(); i < n; i++) {
        char c = str.charAt(i);
    }
    // ---> to here
    System.out.println("str.charAt(i):"+(System.nanoTime()-currentTime)/1000000.0 +" (ms)");

    /**
     *   ch = str.toCharArray() itself takes lots of time   
     */
    // ---> from here
    currentTime = System.nanoTime();
    ch = str.toCharArray();
    for (int i = 0, n = str.length(); i < n; i++) {
        char c = ch[i];
    }
    // ---> to  here
    System.out.println("ch = str.toCharArray() + c = ch[i] :"+(System.nanoTime()-currentTime)/1000000.0 +" (ms)");

çıktı:

str.charAt(i):5.492102 (ms)
ch = str.toCharArray() + c = ch[i] :79.400064 (ms)

2

Niether daha hızlı veya daha yavaş görünüyor

    public static void main(String arguments[]) {


        //Build a long string
        StringBuilder sb = new StringBuilder();
        for(int j = 0; j < 10000; j++) {
            sb.append("a really, really long string");
        }
        String str = sb.toString();
        for (int testscount = 0; testscount < 10; testscount ++) {


            //Test 1
            long start = System.currentTimeMillis();
            for(int c = 0; c < 10000000; c++) {
                for (int i = 0, n = str.length(); i < n; i++) {
                    char chr = str.charAt(i);
                    doSomethingWithChar(chr);//To trick JIT optimistaion
                }
            }

            System.out.println("1: " + (System.currentTimeMillis() - start));

            //Test 2
            start = System.currentTimeMillis();
            char[] chars = str.toCharArray();
            for(int c = 0; c < 10000000; c++) {
                for (int i = 0, n = chars.length; i < n; i++) {
                    char chr = chars[i];
                    doSomethingWithChar(chr);//To trick JIT optimistaion
                }
            }
            System.out.println("2: " + (System.currentTimeMillis() - start));
            System.out.println();
        }


    }


    public static void doSomethingWithChar(char chr) {
        int newInt = chr << 2;
    }

Uzun teller için ilkini seçeceğim. Neden uzun dizelerin etrafına kopyalama? Belgeler diyor ki:

public char [] toCharArray () Bu dizeyi yeni bir karakter dizisine dönüştürür.

Döndürür: uzunluğu bu dizenin uzunluğu olan ve içeriği bu dizeyle temsil edilen karakter dizisini içerecek şekilde başlatılan yeni atanmış bir karakter dizisi.

// 1'i düzenle

JIT optimizasyonunu kandırmak için testi değiştirdim.

// 2'yi düzenle

JVM'nin ısınması için testi 10 kez tekrarlayın.

// Düzenle 3

Sonuç:

Her şeyden önce str.toCharArray();tüm dizeyi bellekte kopyalar. Uzun dizeler için bellek tüketebilir. Yöntem daha String.charAt( )önce String sınıfı kontrol dizini içinde char dizisinde char arar. Görünüşe göre yeterince kısa dizeleri ilk yöntem (yani chatAtyöntem) bu dizin denetimi nedeniyle biraz daha yavaş. Ancak Dize yeterince uzunsa, tüm char dizisini kopyalamak yavaşlar ve ilk yöntem daha hızlıdır. Dize ne kadar uzun olursa, o kadar yavaş toCharArrayçalışır. for(int j = 0; j < 10000; j++)Görmek için döngüdeki sınırı değiştirmeyi deneyin . JVM'nin ısınmasına izin verirsek, kod daha hızlı çalışır, ancak oranlar aynıdır.

Sonuçta bu sadece mikro optimizasyon.


for:inSeçeneği deneyebilir misin , sadece eğlenmek için?
dasblinkenlight

2
Karşılaştırmanız hatalı: JIT'in optimizasyonlarını yapmasına izin vermiyor; JIT döngüleri tamamen kaldırabilir, çünkü hiçbir şey yapmazlar.
JB Nizet

Dize na Iterableveya dizi değildir .
Piotr Gwiazda

2
Bu geçerli bir test değil, JVM'nizi Test 1'in lehine ısınan Test 1 ile 'ısıttınız'. OP'nin tüm sorusu yine de mikro optimizasyon kokuyor.
Algı

1
Doğru. Isındıktan sonra (bkz. Düzenle 2) her iki zaman da birbirine daha yakındır. Örneğimde ikinci test biraz daha hızlı. Ama eğer dizeyi daha uzun yaparsam, birincisi daha hızlıdır. Daha uzun dize, char dizi kopyası nedeniyle daha yavaş ikinci testtir. Sadece ilk yoldan yap.
Piotr Gwiazda

2

String.toCharArray()yeni karakter dizisi oluşturur, karakter dizisinin uzunluğunun tahsisi anlamına gelir, daha sonra karakter dizisini kullanarak orijinal karakter dizisini kopyalar System.arraycopy()ve bu kopyayı arayana döndürür. String.charAt () i, orijinal kopyadaki konumdaki karakteri döndürür , bu yüzden String.charAt()bundan daha hızlı olacaktır String.toCharArray(). Bununla birlikte, orijinal karakter dizisinden karakter döndüren String.toCharArray()orijinal String dizisinden char değil, char String.charAt()döndürür. Aşağıdaki kod, bu dizenin belirtilen dizinindeki değeri döndürür.

public char charAt(int index) {
    if ((index < 0) || (index >= value.length)) {
        throw new StringIndexOutOfBoundsException(index);
    }
    return value[index];
}

Aşağıdaki kod, uzunluğu bu dizenin uzunluğu olan yeni atanmış bir karakter dizisi döndürür

public char[] toCharArray() {
    // Cannot use Arrays.copyOf because of class initialization order issues
    char result[] = new char[value.length];
    System.arraycopy(value, 0, result, 0, value.length);
    return result;
}

1

İkincisi, yeni bir char dizisi oluşturulacak ve Dize tüm karakterleri bu yeni char dizisine kopyalanacak neden olur, bu yüzden ilki daha hızlı (ve daha az bellek aç) tahmin ediyorum.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.