Concatenate char literal ('x') vs single char строковый литерал ( "x" )

Когда у меня есть String, мне нужно конкатенировать один char до конца, должен ли я s = .... + ']' превышать s = .... + "]" для любой причины производительности?

Я знаю объединение строк массива и строковых построек, и я НЕ прошу предложений о том, как объединить строки в целом.

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

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

Обновление

Как @Scheintod написал, что это действительно теоретический Q, и он должен сделать больше с моим желанием лучше понять, как работает java и меньше с любой реальной жизнью "позволяет сохранить другой микросекундный" сценарий... возможно, я должен был сказать это более четко.

Мне нравится понимать, как все работает "за кулисами". И я нахожу, что он может когда-нибудь помочь мне создать лучший код...

Правда - я вообще не думал о оптимизации компилятора...

Я бы не ожидал, что JIT будет использовать StringBuilder вместо String для меня... Поскольку я (возможно, ошибочно) думаю, что строковые сборщики являются "более тяжелыми", тогда Strings с одной стороны, но быстрее при создании и изменении строк с другой стороны. Поэтому я предполагаю, что в некоторых случаях использование StringBuilder было бы еще менее эффективным, чем использование stings... (если бы это было не так, то весь класс String должен был иметь свою реализацию, чтобы она была такой, как версия StringBuilder и использовать некоторое внутреннее представление для реальных неизменяемых строк... - или это то, что делает JIT? - считая, что в общем случае, вероятно, лучше не позволить разработчику выбирать...)

Если это меняет мой код до такой степени, возможно, My Q должен был быть на этом уровне, спрашивая, подходит ли JIT для выполнения чего-то подобного, и было бы лучше, если бы он использовался.

Возможно, мне стоит начать поиск скомпилированного байтового кода... [Мне нужно научиться делать это в java...]

В качестве побочного примечания и примера того, почему я даже подумал о том, чтобы посмотреть на байт-код, посмотрите на довольно старый пост в блоге о Оптимизация Actionscript 2.0 - с точки зрения байт-кода - часть I, это показывает, что знание того, что ваш код компилирует, действительно может помочь вам написать лучший код.

Ответы

Ответ 1

Помимо профилирования, у нас есть еще одна возможность получить некоторые идеи. Я хочу сосредоточиться на возможных различиях скорости, а не на том, что их снова удаляет.

Итак, давайте начнем с этого класса Test:

public class Test {

    // Do not optimize this
    public static volatile String A = "A String";

    public static void main( String [] args ) throws Exception {

        String a1 = A + "B";

        String a2 = A + 'B';

        a1.equals( a2 );

    }

}

Я скомпилировал это с помощью javac Test.java(используя javac -v: javac 1.7.0_55)

Используя javap -c Test.class, получаем:

Compiled from "Test.java"
public class Test {
  public static volatile java.lang.String A;

  public Test();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]) throws java.lang.Exception;
    Code:
       0: new           #2                  // class java/lang/StringBuilder
       3: dup
       4: invokespecial #3                  // Method java/lang/StringBuilder."<init>":()V
       7: getstatic     #4                  // Field A:Ljava/lang/String;
      10: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      13: ldc           #6                  // String B
      15: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      18: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      21: astore_1
      22: new           #2                  // class java/lang/StringBuilder
      25: dup
      26: invokespecial #3                  // Method java/lang/StringBuilder."<init>":()V
      29: getstatic     #4                  // Field A:Ljava/lang/String;
      32: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      35: bipush        66
      37: invokevirtual #8                  // Method java/lang/StringBuilder.append:(C)Ljava/lang/StringBuilder;
      40: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      43: astore_2
      44: aload_1
      45: aload_2
      46: invokevirtual #9                  // Method java/lang/String.equals:(Ljava/lang/Object;)Z
      49: pop
      50: return

  static {};
    Code:
       0: ldc           #10                 // String A String
       2: putstatic     #4                  // Field A:Ljava/lang/String;
       5: return
}

Мы видим, что задействованы два StringBuilders (строки 4, 22). Итак, первое, что мы обнаруживаем, заключается в том, что использование + для concat Strings фактически совпадает с использованием StringBuilder.

Второе, что мы видим здесь, это то, что StringBuilders вызывается дважды. Сначала для добавления изменчивой переменной (строки 10, 32) и второй раз для добавления константной части (строки 15, 37)

В случае A + "B" append вызывается с аргументом Ljava/lang/String (String), а в случае A + 'B' он вызывается с аргументом C (a char).

Таким образом, компилятор не преобразовывает String в char, но оставляет его как есть.

Теперь, находясь в AbstractStringBuilder, который содержит используемые методы, мы имеем:

public AbstractStringBuilder append(char c) {
    ensureCapacityInternal(count + 1);
    value[count++] = c;
    return this;
}

и

public AbstractStringBuilder append(String str) {
    if (str == null) str = "null";
    int len = str.length();
    ensureCapacityInternal(count + len);
    str.getChars(0, len, value, count);
    count += len;
    return this;
}

