Компилятор Java не оптимизирует конкатенацию строк автоматически?

Следующий код Jsoup объединяет текст всех элементы в контейнере els:

for (Element el : els)
  entireText += el.text();          

В контейнере примерно с 64 элементами, каждый из которых содержит примерно 1 КБ (всего около 64 КБ в тексте), этот простой цикл занимает около 8 секунд на обычном бюджетном телефоне Android.

Такая низкая производительность меня немного удивляет, потому что у меня сложилось впечатление, что компиляторы Java заменяют такие выражения, как A + B + C, на new StringBuilder(A).append(B).append(C).toString().

Разве это не так?

Что мне не хватает?


person Souper    schedule 03.07.2012    source источник
comment
Вы сравнивали два варианта или это просто предположение, что конкатенация строк является основной причиной вашей проблемы?   -  person home    schedule 03.07.2012
comment
@home Конечно, я сравнил. Параметр StringBuilder делает это менее чем за 1 секунду.   -  person Souper    schedule 03.07.2012
comment
Компилятор javac почти не занимается оптимизацией. Даже с JIT это не сильно оптимизирует этот код. Разработчик должен знать, что это действительно эффективно.   -  person Peter Lawrey    schedule 03.07.2012


Ответы (2)


Такая низкая производительность меня удивляет, потому что у меня сложилось впечатление, что компиляторы Java заменяют такие выражения, как A + B + C, на new StringBuilder(A).append(B).append(C).toString().

Итак, компилятор создает код:

for (Element el : els)
  entireText = new StringBuilder(entireText).append(el.text()).toString(); 

Вам нужно будет создать StringBuilder вне цикла и добавить его вручную.

person Michael Lloyd Lee mlk    schedule 03.07.2012
comment
Почему бы не добавлять текст на каждой итерации к одному экземпляру StringBuilder? Для чего вы делаете new StringBuilder каждую итерацию? - person alaster; 03.07.2012
comment
@mlk А, хорошо. Я думал, что компилятор Java умнее этого. - person Souper; 03.07.2012
comment
@alaster - вы должны создать один StringBuilder. Мой код должен был показать, что делает компилятор, а не то, что должен делать OP. - person Michael Lloyd Lee mlk; 03.07.2012
comment
@Souper: на самом деле не может. Вы запрашиваете и создаете переменную String. Компилятор не может игнорировать этот запрос и не построить этот объект. JVM могла теоретически определить, что вы никогда не используете String, кроме как для создания нового и вместо этого создаете StringBuilder, но я сомневаюсь, что какая-либо JVM выполняет такую ​​оптимизацию. - person Joachim Sauer; 03.07.2012

Проблема здесь в том, что ваша первая итерация создает одну строку 1k, вторая создает строку 2k, третья создает строку 3k,...

И каждая строка требует создания копии предыдущей. Итак, ваша первая итерация копирует 1k текста, вторая копирует 2k, третья копирует 3k,...

Таким образом, каждая итерация медленнее предыдущей, а ваша последняя итерация выделяет буфер размером 64 КБ и копирует 64 КБ.

Использование StringBuilder (как показал @mlk) означает, что вы просто выделяете 64 КБ один раз (не совсем, но достаточно близко) и копируете всего 64 КБ данных (в отличие от 64 КБ + 63 КБ + 62 КБ + 61 КБ + 60 КБ +...).

person Joachim Sauer    schedule 03.07.2012