Самый эффективный размер начальной емкости для StringBuilder?

Я пишу много вещей, чтобы регистрировать всплески и оптимизировать путь данных. Я строю текст журнала с помощью StringBuilder. Какова будет наиболее эффективная начальная емкость с точки зрения управления памятью, чтобы она хорошо работала независимо от JVM? Цель состоит в том, чтобы почти всегда избегать перераспределения, которое должно быть покрыто начальной емкостью около 80-100. Но я также хочу потерять как можно меньше байтов, поскольку экземпляр StringBuilder может зависать в буфере, и появляются потерянные байты.

Я понимаю, что это зависит от JVM, но должно быть какое-то значение, которое будет тратить наименьшее количество байтов, независимо от JVM, своего рода «наименьший общий знаменатель». В настоящее время я использую 128-16, где 128 — хорошее круглое число, а вычитание используется для накладных расходов. Кроме того, это можно рассматривать как случай «преждевременной оптимизации», но, поскольку ответ, который мне нужен, представляет собой «эмпирическое правило», знание этого будет полезно и в будущем.

Я не ожидаю ответов «наилучшее предположение» (мой собственный ответ выше уже такой), я надеюсь, что кто-то уже исследовал это и может поделиться ответом, основанным на знаниях.


person hyde    schedule 13.11.2012    source источник
comment
Ответ на этот вопрос зависит от многих вещей, например, от длины текста, который вы храните в StringBuilder и т. д. Единственный способ узнать это — измерить с помощью профилировщика памяти и/или процессора. Нет причин беспокоиться о нескольких байтах, если только вы не создаете сотни тысяч StringBuilder объектов.   -  person Jesper    schedule 13.11.2012
comment
Безусловно, самые большие накладные расходы — это стоимость ввода-вывода. Если вы не собираетесь записывать эти данные в IO, я бы не стал об этом беспокоиться.   -  person Peter Lawrey    schedule 13.11.2012


Ответы (2)


Не пытайтесь быть умным в этом случае.

В настоящее время я использую 128-16, где 128 — хорошее круглое число, а вычитание используется для накладных расходов.

В Java это основано на совершенно произвольных предположениях о внутренней работе JVM. Java — это не C. Выравнивание по байтам и тому подобное — абсолютно не проблема, которую программист может или должен попытаться использовать.

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

Если вы действительно знаете, что огромное количество ваших StringBuilder будет храниться в течение очень долгого времени (что не совсем соответствует концепции ведения журнала), и вы действительно чувствуете необходимость попытайтесь убедить JVM сохранить несколько байтов пространства кучи, которое вы можете попробовать, и использовать trimToSize() после полного построения строки. Но, опять же, до тех пор, пока ваши строки не тратят впустую мегабайты каждая, вам действительно следует сосредоточиться на других проблемах в вашем приложении.

person JimmyB    schedule 13.11.2012

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

Используя JDK 1.7.0_07 и тестовое приложение, сообщающее имя виртуальной машины «Java HotSpot™ 64-Bit Server VM», степень детализации использования памяти StringBuilder составляет 4 символа, увеличиваясь даже на 4 символа.

Ответ: любое число, кратное 4, одинаково подходит для StringBuilder с точки зрения распределения памяти, по крайней мере, на этой 64-битной JVM.

Протестировано путем создания 1 000 000 объектов StringBuilder с разной начальной емкостью, в различных исполнениях тестовой программы (чтобы иметь одинаковое начальное состояние кучи) и распечатки ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed() до и после.

Распечатка размеров кучи также подтвердила, что сумма, фактически выделенная из кучи для каждого буфера StringBuilder, кратна 8 байтам, как и ожидалось, поскольку длина символа Java составляет 2 байта. Другими словами, выделение 1000000 экземпляров с начальной емкостью 1..4 требует примерно на 8 мегабайт памяти меньше (8 байт на экземпляр), чем выделение такого же количества экземпляров с начальной емкостью 5...8.

person hyde    schedule 13.11.2012
comment
Не могли бы вы поделиться своими методами тестирования? - Как вам удается определять использование кучи с такой степенью детализации? - person JimmyB; 14.11.2012
comment
У меня нет кода под рукой, но использование кучи увеличивалось на шаг вверх при каждом увеличении начальной емкости StringBuilder на 4 единицы, затем было примерно таким же для 3 следующих размеров, прежде чем снова подпрыгнуть на следующем кратном 4. Но это 4 символа, то есть 8 байт, верно? Спасибо за вопрос, я обязательно проверю завтра, чтобы убедиться в этом. - person hyde; 14.11.2012
comment
Итак, вы наблюдали увеличение использования кучи с шагом 1000000 x 4 байта? -- Я не осмеливаюсь думать о том, чтобы оценить, сколько байтов пространства кучи Java будет занимать [структура данных] ни для char, ни для любого другого значения/типа в любой программе Java. - Кроме того, независимо от степени детализации выделения кучи, степень детализации, с которой сборщик мусора решает освободить память обратно в кучу, неизвестна и будет влиять на любое измерение. - Если вы проводите тестирование из любопытства и/или для измерения некоторых характеристик данной JVM, продолжайте. - Иначе... см. мой ответ выше :) - person JimmyB; 14.11.2012
comment
Я заметил, что 1000000 x new StringBuilder(112) занимает примерно такое же количество кучи, что и 1000000 x new StringBuilder(115). Увеличение емкости до 116 заметно увеличило использование кучи, 120 снова увеличило ее и т. д. Я был довольно удивлен, когда подумал, что это 4 байта, но 4 символа = 8 байтов имеет гораздо больше смысла (на 64-битной JVM). - person hyde; 14.11.2012