как фактически вызванные методы.

Самые дорогие операции здесь, конечно, ensureCapacity, но только в том случае, если предел достигнут (он копирует массив старых StringBuffers char [] в новый). Так что это верно для обоих и не имеет реальной разницы.

Как видно, существует множество других операций, которые выполняются, но реальное различие между value[count++] = c; и str.getChars(0, len, value, count);

Если мы посмотрим на getChars, мы увидим, что все это сводится к одному System.arrayCopy, который используется здесь для копирования массива String в буфер, плюс некоторые проверки и дополнительные вызовы методов против одного доступа к массиву.

Поэтому я бы сказал, что теоретически использование A + "B" намного медленнее, чем при использовании A + 'B'.

Я думаю, что в реальном исполнении он медленнее. Но для определения этого нам нужно ориентироваться.

EDIT: Потому что это все до того, как JIT делает это волшебство. См. Ответ Стивена C для этого.

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

EDIT2: И ТЕПЕРЬ ФУНКЦИЯ

Тесты. Этот результат генерируется путем запуска Loops 0..100M для a+'B' и a+"B" несколько раз после разминки:

a+"B": 5096 ms
a+'B': 4569 ms
a+'B': 4384 ms
a+"B": 5502 ms
a+"B": 5395 ms
a+'B': 4833 ms
a+'B': 4601 ms
a+"B": 5090 ms
a+"B": 4766 ms
a+'B': 4362 ms
a+'B': 4249 ms
a+"B": 5142 ms
a+"B": 5022 ms
a+'B': 4643 ms
a+'B': 5222 ms
a+"B": 5322 ms

в среднем:

a+'B': 4608ms
a+"B": 5167ms

Так что даже в реальном мире тестов синтаксических знаний (хе-хе) a+'B' примерно на 10% быстрее, чем a+"B"...

... по крайней мере (отказ от ответственности) в моей системе с моим компилятором и моим процессором, и он действительно не отличается/не заметен в реальных программах. Кроме причины, у вас есть кусок кода, который вы часто выполняете, и от этого зависит все ваши приложения. Но тогда вы, вероятно, сделаете что-то другое.

EDIT4:

Думая об этом. Это цикл, используемый для сравнения:

    start = System.currentTimeMillis();
    for( int i=0; i<RUNS; i++ ){
        a1 = a + 'B';
    }
    end = System.currentTimeMillis();
    System.out.println( "a+'B': " + (end-start) + " ms" );

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

Ответ 2

Когда у меня есть String, мне нужно объединить один конец char, я должен предпочесть s =.... + ']' над s =.... + "]" для любой причины производительности

Здесь есть два вопроса:

Q1: Есть ли разница в производительности?

Ответ: Это зависит...

  • В некоторых случаях, возможно, да, в зависимости от JVM и/или компилятора байт-кода. Если компилятор байт-кода генерирует вызов StringBuilder.append(char), а не StringBuilder.append(String), вы можете ожидать, что первое будет быстрее. Но JIT-компилятор может рассматривать эти методы как "intrinics" и оптимизировать вызовы append(String) с помощью символа (буква).

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

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

        System.out.println("234" + "]");
    
        System.out.println("234" + ']');
    

    Это гарантируется JLS.

Q2: Если вы предпочитаете одну версию поверх другой.

Ответ:

  • В общем смысле это, вероятно, будет преждевременная оптимизация. Вы предпочитаете только одну форму над другой по соображениям производительности, если вы профилировали свой код на уровне приложения и определили, что фрагмент кода оказывает заметное влияние на производительность.

  • Если вы профилировали код, используйте ответ на Q1 в качестве руководства.

    И если бы стоило попытаться оптимизировать фрагмент, то важно, чтобы вы повторно запустили ваш бенчмаркинг/профилирование после оптимизации, чтобы убедиться, что это имеет значение. Ваша интуиция о том, что является самым быстрым... и то, что вы прочитали в какой-то старой статье в Интернете... может быть очень неправильным.

Ответ 3

Нет! Это преждевременная оптимизация и полная трата времени. Предпочитайте в зависимости от того, что вы предпочитаете, и используйте то, что работает. Иногда ']' не будет продвигать "]" автоматически. Я ожидаю, что JIT оптимизирует любую разницу в производительности, которую, по вашему мнению, вы измеряете. И в тех случаях, когда он работает, он работает, потому что ']' был преобразован в "]" (в вашем примере). Теперь использование StringBuilder напрямую и append(char) может быть немного быстрее... но опять же, это различие не будет иметь смысла в любом реальном приложении.

Edit

Лучший совет, который я могу дать вам о производительности:

  • Перед тем, как приступить к внесению изменений в код (и попытайтесь ограничить себя областями, в которых вы, вероятно, будете иметь влияние посредством реальной идентификации критического пути) и
  • Если вы можете, поместите результат, а не создайте временную String, где это возможно (иногда это приносит огромные дивиденды в отношении использования памяти и производительности).