Почему .toString () кажется, исправляет исключение OutOfMemoryError для StringBuilder?

JVon спросил: 13 июня 2018 в 10:49 в: java

Я изучаю, как микрообычать вещи с помощью JMH. Я начал с чего-то вроде простого: string concatenation для StringBuilder vs String +=.

С моей точки зрения, я должен создать объект State, который содержит экземпляр StringBuilder, потому что я не хочу тестировать его конструктор (и я не хочу, чтобы пустая была на каждой итерации). То же самое касается теста String += - я хочу, чтобы объект String в моем State был объединен с новыми строками.

Это мой code:

@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class Test {    @State(Scope.Thread)
    public static class BenchmarkState {        public StringBuilder    builder;
        public String           regularString;        @Setup(Level.Iteration)
        public void setup() {
            builder         = new StringBuilder();
            regularString   = "";
        }    }    @Benchmark
    public String stringTest(BenchmarkState state) {
        state.regularString += "hello";
        return state.regularString;
    }    @Benchmark
    public String stringBuilderTest(BenchmarkState state) {
        state.builder.append("hello");
        return state.builder.toString();
    }    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(Test.class.getSimpleName())
                .forks(1)
                .timeUnit(TimeUnit.MILLISECONDS)
                .mode(Mode.Throughput)
                .measurementTime(TimeValue.seconds(10))
                .build();        new Runner(opt).run();
    }}

Он работает, но я думал: я не хочу вызывать .toString() в конце каждой итерации. Я тестирую только конкатенацию. Поэтому я решил удалить его, просто вернув null.

Но тогда это происходит во время первой итерации прогрева:

java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Arrays.java:3332)
    at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
    at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:448)
    at java.lang.StringBuilder.append(StringBuilder.java:136)

Я понимаю, что у меня скоро закончится память, если JMH добавит к StringBuilder настолько быстро, насколько это возможно, поэтому я не удивлен проблемой OutOfMemoryError. Но я не понимаю, почему это исправляет builder.toString().

Итак, мои вопросы:

  • Почему builder.toString() избежать проблемы OutOfMemoryError? Не сохраняет ли StringBuilder все символы в памяти?

  • Предполагая, что я НЕ хочу ни конструктора StringBuilder или его метод .toString(), чтобы быть частью эталона, как правильно записать этот тест?


1 ответ

Andreas ответил: 13 июня 2018 в 10:57

Вызов toString() требует времени и генерирует мусор, требующий GC-прогона, что еще больше замедляет работу кода.

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

Jason Sperske ответил: 13 июня 2018 в 11:01
Таким образом, вызов toString был бы эквивалентен тому, чтобы сообщить потоку спать в течение короткого промежутка времени между тестовыми запусками? Интересная теория :)
Andreas ответил: 13 июня 2018 в 11:06
@JasonSperske Сон? Нет, совсем нет. Почему вы думаете, что я это сказал? Я сказал, что код будет делать больше (выполнить toString) и что он генерирует больше мусора, требуя, чтобы JVM собирал мусор чаще (т.е. делал больше ), замедлял вниз, т.е. ухудшение производительности кода. Если вы контролируете CPU, вы не видите, что спать не происходит.

Дополнительное видео по вопросу: Почему .toString () кажется, исправляет исключение OutOfMemoryError для StringBuilder?

Алексей Шипилёв — Java Benchmarking: как два таймстампа прочитать!

Алексей Шипилёв — OpenJDK Frameworks: jmh & jcstress

Java Strings. Урок 2. Приведение типов данных к строке