Pertanyaan Commons Lang StringUtils.replace kinerja vs String.replace


Ketika saya membandingkan kinerja Apache StringUtils.replace() vs String.replace() Saya terkejut mengetahui bahwa yang pertama sekitar 4 kali lebih cepat. Saya menggunakan kerangka Caliper Google untuk mengukur kinerja. Ini tes saya

public class Performance extends SimpleBenchmark {
    String s = "111222111222";

    public int timeM1(int n) {
        int res = 0;
        for (int x = 0; x < n; x++) {
            res += s.replace("111", "333").length();
        }
        return res;
    }

    public int timeM2(int n) {
        int res = 0;
        for (int x = 0; x < n; x++) {
            res += StringUtils.replace(s, "111", "333", -1).length();
        }
        return res;
    }

    public static void main(String... args) {
        Runner.main(Performance.class, args);
    }
}

keluaran

 0% Scenario{vm=java, trial=0, benchmark=M1} 9820,93 ns; ?=1053,91 ns @ 10 trials
50% Scenario{vm=java, trial=0, benchmark=M2} 2594,67 ns; ?=58,12 ns @ 10 trials

benchmark   us linear runtime
       M1 9,82 ==============================
       M2 2,59 =======

Mengapa demikian? Kedua metode tampaknya melakukan pekerjaan yang sama, StringUtils.replace() bahkan lebih fleksibel.


32
2018-04-26 04:51


asal


Jawaban:


Dari kode sumber java.lang.String1:

public String replace(CharSequence target, CharSequence replacement) {
   return Pattern
            .compile(target.toString(), Pattern.LITERAL)
            .matcher(this )
            .replaceAll(
                    Matcher.quoteReplacement(replacement.toString()));
}

String.replace(CharSequence target, CharSequence replacement) diimplementasikan dengan java.util.regex.PatternOleh karena itu, tidak mengherankan bahwa itu lebih lambat StringUtils.replace(String text, String searchString, String replacement)2, yang diimplementasikan dengan indexOf dan StringBuffer.

public static String replace(String text, String searchString, String replacement) {
    return replace(text, searchString, replacement, -1);
}

public static String replace(String text, String searchString, String replacement, int max) {
    if (isEmpty(text) || isEmpty(searchString) || replacement == null || max == 0) {
        return text;
    }
    int start = 0;
    int end = text.indexOf(searchString, start);
    if (end == -1) {
        return text;
    }
    int replLength = searchString.length();
    int increase = replacement.length() - replLength;
    increase = (increase < 0 ? 0 : increase);
    increase *= (max < 0 ? 16 : (max > 64 ? 64 : max));
    StringBuffer buf = new StringBuffer(text.length() + increase);
    while (end != -1) {
        buf.append(text.substring(start, end)).append(replacement);
        start = end + replLength;
        if (--max == 0) {
            break;
        }
        end = text.indexOf(searchString, start);
    }
    buf.append(text.substring(start));
    return buf.toString();
}

Catatan kaki

1 Versi yang saya tautkan dan salin kode sumbernya adalah JDK 7

2 Versi yang saya tautkan dan salin kode sumbernya adalah common-lang-2.5


28
2018-04-26 05:06



Coba yang ini, Anda akan melihat bahwa itu sangat performant dari yang Apache:

public static String replace (String source, String os, String ns) {
    if (source == null) {
        return null;
    }
    int i = 0;
    if ((i = source.indexOf(os, i)) >= 0) {
        char[] sourceArray = source.toCharArray();
        char[] nsArray = ns.toCharArray();
        int oLength = os.length();
        StringBuilder buf = new StringBuilder (sourceArray.length);
        buf.append (sourceArray, 0, i).append(nsArray);
        i += oLength;
        int j = i;
        // Replace all remaining instances of oldString with newString.
        while ((i = source.indexOf(os, i)) > 0) {
            buf.append (sourceArray, j, i - j).append(nsArray);
            i += oLength;
            j = i;
        }
        buf.append (sourceArray, j, sourceArray.length - j);
        source = buf.toString();
        buf.setLength (0);
    }
    return source;
}

7
2017-10-03 15:48



Mengapa demikian? Kedua metode tampaknya melakukan pekerjaan yang sama.

Anda perlu melihat kode sumber dan melakukan penyelidikan serius dengan seorang profiler untuk mendapatkan jawaban (teknis) yang bagus untuk itu.

Namun, satu penjelasan yang mungkin adalah itu StringUtils.replace dan String.replace telah disetel untuk berbagai kasus penggunaan. Anda hanya melihat satu kasus ... dengan string yang cukup kecil, dan string pengganti dengan ukuran yang sama dengan substring yang diganti.

Penjelasan lain yang mungkin adalah bahwa pengembang Apache hanya menghabiskan lebih banyak waktu pada penyetelan. (Dan jangan salahkan pengembang Java untuk itu. Mereka telah bekerja di bawah batasan staf yang ketat untuk waktu yang lama. Dalam skema besar, ada banyak tugas yang lebih penting daripada penyetelan kinerja String.replace.)


Bahkan, melihat kode sumber, sepertinya versi Java 7 hanya menggunakan ekspresi berbasis reguler replace Dibawah tenda. Sebaliknya, versi Apache akan cukup panjang untuk menghindari penyalinan. Berdasarkan bukti itu, saya mengharapkan perbedaan kinerja antara dua versi menjadi relatif lebih kecil untuk string target besar. Dan saya menduga versi Java 7 bahkan mungkin lebih baik dalam beberapa kasus.

(Penjelasan non-teknis juga masuk akal, berdasarkan bukti dalam kode.)


3
2018-04-26 05:00



pada pengujian saya dengan JMH:https://github.com/qxo/Benchmark4StringReplace The dilanda adalah cara loukili:

java -jar target/benchmarks.jar StringReplaceBenchmark -wi 3 -i 6 -f 1 -tu ms Benchmark Mode Cnt Score Error Units StringReplaceBenchmark.test4String thrpt 6 1255.017 ± 230.012 ops/ms StringReplaceBenchmark.test4StringUtils thrpt 6 4068.229 ± 67.708 ops/ms StringReplaceBenchmark.test4fast thrpt 6 4821.035 ± 97.790 ops/ms StringReplaceBenchmark.test4lang3StringUtils thrpt 6 3186.007 ± 102.786 ops/ms


2
2018-05-29 02:19