4

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

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

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

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

?

(我希望大多数情况下条件为真。假意味着错误。)

4

3 回答 3

5

如果您希望检查在大多数情况下不会抛出任何异常,则没有理由使用字符串连接。在调用方法之前连接(使用.concat或 a StringBuilder)会比在确定抛出异常后执行它花费更多的时间。

相反,如果你抛出一个异常,你已经在分支中。

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

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

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

您应该只将变量放入 中%s,而不是常量以获得边际速度。

于 2015-10-14T08:09:11.320 回答
1

String文字串联的情况下,编译器应该在编译时执行此操作,因此不会发生运行时性能损失。至少标准 JDK 做到了这一点,它不符合规范(因此某些编译器可能不会对此进行优化)。

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

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

无论如何,总结一下:我认为重写现有的调用不值得。但是,在新代码中,您可以毫无疑问地使用格式化程序。

于 2015-10-14T07:21:39.713 回答
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);
    }

}
于 2015-10-15T09:46:46.793 回答