使用带连接字符串的Guava.Preconditions时是否会对性能产生影响?

时间:2015-10-14 07:14:22

标签: java performance guava preconditions

在我们的代码中,我们经常使用Preconditions检查参数:

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

但有时,这段代码经常被调用。这会对性能产生显着的负面影响吗?我们应该切换到

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

(我希望大多数时候情况都是正确的。虚假意味着错误。)

3 个答案:

答案 0 :(得分:5)

如果您希望检查在大多数情况下不抛出任何异常,则没有理由使用字符串连接。在您调用该方法之前,您将失去更多时间连接(使用.concatStringBuilder),而不是在您确定要引发异常之后进行连接。

相反,如果您要抛出异常,那么您已经在分支中。

值得一提的是,Guava使用的是自定义且速度更快的格式化程序,仅接受%s。因此,时间损失实际上更类似于标准记录器{}句柄(在slf4j或log4j 2中)。但如上所述,这就是你已经处于慢速分支的情况。

无论如何,我强烈建议您反对任何建议,但我会改用此建议:

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

你应该只在%s中放置变量,而不是常数来获得边际速度。

答案 1 :(得分:1)

String字面串联的情况下,编译器应该在编译时执行此操作,因此不会发生运行时性能损失。至少标准JDK这样做,它不是每个规范(所以一些编译器可能不优化这个)。

在变量的情况下,常量折叠不起作用,因此将在运行时工作。但是,较新的Java编译器会将字符串连接替换为StringBuilder,这应该更有效,因为它不是不可变的,与String不同。

如果调用格式化程序,这应该比使用格式化程序更快。但是,如果你不经常调用它,那么这可能会变慢,因为连接总是发生,即使参数为真,并且该方法什么都不做。

无论如何,要把它包起来:我不认为重写现有的电话是值得的。但是,在新代码中,您可以毫无疑问地使用格式化程序。

答案 2 :(得分:0)

我写了一个简单的测试。使用格式化程序要快得多,如此处所示。性能差异随着调用次数的增加而增加(格式化程序的性能不会改变O(1))。我想在使用简单字符串的情况下,垃圾收集器时间会随着调用次数而增加。

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)

这是代码(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);
    }

}