-Поиск по дневнику

Поиск сообщений в rss_rss_hh_new

 -Подписка по e-mail

 

 -Статистика

Статистика LiveInternet.ru: показано количество хитов и посетителей
Создан: 17.03.2011
Записей:
Комментариев:
Написано: 51


Цепочка вызовов append(x).append(y) в StringBuilder работает быстрее чем типичные sb.append(x); sb.append(y)

Вторник, 13 Июня 2017 г. 09:50 + в цитатник
Всем привет, к прошлой статье о наследии StringBuffer в комментариях оставили интересную ссылку. В этой статье есть интересный бенчмарк, который я изменил для придания большей драматичности:

@BenchmarkMode(Mode.Throughput)
@Fork(1)
@State(Scope.Thread)
@Warmup(iterations = 10, time = 1, batchSize = 1000)
@Measurement(iterations = 40, time = 1, batchSize = 1000)
public class Chaining {

    private String a1 = "111111111111111111111111";
    private String a2 = "222222222222222222222222";
    private String a3 = "333333333333333333333333";

    @Benchmark
    public String typicalChaining() {
        return new StringBuilder().append(a1).append(a2).append(a3).toString();
    }
    
    @Benchmark
    public String noChaining() {
        StringBuilder sb = new StringBuilder();
        sb.append(a1);
        sb.append(a2);
        sb.append(a3);
        return sb.toString();
    }

}


Результат:
Benchmark                  Mode  Cnt      Score      Error  Units
Chaining.noChaining       thrpt   40   8408.703 ±  214.582  ops/s
Chaining.typicalChaining  thrpt   40  35830.907 ± 1277.455  ops/s

Итого, конкатеницая через цепочку вызовов sb.append().append() в 4 раза быстрее… Автор из статьи выше утверждает, что разница связана с тем, что в случае цепочки вызовов генерируется меньше байткода и, соответственно, он выполняется быстрее.
Ну что ж, давайте проверим.


Разница в байткоде?


Гипотезу можно легко проверить без углубления в байт код — создадим типичный UriBuilder:

public class UriBuilder {

    private String schema;
    private String host;
    private String path;

    public UriBuilder setSchema(String schema) {
        this.schema = schema;
        return this;
    }

   ...

   @Override
   public String toString() {
       return schema + "://" + host + path;
   }
}


И повторим бенчмарк:

@BenchmarkMode(Mode.Throughput)
@Fork(1)
@State(Scope.Thread)
@Warmup(iterations = 10, time = 1, batchSize = 1000)
@Measurement(iterations = 40, time = 1, batchSize = 1000)
public class UriBuilderChaining {

    private String host = "host";
    private String schema = "http";
    private String path = "/123/123/123";

    @Benchmark
    public String chaining() {
        return new UriBuilder().setSchema(schema).setHost(host).setPath(path).toString();
    }

    @Benchmark
    public String noChaining() {
        UriBuilder uriBuilder = new UriBuilder();
        uriBuilder.setSchema(schema);
        uriBuilder.setHost(host);
        uriBuilder.setPath(path);
        return uriBuilder.toString();
    }

}


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

Результат:
Benchmark                       Mode  Cnt      Score      Error  Units
UriBuilderChaining.chaining    thrpt   40  35797.519 ± 2051.165  ops/s
UriBuilderChaining.noChaining  thrpt   40  36080.534 ± 1962.470  ops/s


Хм… Разница на уровне погрешности. Значит количество байткода тут ни при чем. Так как аномалия проявляется со StringBuilder и append(), то наверное это как-то связано с известной JVM опцией +XX:OptimizeStringConcat. Давайте проверим. Повторим самый первый тест, но с отключенной опцией.

В JMH через аннотации сделать это можно так:
@Fork(value = 1, jvmArgsAppend = "-XX:-OptimizeStringConcat")

Повторяем первый тест:
Benchmark                  Mode  Cnt     Score     Error  Units
Chaining.noChaining       thrpt   40  7598.743 ± 554.192  ops/s
Chaining.typicalChaining  thrpt   40  7946.422 ± 313.967  ops/s


Бинго!
Так как соединение строк через x + y довольно частая операция в любом приложении — Hotspot JVM находит new StringBuilder().append(x).append(y).toString() паттерны в байткоде и заменяет их на оптимизированный машинний код, обходясь без создания промежуточных объектов.
К сожалению, эта оптимизация не применяется к sb.append(x); sb.append(y);. Разница на больших строках может быть на порядок.

Выводы


Используйте паттерн «цепочка вызовов» (method chaining), где это возможно. Во-первых, в случае StringBuilder это поможет JIT заоптимизировать конкатенацию строк. Во-вторых, так генерируется меньше байт кода и это действительно может помочь заинлайнить Ваш метод в некоторых случаях.

Вопрос на SO
Связанный доклад с грязными подробностями от Шипилева.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/330220/

Метки:  

 

Добавить комментарий:
Текст комментария: смайлики

Проверка орфографии: (найти ошибки)

Прикрепить картинку:

 Переводить URL в ссылку
 Подписаться на комментарии
 Подписать картинку