2016-10-26 4 views
7

Я знаю, что в более поздней версиях Java конкатенацииСколько Java оптимизирует конкатенацию строк с помощью +?

String test = one + "two"+ three; 

получит оптимизированный использовать StringBuilder.

Однако будет генерироваться новый StringBuilder каждый раз, когда он попадает в эту строку или будет создан единый поток StringBuilder Thread, который затем будет использоваться для всех конкатенаций строк?

Другими словами, можно ли улучшить производительность для часто вызываемого метода, создав собственный собственный поток StringBuilder для повторного использования или не получив значительных выигрышей?

Я могу просто написать тест для этого, но мне интересно, может ли это быть компилятор/JVM или что-то, на что можно ответить в более общем плане?

+1

Остерегайтесь повторного использования при конкатенации выражений. – SLaks

+1

Последнее, что я проверил, было довольно глупым, заставляя 'StringBuilder' повторно перераспределять. Но это было специфично для JDK Oracle, рассматривая полученный байт-код, и поэтому не учитывали никакой оптимизации, которую может выполнять JVM. Мое правило было: 99,999% времени, которое вам все равно, конечно; для .001%, где вам небезразлично, используйте явный 'StringBuilder', выделенный достаточно большим для обработки итогового результата. –

+0

Если вы не выполняете гораздо более строгие манипуляции, чем только одну строку, я согласен с T.J .: 99.999% времени, когда вы не увидите никакой разницы. JVM фактически будет распределять всю память как локальную для потока в любом случае (до тех пор, пока ей не понадобится поделиться с другим потоком), iiuc, поэтому ваш поток локально, вероятно, не принесет никакой пользы. – markspace

ответ

6

Насколько я знаю, нет повторного использования кода генерации кода компилятора StringBuilder экземпляров, в первую очередь javac и ECJ не генерируют повторное использование кода.

Важно подчеркнуть, что разумно не делать такого повторного использования. Небезопасно предположить, что код, возвращающий экземпляр из переменной ThreadLocal, быстрее, чем простое выделение из TLAB. Даже пытаясь добавить потенциальные затраты на локальный цикл gc для восстановления этого экземпляра, насколько мы можем определить его долю в затратах, мы не можем это сделать.

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

Особенно если учесть, что в дополнении к заявлению выше

  • виртуальных машин, таким как HotSpot есть побег анализа, который может игнорировать чистые локальные распределения, как это в целом, а также может игнорировать копировальную затрату на массив размере операций
  • Такие сложные JVM обычно также имеют оптимизации, специально предназначенные для конкатенации StringBuilder, которые лучше всего работают, когда скомпилированный код следует общему шаблону

С Java 9 изображение изменится еще раз. Затем конкатенация строк будет скомпилирована в команду invokedynamic, которая будет привязана к фабрике, предоставленной JRE во время выполнения (см. StringConcatFactory). Затем JRE решит, как будет выглядеть код, который позволяет адаптировать его к конкретному JVM, включая повторное использование буфера, если он имеет преимущество для конкретной JVM. Это также уменьшит размер кода, так как для него требуется только одна команда, а не последовательность распределения и несколько вызовов в StringBuilder.

+1

с jdk-9 картинки меняются * резко * снова :) – Eugene

6

Вы были бы удивлены, сколько усилий было уложено в конкатенацию jdk-9 String. Первый javac испускает invokedynamic вместо вызова на StringBuilder#append. Эта invokedynamic вернет CallSite с содержит MethodHandle (это фактически серия MethodHandles).

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

Тогда существует ряд стратегий, которые вы можете выбрать из когда конкатенаций строки (вы можете изменить настройки по умолчанию один через java.lang.invoke.stringConcat параметр):

private enum Strategy { 
    /** 
    * Bytecode generator, calling into {@link java.lang.StringBuilder}. 
    */ 
    BC_SB, 

    /** 
    * Bytecode generator, calling into {@link java.lang.StringBuilder}; 
    * but trying to estimate the required storage. 
    */ 
    BC_SB_SIZED, 

    /** 
    * Bytecode generator, calling into {@link java.lang.StringBuilder}; 
    * but computing the required storage exactly. 
    */ 
    BC_SB_SIZED_EXACT, 

    /** 
    * MethodHandle-based generator, that in the end calls into {@link java.lang.StringBuilder}. 
    * This strategy also tries to estimate the required storage. 
    */ 
    MH_SB_SIZED, 

    /** 
    * MethodHandle-based generator, that in the end calls into {@link java.lang.StringBuilder}. 
    * This strategy also estimate the required storage exactly. 
    */ 
    MH_SB_SIZED_EXACT, 

    /** 
    * MethodHandle-based generator, that constructs its own byte[] array from 
    * the arguments. It computes the required storage exactly. 
    */ 
    MH_INLINE_SIZED_EXACT 
} 

стратегия по умолчанию: MH_INLINE_SIZED_EXACT который является зверем!

Он использует пакет частно-конструктор для создания String (который является самым быстрым):

/* 
* Package private constructor which shares value array for speed. 
*/ 
String(byte[] value, byte coder) { 
    this.value = value; 
    this.coder = coder; 
} 

Сначала эта стратегия создает так называемый фильтры; это в основном методы, которые преобразуют входящий параметр в значение String. Как и следовало ожидать, эти MethodHandles хранятся в классе под названием Stringifiers, что в большинстве случаев производят MethodHandle, что вызывает:

String.valueOf(YourInstance) 

Так что если у вас есть 3 объектов, которые вы хотите, чтобы сцепить будет 3 MethodHandles, которые делегируют до String.valueOf(YourObject), что фактически означает, что вы превратили свои объекты в строки. В этом классе есть определенные твики, которые я до сих пор не могу понять; например, необходимость иметь отдельные классы StringifierMost (который преобразуется только в String только ссылки, float и double) и StringifierAny.

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

Способ, которым это делается с помощью методов в StringConcatHelper#mixLen, которые принимают стробированную версию ваших входных параметров (Ссылки/float/double). На этом этапе мы знаем размер нашей финальной строки. Ну, мы действительно не знаем его, у нас есть MethodHandle, который его вычислит.

В String jdk-9 есть еще одно изменение, которое стоит упомянуть здесь - добавление поля coder. Это необходимо для вычисления размера/равенства/charAt строки. Поскольку это необходимо для размера, нам также нужно вычислить его; это делается через StringConcatHelper#mixCoder.

Это безопасно на данный момент делегировать MethodHandle, который будет создавать ур массив:

@ForceInline 
    private static byte[] newArray(int length, byte coder) { 
     return (byte[]) UNSAFE.allocateUninitializedArray(byte.class, length << coder); 
    } 

Как каждый элемент добавляется? С помощью методов в StringConcatHelper#prepend.

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


Все эти операции (и многие другие, которые я пропускаюсь для простоты), обрабатываются с помощью испуская MethodHandle, который будет вызываться при приложенной на самом деле происходит.

+1

Я немного увлекся этим ответом по той простой причине, что детали такой простой операции увлекательны IMO. – Eugene

+0

Это действительно интересно, хотя, к сожалению, на самом деле он прямо не отвечает на вопрос - поэтому я чувствую, что не могу переместить галочку :( –

+1

@TimB полностью согласен, это не про галочку :). принятый ответ правильный. – Eugene