在我们的代码中,我们经常检查参数Preconditions
:
Preconditions.checkArgument(expression, "1" + var + "3");
但有时,此代码会被非常频繁地调用。这会对性能产生显着的负面影响吗?我们应该切换到
Preconditions.checkArgument(expression, "%s%s%s", 1, var, 3);
?
(我希望大多数情况下条件为真。假意味着错误。)
在我们的代码中,我们经常检查参数Preconditions
:
Preconditions.checkArgument(expression, "1" + var + "3");
但有时,此代码会被非常频繁地调用。这会对性能产生显着的负面影响吗?我们应该切换到
Preconditions.checkArgument(expression, "%s%s%s", 1, var, 3);
?
(我希望大多数情况下条件为真。假意味着错误。)
如果您希望检查在大多数情况下不会抛出任何异常,则没有理由使用字符串连接。在调用方法之前连接(使用.concat
或 a StringBuilder
)会比在确定抛出异常后执行它花费更多的时间。
相反,如果你抛出一个异常,你已经在慢分支中。
值得一提的是,Guava 使用了一个自定义且更快的格式化程序,它只接受%s
. 所以时间损失实际上更类似于标准的记录器{}
句柄(在 slf4j 或 log4j 2 中)。但如上所述,这是在您已经处于慢速分支的情况下。
无论如何,我强烈建议您反对您的任何建议,但我会改用这个:
Preconditions.checkArgument(expression, "1%s3", var);
您应该只将变量放入 中%s
,而不是常量以获得边际速度。
在String
文字串联的情况下,编译器应该在编译时执行此操作,因此不会发生运行时性能损失。至少标准 JDK 做到了这一点,它不符合规范(因此某些编译器可能不会对此进行优化)。
在变量的情况下,常量折叠不起作用,所以在运行时会有工作。但是,较新的 Java 编译器会将字符串连接替换为StringBuilder
,这应该更有效,因为它不是不可变的,不像String
.
如果调用它,这应该比使用格式化程序更快。但是,如果您不经常调用它,那么这可能会更慢,因为连接总是发生,即使参数为真,并且该方法什么也不做。
无论如何,总结一下:我认为重写现有的调用不值得。但是,在新代码中,您可以毫无疑问地使用格式化程序。
我写了一个简单的测试。如此处所建议的,使用格式化程序要快得多。性能差异随着调用次数的增加而增加(格式化程序的性能不会改变 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);
}
}