Должен ли я использовать Java String.format (), если важна производительность? - PullRequest
197 голосов
/ 05 февраля 2009

Мы должны постоянно строить строки для вывода журнала и так далее. В версиях JDK мы узнали, когда использовать StringBuffer (многие добавления, поточно-ориентированные) и StringBuilder (многие добавления, не поточнобезопасные).

Какой совет по использованию String.format()? Это эффективно, или мы вынуждены придерживаться конкатенации для однострочников, где важна производительность?

например. уродливый старый стиль,

String s = "What do you get if you multiply " + varSix + " by " + varNine + "?";

против. аккуратный новый стиль (String.format, который возможно медленнее),

String s = String.format("What do you get if you multiply %d by %d?", varSix, varNine);

Примечание: мой конкретный вариант использования - это сотни строк журнала «с одной строкой» в моем коде. Они не содержат петли, поэтому StringBuilder слишком тяжелый. Меня особенно интересует String.format().

Ответы [ 13 ]

232 голосов
/ 15 августа 2009

Я взял hhafez код и добавил тест памяти :

private static void test() {
    Runtime runtime = Runtime.getRuntime();
    long memory;
    ...
    memory = runtime.freeMemory();
    // for loop code
    memory = memory-runtime.freeMemory();

Я запускаю это отдельно для каждого подхода, оператора '+', String.format и StringBuilder (вызывая toString ()), поэтому на используемые методы не влияют другие подходы. Я добавил еще несколько конкатенаций, сделав строку «Бла» + я + «Бла» + я + «Бла» + я + «Бла».

Результат выглядит следующим образом (в среднем по 5 прогонов каждый):
Время приближения (мс) Выделена память (длинная)
Оператор «+» 747 320 504
Формат строки 16484 373 312
StringBuilder 769 57 344

Мы видим, что String '+' и StringBuilder практически идентичны по времени, но StringBuilder намного более эффективен в использовании памяти. Это очень важно, когда у нас есть много вызовов журнала (или любых других операторов, включающих строки) за достаточно короткий промежуток времени, поэтому сборщик мусора не сможет очистить множество строковых экземпляров, полученных в результате оператора '+'.

И заметка, кстати, не забудьте проверить запись уровень перед построением сообщения.

Выводы:

  1. Я буду продолжать использовать StringBuilder.
  2. У меня слишком много времени или слишком мало жизни.
115 голосов
/ 05 февраля 2009

Я написал небольшой класс для тестирования, который имеет лучшую производительность, чем два, и + опережает формат. в 5-6 раз. Попробуйте сами

import java.io.*;
import java.util.Date;

public class StringTest{

    public static void main( String[] args ){
    int i = 0;
    long prev_time = System.currentTimeMillis();
    long time;

    for( i = 0; i< 100000; i++){
        String s = "Blah" + i + "Blah";
    }
    time = System.currentTimeMillis() - prev_time;

    System.out.println("Time after for loop " + time);

    prev_time = System.currentTimeMillis();
    for( i = 0; i<100000; i++){
        String s = String.format("Blah %d Blah", i);
    }
    time = System.currentTimeMillis() - prev_time;
    System.out.println("Time after for loop " + time);

    }
}

Выполнение вышеуказанного для разных N показывает, что оба ведут себя линейно, но String.format медленнее в 5-30 раз.

Причина в том, что в текущей реализации String.format сначала анализирует ввод с помощью регулярных выражений, а затем заполняет параметры. С другой стороны, конкатенация с плюсом оптимизируется с помощью javac (а не JIT) и напрямую использует StringBuilder.append.

Runtime comparison

23 голосов
/ 26 июня 2015

Все представленные здесь критерии имеют некоторые недостатки , поэтому результаты не являются надежными.

Я был удивлен, что никто не использовал JMH для бенчмаркинга, поэтому я так и сделал.

Результаты:

Benchmark             Mode  Cnt     Score     Error  Units
MyBenchmark.testOld  thrpt   20  9645.834 ± 238.165  ops/s  // using +
MyBenchmark.testNew  thrpt   20   429.898 ±  10.551  ops/s  // using String.format

Единицы - это операции в секунду, чем больше, тем лучше. Исходный код теста . Использовалась OpenJDK IcedTea 2.5.4 Java Virtual Machine.

Итак, старый стиль (с использованием +) намного быстрее.

20 голосов
/ 29 июня 2011

Ваш старый уродливый стиль автоматически компилируется JAVAC 1.6 как:

StringBuilder sb = new StringBuilder("What do you get if you multiply ");
sb.append(varSix);
sb.append(" by ");
sb.append(varNine);
sb.append("?");
String s =  sb.toString();

Так что нет абсолютно никакой разницы между этим и использованием StringBuilder.

String.format намного тяжелее, поскольку он создает новый Formatter, анализирует строку входного формата, создает StringBuilder, добавляет к нему все и вызывает toString ().

12 голосов
/ 20 августа 2011

Java String.format работает так:

  1. анализирует строку формата, разбивая ее на список фрагментов формата
  2. он повторяет фрагменты формата, рендеринг в StringBuilder, который в основном является массивом, который изменяет размеры по мере необходимости, копируя в новый массив. это необходимо, потому что мы еще не знаем, насколько велика для выделения финальная строка
  3. StringBuilder.toString () копирует свой внутренний буфер в новую строку

если конечным пунктом назначения для этих данных является поток (например, рендеринг веб-страницы или запись в файл), вы можете собрать фрагменты формата непосредственно в свой поток:

new PrintStream(outputStream, autoFlush, encoding).format("hello {0}", "world");

Я предполагаю, что оптимизатор оптимизирует обработку строки формата. Если это так, то вы останетесь с эквивалентной амортизированной производительностью, чтобы вручную развернуть ваш String.format в StringBuilder.

8 голосов
/ 05 февраля 2009

Чтобы развернуть / исправить первый ответ выше, String.format не поможет в переводе.
String.format поможет вам при печати даты / времени (или числового формата и т. Д.), Где существуют различия в локализации (l10n) (т. Е. Некоторые страны будут печатать 04Feb2009, а другие - фев042009).
При переводе вы просто говорите о перемещении любых внешних строк (таких как сообщения об ошибках и тому подобное) в пакет свойств, чтобы вы могли использовать правильный пакет для нужного языка, используя ResourceBundle и MessageFormat.

Глядя на все вышесказанное, я бы сказал, что с точки зрения производительности, String.format и простой конкатенации сводится к тому, что вы предпочитаете. Если вы предпочитаете смотреть на вызовы .format, а не на конкатенацию, то обязательно используйте это.
В конце концов, код читается намного больше, чем написано.

6 голосов
/ 05 февраля 2009

В вашем примере производительность Probalby не слишком отличается, но есть и другие вопросы, которые следует учитывать: а именно фрагментация памяти. Даже операция конкатенации создает новую строку, даже если она временная (для ее сборки требуется время, и это требует больше работы). String.format () просто более читабелен и требует меньше фрагментации.

Кроме того, если вы часто используете определенный формат, не забывайте, что вы можете использовать класс Formatter () напрямую (все, что делает String.format () - это создание экземпляра одноразового Formatter).

Также, кое-что еще, что вы должны знать: будьте осторожны с использованием substring (). Например:

String getSmallString() {
  String largeString = // load from file; say 2M in size
  return largeString.substring(100, 300);
}

Эта большая строка все еще находится в памяти, потому что именно так работают подстроки Java. Лучшая версия:

  return new String(largeString.substring(100, 300));

или

  return String.format("%s", largeString.substring(100, 300));

Вторая форма, вероятно, более полезна, если вы делаете другие вещи одновременно.

5 голосов
/ 05 февраля 2009

Как правило, вы должны использовать String.Format, потому что он относительно быстрый и поддерживает глобализацию (при условии, что вы на самом деле пытаетесь написать что-то, что читается пользователем). Это также упрощает глобализацию, если вы пытаетесь перевести одну строку вместо 3 или более на оператор (особенно для языков, которые имеют резко отличающиеся грамматические структуры).

Теперь, если вы никогда не планируете что-либо переводить, тогда либо полагайтесь на встроенную в Java конвертацию операторов + в StringBuilder. Или используйте Java StringBuilder явно.

3 голосов
/ 21 мая 2015

Другая перспектива только с точки зрения ведения журнала.

Я вижу много дискуссий, связанных с входом в эту ветку, поэтому подумал добавить свой опыт в ответ. Может быть, кто-то найдет это полезным.

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

Вам не нужно выполнять конкататацию / форматирование, если вы не хотите регистрироваться. Скажем, если я определю метод, подобный этому

public void logDebug(String... args, Throwable t) {
    if(debugOn) {
       // call concat methods for all args
       //log the final debug message
    }
}

В этом подходе cancat / formatter на самом деле вообще не вызывается, если это сообщение отладки и debugOn = false

Хотя здесь все равно будет лучше использовать StringBuilder вместо форматера. Основная мотивация - избегать всего этого.

В то же время я не люблю добавлять блок «if» для каждого оператора логирования, начиная с

  • Влияет на читабельность
  • Уменьшает охват моих модульных тестов - это сбивает с толку, когда вы хотите убедиться, что каждая строка проверена.

Поэтому я предпочитаю создавать класс утилиты ведения журнала с помощью методов, описанных выше, и использовать его везде, не беспокоясь о падении производительности и любых других проблемах, связанных с ним.

2 голосов
/ 05 февраля 2009

Я только что изменил тест Хафеза, чтобы включить StringBuilder. StringBuilder в 33 раза быстрее, чем String.format с использованием клиента jdk 1.6.0_10 в XP. Использование ключа -server снижает коэффициент до 20.

public class StringTest {

   public static void main( String[] args ) {
      test();
      test();
   }

   private static void test() {
      int i = 0;
      long prev_time = System.currentTimeMillis();
      long time;

      for ( i = 0; i < 1000000; i++ ) {
         String s = "Blah" + i + "Blah";
      }
      time = System.currentTimeMillis() - prev_time;

      System.out.println("Time after for loop " + time);

      prev_time = System.currentTimeMillis();
      for ( i = 0; i < 1000000; i++ ) {
         String s = String.format("Blah %d Blah", i);
      }
      time = System.currentTimeMillis() - prev_time;
      System.out.println("Time after for loop " + time);

      prev_time = System.currentTimeMillis();
      for ( i = 0; i < 1000000; i++ ) {
         new StringBuilder("Blah").append(i).append("Blah");
      }
      time = System.currentTimeMillis() - prev_time;
      System.out.println("Time after for loop " + time);
   }
}

Хотя это может звучать радикально, но я считаю, что это уместно только в редких случаях, потому что абсолютные числа довольно низкие: 4 с на 1 миллион простых вызовов String.format вроде бы хорошо - если я использую их ведение журнала или тому подобное.

Обновление: Как указано sjbotha в комментариях, тест StringBuilder недопустим, поскольку в нем отсутствует окончательный .toString().

Правильный коэффициент ускорения от String.format(.) до StringBuilder на моей машине равен 23 (16 с переключателем -server).

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