[PATCH] Use StringJoiner where appropriate in java.base

Сергей Цыпанов sergei.tsypanov at yandex.ru
Thu Jun 20 12:53:04 UTC 2019


Hello,

pay attention that results also depend on String length: e.g. consider this benchmark https://github.com/stsypanov/strings/blob/master/src/main/java/tsypanov/strings/benchmark/string/StringBuilderVsStringJoinerBenchmark.java

On JDK 11 I've got the following results

https://github.com/stsypanov/strings/blob/master/results/StringBuilderVsStringJoinerBenchmark.txt

In other words StringJoiner wins when we have longer Strings or more of them because we don't need to reallocate underlying array of StringBuilder and check it's bounds when appending. Instead we can allocate storage only once in StringJoine::toString.

With best regards,
Sergey Tsypanov


20.06.2019, 13:17, "Kasper Nielsen" <kasperni at gmail.com>:
> On Thu, 20 Jun 2019 at 11:55, Peter Levart <peter.levart at gmail.com> wrote:
>>  Hi,
>>
>>  On 6/20/19 10:50 AM, Kasper Nielsen wrote:
>>  > Hi,
>>  >
>>  > On Wed, 19 Jun 2019 at 14:12, Сергей Цыпанов <sergei.tsypanov at yandex.ru> wrote:
>>  >> Hello,
>>  >>
>>  >> in JDK code base we have many places (mainly in j.u.Arrays) where we convert array to String using verbose constructions with StringBuilder.
>>  >>
>>  >> As far as we have got StringJoiner for a long time we can use it making the code more simple.
>>  > It may make the code simpler, but it also comes with a hefty
>>  > performance penalty, taking twice as long in most cases compared to
>>  > the existing code.
>>  >
>>  > A quick benchmark toString'ing int arrays of size 1,10,100,1000
>>  >
>>  > Benchmark (size) Mode Cnt Score Error Units
>>  > ToString2.toStringExisting 1 avgt 5 16.675 ± 0.327 ns/op
>>  > ToString2.toStringExisting 10 avgt 5 78.467 ± 1.373 ns/op
>>  > ToString2.toStringExisting 100 avgt 5 801.956 ± 7.517 ns/op
>>  > ToString2.toStringExisting 1000 avgt 5 14944.235 ± 155.393 ns/op
>>  >
>>  > ToString2.toStringSuggested 1 avgt 5 35.053 ± 0.533 ns/op
>>  > ToString2.toStringSuggested 10 avgt 5 222.043 ± 10.157 ns/op
>>  > ToString2.toStringSuggested 100 avgt 5 2150.948 ± 13.285 ns/op
>>  > ToString2.toStringSuggested 1000 avgt 5 23411.264 ± 201.721 ns/op
>>  >
>>  > /Kasper
>>
>>  StringJoiner may be faster if you already have String objects at hand,
>>  since it is able to exactly pre-size the target array and need not
>>  copy/resize it later, but if you append primitive types, then creating
>>  intermediate String objects referenced from a data structure - the
>>  StringJointer (so they can not be scalarized by JIT) is contra-productive.
>>
>>  An interesting test would be to run Kasper's JMH benchmark with "-prof
>>  gc" option. I think it will show more garbage created per call too.
>
> Yes, less garbage with StringBuilder (existing)
>
> ToString2.toStringExisting:·gc.alloc.rate.norm 1 avgt
>    5 80.000 ± 0.001 B/op
> ToString2.toStringExisting:·gc.alloc.rate.norm 10 avgt
>    5 160.000 ± 0.001 B/op
> ToString2.toStringExisting:·gc.alloc.rate.norm 100 avgt
>    5 1664.000 ± 0.001 B/op
> ToString2.toStringExisting:·gc.alloc.rate.norm 1000 avgt
>    5 23536.006 ± 0.001 B/op
>
> ToString2.toStringSuggested:·gc.alloc.rate.norm 1 avgt
>    5 168.000 ± 0.001 B/op
> ToString2.toStringSuggested:·gc.alloc.rate.norm 10 avgt
>    5 760.000 ± 0.001 B/op
> ToString2.toStringSuggested:·gc.alloc.rate.norm 100 avgt
>    5 7144.001 ± 0.001 B/op
> ToString2.toStringSuggested:·gc.alloc.rate.norm 1000 avgt
>    5 71024.010 ± 0.001 B/op
>
> If you allowed a two-time pass of the primitive array you could
> actually create a version that only allocated the actual String and
> backing array.
> But don't know if it is worth it. But then again I'm always surprised
> by the amount of string processing being done.
>
> /Kasper


More information about the core-libs-dev mailing list