Pregunta Commons Lang StringUtils.replace performance vs String.replace


Cuando comparé el rendimiento de Apache StringUtils.replace() vs String.replace() Me sorprendió saber que el primero es aproximadamente 4 veces más rápido. Utilicé el marco Caliper de Google para medir el rendimiento. Aquí está mi prueba

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

salida

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

¿Porqué es eso? Ambos métodos parecen hacer el mismo trabajo, StringUtils.replace() es aún más flexible.


32
2018-04-26 04:51


origen


Respuestas:


Del código fuente de 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) se implementa con java.util.regex.Pattern, por lo tanto, no es sorprendente que sea más lento que StringUtils.replace(String text, String searchString, String replacement)2, que se implementa con indexOf y 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();
}

Nota

1 La versión a la que me enlace y copié el código fuente es JDK 7

2 La versión que enlazo y copié el código fuente es common-lang-2.5


28
2018-04-26 05:06



Prueba este, notarás que es extremadamente eficiente que el de 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



¿Porqué es eso? Ambos métodos parecen hacer el mismo trabajo.

Debería mirar el código fuente y realizar una investigación seria con un generador de perfiles para obtener una buena respuesta (técnica).

Sin embargo, una posible explicación es que StringUtils.replace y String.replace han sido ajustados para diferentes casos de uso. Solo está mirando un caso ... con una cadena bastante pequeña y una cadena de reemplazo del mismo tamaño que la subcadena que se reemplaza.

Otra posible explicación es que los desarrolladores de Apache simplemente dedicaron más tiempo a la optimización. (Y no culpemos a los desarrolladores de Java por eso. Han estado trabajando bajo severas limitaciones de personal durante mucho tiempo. En el gran esquema de cosas, hay muchas tareas más importantes que la optimización del rendimiento String.replace.)


De hecho, mirando el código fuente, parece que la versión de Java 7 solo usa la expresión regular basada en replace bajo el capó. Por el contrario, la versión de Apache va bastante lejos para evitar copiar. En base a esa evidencia, esperaría que la diferencia de rendimiento entre las dos versiones sea relativamente menor para cadenas de destino grandes. Y sospecho que la versión de Java 7 podría ser incluso mejor en algunos casos.

(La explicación no técnica también es plausible, según la evidencia del código).


3
2018-04-26 05:00



en mi prueba con JMH:https://github.com/qxo/Benchmark4StringReplace El acosado es el camino de 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