[译] D8 优化: Assertions
原文出自 jakewharton 关于 D8 和 R8 系列文章第 16 篇。
原文链接 : D8 Optimization: Assertions
原文作者 : jakewharton
assert 关键字是用于测试不合法的 Java 语法。也就是说:你所期望的总是真实的。
它的语法有两种格式:
只有在 JVM 上设置了 -ea(enable assertions)标志时,才会在运行时计算第一个表达式。第二个表达式(如果存在)用作断言错误构造函数的参数,该构造函数在第一个表达式返回 false 时抛出。
作为一名 Android 开发者可能对 assert 不是很熟悉,这是因为每个 Androd 进程都是来自 zygote 进程,而该进程已经禁用 assert。所以即使你在代码中使用 assert,它也是毫无效果的。
那为什么要费心谈论它呢?事实证明,它们将第一次在 Android 上变得有用!
1. 今天的行为
assert 语句保护必须始终为真的内容,以便程序正确执行。让我们写一个。
这个类创建唯一的 id,并通过只允许来自主线程的调用来保证它们的唯一性。如果这个类是从多个线程并发调用的,您可能会看到重复的 id 值。当然,它有点做作,还有像 @MainThread 这样的东西是由 Lint 检查的,但是我们关注的是 assert,所以继续吧。
在 Null Data Flow Analysis 这篇文章中,我们介绍了 R8 通过 SSA 来优化代码分支,从 Java 字节码解析 next() 方法时的 SSA 大致如下所示:
D8 知道 Android 不支持 Java 断言。它将删除检查并用 false 替换它,从而允许消除死代码。这将传播到只有在返回 true 时才能获取的节点。
结果,布尔表达式和可选消息表达式从字节码中完全消除。只保留字段 read、field、increment 和 return。
我们可以通过编译 Java 源代码来确认这一点:
消除运行时检查并且返回 false 很容易做到,但是 SSA 的方式意味着我们消除 assert 语句的两个表达式的字节码,包括它们所依赖的任何中间值。
2. 明天的行为
AGP 4.1 中的 D8 版本稍微改变了 Java assert 的处理。通过在编译时的检查来替换原来的在运行时检查。
实际上,这意味着任何调试变量都会开启编译时的断言 check 功能。
这去除了启用的检查,但保留不变的检查。
通过给 D8 指定 --force-enable-assertions (该指令在被 AGP 在 Debug 时自动添加)来编译 IdGenerator 类。
我们的调试构建仍然在运行时测试不变量,但是发布构建完全消除了检查。这种行为现在类似于 JVM,在 JVM 中,单元测试启用 -ea 标志,而生产则不启用。
(如果您想知道为什么抛出异常的代码被移到方法的底部,请通过这篇 Optimizing Bytecode by Manipulating Source Code 文章了解。)
这些特性在最新的 AGP 4.1 alphas 版本上实现,不变量的本质是,除非你已经做了非常错误的事情,否则它们永远不会失败。通过在调试版本中检查它们,我们有信心在 Android 运行时获得库和应用程序代码的正确性。
Kotlin 的 assert() 函数与 Java 的 assert 关键字相比,在行为上存在细微的差异。更多的信息可以参照 Jesse Wilson 的 Kotlin’s Assert Is Not Like Java’s Assert 这篇文章。D8 目前无法识别 Kotlin 的 assert() 来做优化,但由于这个原因,关于 D8 的这个 original D8 feature request 仍然没有关闭。
与最近文章中介绍的一些 R8 优化不同,这种优化只局限于单个方法的主体,这就是为什么 D8 也可以执行它的原因。查看 D8 优化的文章 D8 Optimizations,了解更多适用于 D8 和 R8 的优化。
敬请期待更多的 D8 和 R8 优化帖子即将发布!











评论