it-swarm.com.de

Commons Lang StringUtils.replace-Leistung im Vergleich zu String.replace

Als ich die Leistung von Apaches StringUtils.replace() vs String.replace() verglichen habe, war ich überrascht zu wissen, dass ersteres ungefähr viermal schneller ist. Ich habe das Caliper-Framework von Google verwendet, um die Leistung zu messen. Hier ist mein Test

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);
    }
}

ausgabe

 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 =======

Warum das? Beide Methoden scheinen die gleiche Arbeit zu leisten, StringUtils.replace() ist noch flexibler.

37

Aus dem Quellcode von 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) wird mit Java.util.regex.Pattern implementiert, daher ist es nicht verwunderlich, dass StringUtils.replace(String text, String searchString, String replacement) langsamer ist2, das mit indexOf und StringBuffer implementiert wird.

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();
}

Fußnote

1 Die Version, von der ich den Quellcode verlinke und kopiere, ist JDK 7

2 Die Version, von der ich auf Quellcode verweise und diesen kopiere, ist common-lang-2.5

36
nhahtdh

Probieren Sie dieses aus, Sie werden feststellen, dass es extrem performant ist als das von 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;
}
9
loukili

zu meinem Test mit JMH: https://github.com/qxo/Benchmark4StringReplace Der Streit ist Loukilis Art:

Java -jar target/benchmarks.jar StringReplaceBenchmark -wi 3 -i 6 -f 1 -tu msBenchmark 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

6
qxo

Warum das? Beide Methoden scheinen die gleiche Arbeit zu leisten.

Sie müssen sich den Quellcode ansehen und einige ernsthafte Untersuchungen mit einem Profiler durchführen, um eine gute (technische) Antwort darauf zu erhalten.

Eine mögliche Erklärung ist jedoch, dass StringUtils.replace und String.replace wurden für verschiedene Anwendungsfälle optimiert. Sie betrachten nur einen Fall ... mit einer ziemlich kleinen Zeichenfolge und einer Ersatzzeichenfolge, die dieselbe Größe wie die zu ersetzende Teilzeichenfolge hat.

Eine andere mögliche Erklärung ist, dass die Apache-Entwickler einfach mehr Zeit für die Optimierung aufgewendet haben. (Und lasst uns nicht die Java Entwickler dafür verantwortlich machen. Sie haben lange Zeit unter strengen personellen Einschränkungen gearbeitet. Im großen Rahmen der Dinge gibt es viele Aufgaben, die wichtiger sind als die Leistungsoptimierung String.replace.)


Betrachtet man den Quellcode, sieht es so aus, als ob die Java 7-Version nur den regulären Ausdruck replace verwendet. Im Gegensatz dazu wird die Apache-Version verwendet Aufgrund dieser Beweise würde ich erwarten, dass der Leistungsunterschied zwischen den beiden Versionen für große Zielzeichenfolgen relativ gering ist, und ich vermute, dass die Version Java= 7 sogar in einigen Fällen besser sein.

(Aufgrund der Beweise im Code ist auch jede nicht-technische Erklärung plausibel.)

4
Stephen C