Skip to content

Conversation

@loicmathieu
Copy link

I recently performance load test on an application using the java interceptor to send traces to Jaeger.
Inside a performance profile, I saw a high cost (3% total CPU cost, but more than 60% of span creation+start+finish time) for the getOperationMethod due to the usage of a StringFormatter.

I did some small experiment with JHM, and using a StringBuilder with an initial capacity seems a better option (10x quicker). As creating span occurs in the hot path of an application, I think it's a good thing to remove this hotspot.

I try several approach, this seems to be the better. We can go with simple string concatenation if the code seems too complex, as since Java 9 (and the string concatenation improvement) it is as performant as using a StringBuilder.

Here is my JMH benchmark

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Thread)
public class StringFormatVsJoinerVsBuilderVsConcat {

    private Method method;

    @Setup
    public void setup() throws NoSuchMethodException {
        method = TestClass.class.getMethod("testMethod");
    }

    @Benchmark
    public String stringFormat() {
        return String.format("%s.%s", method.getDeclaringClass().getName(), method.getName());
    }

    @Benchmark
    public String stringJoiner() {
        StringJoiner stringJoiner = new StringJoiner(".");
        stringJoiner.add(method.getDeclaringClass().getName());
        stringJoiner.add(method.getName());
        return stringJoiner.toString();
    }

    @Benchmark
    public String stringBuilder() {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append(method.getDeclaringClass().getName());
        stringBuilder.append('.');
        stringBuilder.append(method.getName());
        return stringBuilder.toString();
    }

    @Benchmark
    public String stringBuilderWithCapacity() {
        int capacity = method.getDeclaringClass().getName().length() + method.getName().length() + 1;
        StringBuilder stringBuilder = new StringBuilder(capacity);
        stringBuilder.append(method.getDeclaringClass().getName());
        stringBuilder.append('.');
        stringBuilder.append(method.getName());
        return stringBuilder.toString();
    }

    @Benchmark
    public String stringConcat() {
        return method.getDeclaringClass().getName() + '.' + method.getName();
    }

    public static class TestClass {
        public void testMethod() {
            //do nothing here
        }
    }
}

And the result on my laptop (Linux - Java 11 - 6 cores)

Benchmark                                                        Mode  Cnt    Score    Error  Units
StringFormatVsJoinerVsBuilderVsConcat.stringBuilder              avgt    5   59,093 ±  6,031  ns/op
StringFormatVsJoinerVsBuilderVsConcat.stringBuilderWithCapacity  avgt    5   40,480 ±  3,536  ns/op
StringFormatVsJoinerVsBuilderVsConcat.stringConcat               avgt    5   60,190 ±  5,230  ns/op
StringFormatVsJoinerVsBuilderVsConcat.stringFormat               avgt    5  543,775 ± 35,095  ns/op
StringFormatVsJoinerVsBuilderVsConcat.stringJoiner               avgt    5   62,161 ±  2,027  ns/op

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.

1 participant