Skip to content

Conversation

@franz1981
Copy link
Contributor

@franz1981 franz1981 commented Jan 9, 2026

https://hibernate.atlassian.net/browse/HHH-20054


By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license
and can be relicensed under the terms of the LGPL v2.1 license in the future at the maintainers' discretion.
For more information on licensing, please check here.


@hibernate-github-bot
Copy link

hibernate-github-bot bot commented Jan 9, 2026

Thanks for your pull request!

This pull request appears to follow the contribution rules.

› This message was automatically generated.

@franz1981
Copy link
Contributor Author

I've created a JMH benchmark to show the type of improvement

package red.hat.puzzles.string;

import org.openjdk.jmh.annotations.*;

import java.util.Locale;
import java.util.concurrent.TimeUnit;

@State(Scope.Benchmark)
@Warmup(iterations = 10, time = 400, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 10, time = 400, timeUnit = TimeUnit.MILLISECONDS)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@BenchmarkMode(Mode.AverageTime)
@Fork(2)
public class StringBuilderVsFormat {

    private static final String EXTERNALIZED_PREFIX = "ExternalizedType";

    // Simulated fixed values
    private int count;
    private String typeClassName;
    private int defaultSqlTypeCode;

    @Setup(Level.Trial)
    public void setup() {
        count = 12345;
        typeClassName = "java.lang.String";
        defaultSqlTypeCode = 4;
    }

    @Benchmark
    public String stringFormat() {
        return String.format(
                Locale.ROOT,
                "%s@%s(%s,%s)",
                EXTERNALIZED_PREFIX,
                count,
                typeClassName,
                defaultSqlTypeCode
        );
    }

    @Benchmark
    public String stringConcat() {
        return EXTERNALIZED_PREFIX + '@' + count + '(' + typeClassName + ',' + defaultSqlTypeCode + ')';
    }
}

which will result, on my machine with OpenJDK 25

Benchmark                                              Mode  Cnt     Score     Error   Units
StringBuilderVsFormat.stringConcat                     avgt   20    11.519 ±   0.395   ns/op
StringBuilderVsFormat.stringConcat:gc.alloc.rate       avgt   20  7292.675 ± 229.618  MB/sec
StringBuilderVsFormat.stringConcat:gc.alloc.rate.norm  avgt   20    88.000 ±   0.001    B/op
StringBuilderVsFormat.stringConcat:gc.count            avgt   20    82.000            counts
StringBuilderVsFormat.stringConcat:gc.time             avgt   20    34.000                ms
StringBuilderVsFormat.stringFormat                     avgt   20   131.551 ±   1.885   ns/op
StringBuilderVsFormat.stringFormat:gc.alloc.rate       avgt   20  5334.785 ±  74.175  MB/sec
StringBuilderVsFormat.stringFormat:gc.alloc.rate.norm  avgt   20   736.002 ±   0.001    B/op
StringBuilderVsFormat.stringFormat:gc.count            avgt   20    72.000            counts
StringBuilderVsFormat.stringFormat:gc.time             avgt   20    32.000                ms

@franz1981
Copy link
Contributor Author

franz1981 commented Jan 9, 2026

For who is not a fan of string concat indy, I've added

    @Benchmark
    public String stringBuilderWithPrecomputedSize() {
        int chars = EXTERNALIZED_PREFIX.length() + 1 // '@'
                + stringSize(count)
                + 2 // '(' and ','
                + typeClassName.length()
                + stringSize(defaultSqlTypeCode)
                + 1; // ')'
        StringBuilder nameBuilder = new StringBuilder(chars);
        nameBuilder.append(EXTERNALIZED_PREFIX)
                .append('@')
                .append(count)
                .append('(')
                .append(typeClassName)
                .append(',')
                .append(defaultSqlTypeCode)
                .append(')');
        return nameBuilder.toString();
    }

    public static int stringSize(int x) {
        int sign = 1;
        if (x >= 0) {
            sign = 0;
            x = -x;
        }
        if (x >= -10) {
            return sign + 1;
        }
        if (x >= -100) {
            return sign + 2;
        }
        if (x >= -1000) {
            return sign + 3;
        }
        if (x >= -10000) {
            return sign + 4;
        }
        if (x >= -100000) {
            return sign + 5;
        }
        if (x >= -1000000) {
            return sign + 6;
        }
        if (x >= -10000000) {
            return sign + 7;
        }
        if (x >= -100000000) {
            return sign + 8;
        }
        if (x >= -1000000000) {
            return sign + 9;
        }
        return sign + 10;
    }

to the benchmark, getting

Benchmark                                                                  Mode  Cnt     Score     Error   Units
StringBuilderVsFormat.stringBuilderWithPrecomputedSize                     avgt   20    19.110 ±   0.308   ns/op
StringBuilderVsFormat.stringBuilderWithPrecomputedSize:gc.alloc.rate       avgt   20  7585.588 ± 119.209  MB/sec
StringBuilderVsFormat.stringBuilderWithPrecomputedSize:gc.alloc.rate.norm  avgt   20   152.000 ±   0.001    B/op
StringBuilderVsFormat.stringBuilderWithPrecomputedSize:gc.count            avgt   20   103.000            counts
StringBuilderVsFormat.stringBuilderWithPrecomputedSize:gc.time             avgt   20    37.000                ms
StringBuilderVsFormat.stringConcat                                         avgt   20    11.768 ±   0.123   ns/op
StringBuilderVsFormat.stringConcat:gc.alloc.rate                           avgt   20  7130.187 ±  71.639  MB/sec
StringBuilderVsFormat.stringConcat:gc.alloc.rate.norm                      avgt   20    88.000 ±   0.001    B/op
StringBuilderVsFormat.stringConcat:gc.count                                avgt   20    88.000            counts
StringBuilderVsFormat.stringConcat:gc.time                                 avgt   20    31.000                ms
StringBuilderVsFormat.stringFormat                                         avgt   20   143.261 ±   6.198   ns/op
StringBuilderVsFormat.stringFormat:gc.alloc.rate                           avgt   20  4908.943 ± 213.770  MB/sec
StringBuilderVsFormat.stringFormat:gc.alloc.rate.norm                      avgt   20   736.002 ±   0.001    B/op
StringBuilderVsFormat.stringFormat:gc.count                                avgt   20    66.000            counts
StringBuilderVsFormat.stringFormat:gc.time                                 avgt   20    27.000                ms

which shows that the increased allocation of the string builder cost something

stringSize is quite big and could be replaced by something like https://github.com/openjdk/jdk/blob/663a08331a83c852622b8b11900f12b0dc3dbe82/src/java.base/share/classes/java/lang/StringConcatHelper.java#L250

but it won't matter that much tbh

@franz1981
Copy link
Contributor Author

@marko-bekhta I've updated the HHH issue with the flamegraph screenshot 🙏

Copy link
Member

@mbellade mbellade left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @franz1981!

@mbellade mbellade merged commit b0fa631 into hibernate:main Jan 12, 2026
21 of 22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants