4

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.)

Olivier Grégoire
  • 33,839
  • 23
  • 96
  • 137
dermoritz
  • 12,519
  • 25
  • 97
  • 185
  • The first is really just a simple string literal concatenation? – meskobalazs Oct 14 '15 at 07:17
  • yeah - and thats the core of the question - has this notable performance impact – dermoritz Oct 14 '15 at 07:18
  • The string concatenation probably happens compile-time so it shouldn't affect runtime performance. That is, _if_ you're concatenating literals (not variables). – Mick Mnemonic Oct 14 '15 at 07:22
  • variable strings are concatenated via [StringBuilder at runtime](http://www.benf.org/other/cfr/stringbuilder-vs-concatenation.html), all constant parts are put together at compile time. `.format()` is guaranteed to have the worst performance of all (has to figure out what those %s mean etc). (Except in loops) use `+` in source and let the compiler figure it out. – zapl Oct 14 '15 at 07:29
  • 1) Given that the Google Java Libraries team has to maintain any method they add, adding an unnecessary method would be a net loss for them. 2) The variadic method isn't necessary to provide a message with placeholders, you could simply make a call to `String.format` instead, and avoid adding the overload. That the method exists must surely point to there being *some* net-positive benefit to Google's code; I don't have data to say whether that is a performance benefit or not. – Andy Turner Oct 14 '15 at 07:31
  • So you expect `checkArgument` to throw an exception most of the time? If the argument `expression` is `true`, it *will not* throw any exception. If it is `false`, il *will* throw an exception. – Olivier Grégoire Oct 14 '15 at 08:33
  • no sorry - true most of the time. exception only in case of bug (hopefully never in production) – dermoritz Oct 14 '15 at 09:03

3 Answers3

5

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.

Olivier Grégoire
  • 33,839
  • 23
  • 96
  • 137
1

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.

meskobalazs
  • 15,741
  • 2
  • 40
  • 63
0

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);
    }

}
dermoritz
  • 12,519
  • 25
  • 97
  • 185