简体   繁体   中英

Is there a performance impact when using Guava.Preconditions with concatenated strings?

In our code, we often check arguments with Preconditions :

Preconditions.checkArgument(expression, "1" + var + "3");

But sometimes, this code is called very often. Could this have a notable negative impact on performance? Should we switch to

Preconditions.checkArgument(expression, "%s%s%s", 1, var, 3);

?

(i expect the condition true most of the time. False means bug.)

If you expect the check to not throw any exception most of the time, there is no reason to use the string concatenation. You'll lose more time concatenating (using .concat or a StringBuilder ) before calling the method than doing it after you're sure you're throwing an exception.

Reversely, if you're throwing an exception, you're already in the slow branch.

It's also noteworthy to mention that Guava uses a custom and faster formatter which accepts only %s . So the loss of time is actually more similar to the standard logger {} handle (in slf4j or log4j 2). But as written above, this is in the case you're already in the slow branch.

In any case, I would strongly recommend against any of your suggestion, but I'd use this one instead:

Preconditions.checkArgument(expression, "1%s3", var);

You should only put variables in %s , not constants to gain marginal speed.

In the case of String literal concatenation, the compiler should do this in compilation time, so no runtime performance hit will occur. At least the standard JDK does this, it is not per specification (so some compilers may not optimize this).

In the case of variables, constant folding won't work, so there will be work in runtime. However, newer Java compilers will replace string concatenation to StringBuilder , which should be more efficient, as it is not immutable, unlike String .

This should be faster than using a formatter, if it is called. However, if you don't except it to be called very often, then this can be slower, as the concatenation always happen, even if the argument is true, and the method does nothing.

Anyway, to wrap it up: I do not think that it is worth to rewrite the existing calls. However, in new code, you can use the formatter without doubts.

I wrote a simple test. Using formatter is much faster as suggested here. The difference in performance grows with the number of calls (performance with formatter does not change O(1)). I guess the garbage collector time grows with number of calls in case of using simple strings.

Here is one sample result:
started with 10000000 calls and  100 runs
formatter: 0.94 (mean per run)
string: 181.11 (mean per run)
Formatter is 192.67021 times faster. (this difference grows with number of calls)

Here is the code (Java 8, Guava 18):

import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

import com.google.common.base.Preconditions;
import com.google.common.base.Stopwatch;

public class App {

    public static void main(String[] args) {
        int count = 10000000;
        int runs = 100;
        System.out.println("started with " + count + " calls and  " + runs + "runs");
        Stopwatch stopwatch = Stopwatch.createStarted();
        run(count, runs, i->fast(i));
        stopwatch.stop();
        float fastTime = (float)stopwatch.elapsed(TimeUnit.MILLISECONDS)/ runs;
        System.out.println("fast: " + fastTime + " (mean per run)");
        //
        stopwatch.reset();
        System.out.println("reseted: "+stopwatch.elapsed(TimeUnit.MILLISECONDS));
        stopwatch.start();
        run(count, runs,  i->slow(i));
        stopwatch.stop();
        float slowTime = (float)stopwatch.elapsed(TimeUnit.MILLISECONDS)/ runs;
        System.out.println("slow: " + slowTime + " (mean per run)");
        float times = slowTime/fastTime;
        System.out.println("Formatter is " + times + " times faster." );
    }

    private static void run(int count, int runs, Consumer<Integer> function) {
        for(int c=0;c<count;c++){
            for(int r=0;r<runs;r++){
                function.accept(r);
            }
        }
    }

    private static void slow(int i) {
        Preconditions.checkArgument(true, "var was " + i);
    }

    private static void fast(int i) {
        Preconditions.checkArgument(true, "var was %s", i);
    }

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM