Почему метод String replaceAll () в первый раз требует высокой производительности и быстрее в следующий раз? - PullRequest
3 голосов
/ 25 февраля 2020

Я узнал, что метод String's replaceAll() принимает регулярное выражение в качестве входного параметра, и это может привести к значительному снижению производительности. Но однажды я прочитал этот блог с небольшой программой, утверждающей, что (согласно моему пониманию):

Обрабатывайте метод replaceAll() медленно, но быстрее для в следующий раз.

Это результат теста:

regex replace time taken: 14.09 milliseconds
manual replace time taken: 2.371 seconds
-----
regex replace time taken: 9.498 milliseconds
manual replace time taken: 2.406 seconds
-----
regex replace time taken: 2.184 milliseconds
manual replace time taken: 2.360 seconds
-----

Какой механизм оптимизации стоит за этим результатом?

Ответы [ 4 ]

4 голосов
/ 25 февраля 2020

Следующая информация взята из официального 1 исходного кода OpenJDK 11 2 .

Начиная с самого метода String.replaceAll.

public String replaceAll(String regex, String replacement) {
    return Pattern.compile(regex).matcher(this).replaceAll(replacement);
}

Кэширования здесь нет. Далее Pattern.compile

public static Pattern compile(String regex) {
    return new Pattern(regex, 0);
}

Кэширования там тоже нет. И не в приватном конструкторе Pattern.

Конструктор Pattern использует внутренний метод compile() для выполнения компиляции регулярного выражения во внутреннюю форму. Требуются шаги, чтобы избежать компиляции Pattern дважды. Но, как видно из вышесказанного, каждый вызов replaceAll генерирует один-единственный объект Pattern.

Так почему вы видите ускорение в этих показателях производительности?

  • Они могут использовать старую (до Java 6) версию Pattern, которая может иметь 3 кэшированных скомпилированных шаблонов.

  • Наиболее вероятное объяснение состоит в том, что это просто эффект разогрева JVM. Хорошо написанный тест должен учитывать это, но тест, который используется в этом блоге, не выполняет надлежащую прогрев.

Короче говоря, ускорение, которое вы считаете вызвано некоторой «оптимизацией», по-видимому, является просто результатом эффектов разогрева JVM, таких как JIT-компиляция Pattern, Matcher и связанных классов.


1 - Исходный код OpenJDK код для Java 6 и более можно загрузить с https://openjdk.java.net/

2 - Исходный код OpenJDK 6 делает то же самое: без кэширования.

3 - Я не проверял, но это спорный вопрос. Тесты производительности на основе версий EOL Java не являются поучительными для текущих версий Java. Никто не должен все еще использовать Java 5. Если это так, то производительность replaceAll - это наименьшее из их опасений.

4 голосов
/ 25 февраля 2020

Обычно это не вызывает значительного влияния на производительность , если не используется странным и необычным образом. В обычном случае использования (скажем, веб-запрос) он исчезнет при таких вещах, как задержка в сети и другие вещи, которые занимают больше времени. Только если вы будете использовать replaceAll в очень жарком l oop, возникнет необходимость рассмотреть возможность использования классов Pattern и Matcher напрямую, что может помочь с производительностью.

Связанный Сайт учебника кажется сомнительным (и их много, поэтому вы должны быть осторожны с тем, что читаете). Например, он сравнивает replaceAll с плохо написанным методом ручной замены (вот почему вы получаете разницу между секундами и миллисекундами). Затем он сделал aws выводы, основанные на этом.

Так что за результатом в ссылке нет механизма оптимизации. Причиной результата является плохо написанный метод замены вручную, который объединяет множество строк, что делает его медленным по сравнению с replaceAll.

1 голос
/ 25 февраля 2020

Что ж, если вы снова и снова выполняете replaceAll над одним и тем же выражением, то сопоставляемые символы действительно будут менее частыми, поэтому меньше замен.

Пример: /(.)(.*?)\1/ сопоставляет строки с повторяющимся символом, а replaceAll, совпадающий с $ 2, удаляет эти повторяющиеся символы, но его необходимо выполнить несколько раз, чтобы обработать все дубликаты (например, в $ 2 может быть несколько)

  • Например, ABCDEFABCBCDEF дает BCDEFCCDEF
  • BCDEFCCDEF ==> BDEFCDEF
  • BDEFCDEF ==> BEFCEF
  • BEFCEF ==> BFCF
  • BFCF ==> B C

Обрабатываемая строка становится все меньше и меньше, количество совпадений становится все меньше и меньше, поэтому время обработки также уменьшается.

Я допускаю, что этот ответ действительно задан c для варианта использования, но я не могу воспроизвести упомянутое вами поведение, поэтому оно должно как-то указывать c для варианта использования.

Кэширование также может быть причиной - сначала я прочитал 14 секунд вместо м в миллисекундах - но смешивать такие единицы тоже не лучшая идея ...

0 голосов
/ 25 февраля 2020

Что ж, replaceAll использует внутреннее регулярное выражение regex, который будет компилироваться каждый раз, когда вы его вызываете, но возможно, если мы будем снова и снова выполнять replaceAll для одного и того же выражения java использовать некоторый внутренний механизм, чтобы одно и то же выражение не скомпилировалось снова и не выполняло повторную замену быстрее или просто эффект JIT.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...