我最近在Java中发现了“assert”语句,并且在我调试它的时候一直在乱丢我的软件。我最初的本能是避免将控制流语句用于处理断言语句,但后来我意识到这些控制语句可能会在生产构建期间被删除,因为它们的块将是空的。我的印象是它们将被JIT编译器淘汰。
不幸的是,我正在对JIT编译器的工作方式进行模糊的回忆,并找不到合适的文档。我能找到的最好的是来自IBM的优化过程的brief outline。
在我养成实施基于断言的测试的习惯之前,我想知道是否有最佳实践来最小化它们对性能的影响。我讨厌实施一系列测试只是为了发现它们的集体效应是大幅降低性能,即使它们的影响可以忽略不计。
你们是否可以告诉我,以下几行是否会影响生产版本的性能(禁用“断言”,默认情况下)?
for (T obj : Collection){
assert obj.someProperty();
}
如果我有更复杂的东西,包括仅用于断言的短期对象,该怎么办?
TreeMap<Integer,T> map = new TreeMap<>();
int i = 0;
for (T obj : Collection){
map.put(i,obj);
assert obj.someProperty();
i++;
}
// assert something about map, then never use it again
或者一种方法,其效果只是调用“断言”?
提前致谢!
Oracle关于“assert”声明的文档的相关摘录:
这些讨论如何从类文件中删除断言,这不是我所关注的。
从类文件编程器中删除所有断言跟踪 开发资源受限设备的应用程序可能希望如此 完全剥离类文件中的断言。虽然这样做了 不可能在现场启用断言,它也减少了类 文件大小,可能会提高类加载性能。在 缺乏高质量的JIT,可能导致减少 足迹和改进的运行时性能。
断言设施不提供剥离的直接支持 断言类文件。然而,断言声明可能是 与所描述的“条件编译”成语一起使用 在Java语言规范中,使编译器能够消除 这些断言的所有痕迹都来自它生成的类文件:
static final boolean asserts = ...; // false以消除断言
if(asserts)assert;
和FAQ部分:
为什么不提供编译器标志来完全消除断言 来自目标文件?这是一个坚定的要求,它是可能的 在现场启用断言,以提高可维护性。它会 已经可以允许开发人员消除断言 在编译时从目标文件。断言可以包含方面 效果,虽然他们不应该,因此这样的旗帜可能会改变 程序在很大程度上的行为。它被认为是好的 每个有效Java只有一个语义相关的东西 程序。此外,我们希望鼓励用户将断言保留在对象中 文件,以便在现场启用它们。最后,规范要求 当一个类运行之前,该断言的行为就像启用一样 初始化。如果,那就不可能提供这些语义 断言从类文件中删除。但请注意, Java中描述的标准“条件编译习语” 语言规范可用于实现此效果 真正想要它的开发者。
答案 0 :(得分:3)
您可以在assert
内进行方法调用,因此我更喜欢的语法如下:
private static boolean testSomePropertyTrue(Collection<T> collection) {
boolean test = true;
for (T obj : collection){
test = test && obj.someProperty();
}
return test;
}
...
assert testSomePropertyTrue(collection);
这使得JIT优化的代码没有任何歧义,并且当启用断言时将执行相同的不变检查。
如上所述,无论断言是否已启用,您的第二个示例将始终创建TreeMap
。将整个事物包装在函数中并将其作为单个断言进行评估将在运行时完全消除该代码路径。
就像这个问题的另一个答案所示,断言将留下字节码,无论它们是否被启用,但使用上面显示的样式将确定性地阻止这些代码路径执行,除非启用断言。
答案 1 :(得分:0)
你们是否可以告诉我,以下几行是否会影响生产版本的性能(禁用“断言”,默认情况下)?
无论断言是打开还是关闭,您的断言循环都将运行(即它们是字节码的一部分 - 无论是运行代码时是否启用断言)。但是,我不担心第一个片段的性能; JIT可能会意识到循环没有做任何事情并在早期就处理它。
如果我有更复杂的东西,包括仅用于断言的短期对象,该怎么办?
像你的第二个片段更难以确定 - 最好的办法可能是自己计时,尽管在大多数情况下我会想象它也不会有明显的效果。
也许你可以有一个“debug”标志并按原样使用它:
static boolean debug = true;
if (debug) {
// loop here
}