[译] 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
优化帖子即将发布!
评论