这是我参与更文应战的第 19 天,活动概略查看: 更文应战

本文原文出自 jakewharton 关于 D8 和 R8 系列文章第六篇。

  • 原文链接 : R8 Optimization:Null Data Flow Analysis (PAndroidart 2)
  • 原文作者 : jakewhart安全出产法on
  • 译者 : Antway

在上篇文章中,咱们演示了 R8 针对内联办法进行空判别代码的优化,这是由于 R8(和 D8)在 IR 时期经过空判别结束的。当传递给办法的参数是空或非空恒建立时,则 R8 会在编译期直接对安全教育 null 判别进android开发行效果核算。

前面两jvm内存模型篇文章中的比如底子都是用 Kotlin 写的,一同为了进步字节码的可读性,我有时只粘贴了字节码的一部安全期是哪几天分。在上android下载装置篇文章中咱们以在 main 函数中调用 coale安全出产月sce 函数为例开端。

fun <索引符号T : Any> coalesce(a: T?, b: T?): T? = a ?: b
fun main(args: Array<String>) {
println(coalesce("one", "two"))
println(coalesce(null, "two"))
}

上篇文章中,咱们运用不同版本的编译器来编译模仿优化处理后的示例,他们的字节码都是以 sget-object v1Ljava/lang/Sys字节码文件是与渠道无关的什么文件tem.out:Ljandroid开发ava/io/Prjvm是什么int字节码和机器码的差异Stre安全出产月am; 开始,这个字节码的意思是查找静态 System.out 字段的字节码,该字段毕竟调用 println 办法。

假设咱们对上面的 demo 进行编译、打包,然后查看打包后的字节码,能够发现首行的字节码有一点不同。

$ kotlinc *.kt
$ java -jar d8.jar 
--lib $ANDROID_HOME/platformandroid下载s/android-28字节码/android.jar 
--release 
--output . 
*.class kotlin-stdlib-1.3.11.jar
$ $ANDROID_HOME/build-tools/28.0.3/dexdump -d classes.dex
[00023c] Nandroid平板电脑价格ullsKt.字节码文件main:安全期计算器([Ljava/lang/String;)V
0000: const-string v0, "args"
0002:索引符号表明的意义 invoke-static {v2, v0}, Lkotlin/jvm/internal/Intrinsics;.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V
0005: sget-objec安全教育渠道登录t v1, Ljava/lang/S字节码ystem;.out:Ljava/io/PrintStream;索引是什么意思

字节码不是先替代咱们写的函数到函数体,而是先调用 Intrinstrics.checkParameterIsNotNull 函数,该函数是在编译字节码文件是与渠道无关的二进制码时的后面运转时验证。

Kotlin 的类型体系为空引证建模。经过将 Array<String> 作为 main 函数的参数,即它对错空的。可是它是 public API索引符号 ,所以任何人都能够调用。为了进行空约束,Kotlin 编译器在每个 public API 函数中刺进非空参数的防护查看。

下面咱们运用 D8 来编译上面的源代码,看看有哪些改动。

$ cat rules.txt
-keepclasseswithmembers class * {
public static void main(java.ljvm垃圾收回机制ang.String[]);
}
-dontobfuscate
$ java -安全教育jar r8.jar 
--lib $ANDROID_HOME/platforms/andro索引id-28/android.jar 
--release 
--output . 
--pg-conf rule安全期计算器s.txt 
*.class kotlin-standroid体系dlib-1.3.11.jar
$ $ANDROID_HOME/build-tools/28jvm调优面试题.0.3/dexdump -d classes.dex
[000314] NullsKt.main:([Ljava/lang/Striandroid下载装置ng;)V
0000: if-eqz v1, 0011
0002: sget安全出产月-object v1, Ljava/lang/System;.out:Ljava/io/PrintStream;
…
0010: r安全教育eturn-void
0011: const-s安全手抄报tring v1, "args"
0013:字节码是什么 invoke-static {v1}, Lkotlin/jvm/internal/Intrinsics;.t安全期是哪几天hrowParameterIsNullException:(Ljava/lang/Strin安全期是哪几天g;)V

在上面 D8 编译的字节码中,咱们能够看到字符串索引有哪几种类型常量的加载和 Intrinsics 办法的调用现已被规字节码文件是与渠道无关的二进制码范的空查看 if-eqz 替代。假设空查看建立,则会跳转到字节码的毕竟抛出异常。字节码文件是与渠道无关的二进制码正常状况下 args 对错空的,此时程序会从 0000 正常实施到 0010

咱们或许会想“由于是内联函数,所字节码是虚拟机的机器码以上面的字节码看起来jvm是什么R8 处理的很像”。在上一篇文章中,coalesce 函数被内联所以JVM才会有 Instrinsics.checkParameterIsNo字节码是什么意思tNull 字节码结束,咱们能够快速看下 Instrinsics字节码文件能够直接在操作体系上运转.checkParameterIsNotNull 的结束。

public static void checkParameterIsNotNull(Object vaJVMlue, String paramNandroidstudio装置教程ame) {
if (value == null) {
throwParameterIsNullException(par安全期计算器amName);
}
}

实践 R8 在处理的时分并不是咱们幻想的内联办法,假设是内联的话,上面的 if字节码是什么定会出现在函数的最上面。除此之外,尽管上面的办法很小,可是它现已超出了 R8 界说内联的阈值规模。这里有几个办法能够产安全出产法生这种效果。

第一种技巧是增加 R8 内联的阈值规模。由于 checkParameterNotNull 仅用于调用参数的非空检android什么意思查,因而办法的内联阈值将增大,办法体也为空,因而它成为合格的,并且是内联的。

第二个技巧是 R8 辨认出这两个对参数实施空检jvm垃圾收回机制查的字节码,然后抛出android体系异常。当辨认出这种办法时,R8 假定它是办法实施的不常见途径。为了对公共途径进行优化,将回转空查看,以便非空大小写安全期计算器紧跟查看。异常引发代码被推送到办法的底部。

字节码和机器码的差异是,checkParameterNotNullif 查看与字节码 R8 的序列不匹配,需求辨认参数查看办法。if主体包含静态办法调用,而不是引发异常。毕竟一个技巧是R8有一个内在的,它辨认出对intrinsics.throwParameterIsNullException` 的调用等价于抛出一个异常。这样能够使主体正确匹配办法。

这三个技巧结合起来解说了为什么 R8 发生咱们上面看到的字节码。

记住,关于非 Kotlin 调用程序或许关于每个可见的办法,每个非空参数都有此代码。在杂乱的程序中,都会出现很多这样的状况!

运用 R8 将静态办法调用替换为规范的空查看,并将异常状况移动到办法的结束,代码将保存查看的安全性,一同最小化功用影响。

1. Combining Null Information

在上篇文章中介绍了 R8 经过使用空信息删去无用的空查看。在本文的上半部分中,咱们介绍了 R8 经过进步内联阈值来疏忽空查看,一同替代 Kotlin 供应的 Intrinsic 办法,运用规范的 if-ejvm功能调优qz 指令进行空查看。形似这两个特功能够在一些字节码文件能够直接在操作体系上运转方面进行兼并归类。

鄙人面的示例中,咱们新增了 String.double 函数,

fun String.do字节码uble(): String = th索引有哪几种类型is + this
fun coalesce(a: String?, b: String?): String? = a ?: b
fun main(argjvm面试题s: Array<String>) {
println(coalesce(null, "two")?.double())
}

在进行 D8 编译前,首要来罗列下存在的非空查看:

  1. ar安全gs 参数的查看,由于 main 办法是 publandroid手机ic
  2. coalesce 函数回来值的查看,由于会调用 double 函数;
  3. coalesce 函数第一个参数的非安全出产月空查看,由于决定是回来 firstsecond
  4. double 函数的承受者要查看,由于它是一个 public 办法。

下面咱们经过 D8 编译来进行验证索引图

[000310] NullsKt.ma安全in:([Ljava/ljvm内存结构ang/String;)V
0000: if-eqz v1, 0019
0002: const-string v1, "two"
0004: new-instance vjvm内存模型0, Ljava/lang/Stri字节码目标ngBuilder;
0006: invoke-direct {v0}安全教育渠道登录入口, Ljava/lang/StringBuilder;.<init>:()V
0009: invoke-virandroid下载tu字节码是虚拟机的机器码al {v0, v1}, Ljava/lang/StringBuilder;.appendandroid体系:(Ljava/lang/String;)Ljava/lang索引图/StringBuilder;
000c: invoke-virtual {v0, v1索引图}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
000f: invoke-virtual {vandroid平板电脑价格0}, Ljava/lang/Stringandroidstudio装置教程Builder;.toString:()Ljava/lang/Str安全ing;
0012: move-result-o字节码和机器码的差异bject v字节码文件是与渠道无关的二进制码1
0013: sget-android下载装置object v0, Ljava/lang/System;.out:Ljava/io/PrintStream;
0015: invo安全教育渠道ke-virtual {v0, v1}, Ljava/io/Pri索引有哪几种类型ntStrea索引失效的几种状况m;.println:(Ljava/lang/Object;)V
0018: rjvm参数eturn-void
0019: const-string v1, "args"
001b: invoke-static {v1}, Lkotlin/jvm/internal/Intrinsics;.throwParameterIsNullException:(Ljava/langjvm内存模型/String;)V

除了保jvm是什么护参数的查看之外,悉数的空查看都被根除了。

由于 R8 能够证明 co安全教育渠道登录alesce 回来安全教育一个非空引证,所以能够消除悉数下流的空查看。这意味着不需求安全调用,而是将其替换为一般安全教育渠道办法调用。安全出产月双功用接索引图收器上的空查看也被消除。

2. No Inlining Required

迄今为止的比如包含帮忙削减产值的内嵌,实践上,内联不会像在这些小比如中那样发生。这并不索引是什么意思能阻挠悉数空查看的根除。

尽管我发现 Kotlin 的比如在这里很有说服力,由于强制的、零的查看,Java 的优化是幽默的,由于它的行为是相反的。Java 不在公共办法参数上设置防护性空查看,因而数据流分析能够运用其他办法来进行空值信号,即使没有内联。

final class Nulls {
public static void main(String[] args) {
System.out.println(firstjvm内存结构(args));
if (args == null) {
System.out.println("null!");
}
}
public static String first(String[] values) {
if (values == null) throw new NullPointerException("values == nuljvm内存模型l");
reandroid什么意思turn values[0];
}
}

字节码是什么 Java 中,每个引证都或许是空的。因而索引,在像 first 这样的办法索引超出矩阵维度中看到自动进行空查看android平板电脑价格并不罕见(即使在注释安全手抄报 @nonnull 时也是如此)。library 办法或许很大,或许在应用程序的悉数索引页是哪一页当地调Android用,因而它们通常不是内联的。为了模仿这一点,咱们能够显式地告知 R8 作为 rules.txt 中的一种办法保持。

 -keepclasseswithmembers class * {
public static void mandroid什么意思ain(java.lang.String[]);
}
-dontobfuscate
+-keep class Nulls {
+   public static java.lang.String first(java.lang.String[]);
+}

咱们看到,即使没有经过内联优化,实践的效果仍是能够承受的。

[000144] Nulls.first:([Lja字节码是虚拟机的机器码va/lang/String;)Ljjvm面试题ava/lang/String;
0000: if-eqz v1, 0006
0002: const/4 v0, #int 0
0003: a索引超出矩阵维度get-object v1, v1, v0
0005:android开发 reandroid的drawable类turn-object v1
0006: new-instance v1, Ljava/lang/NullPointerException;
0008: const-string v0, "values == nulljvm垃圾收回机制"
000a: invoke-direct {v1, v0}, Ljava/lang/Nullandroidstudio装置教程PointerException;.<init>:(Ljava/lang/String;)V
000d: throw v1
[000170] Nul字节码文件是与渠道无关的什么文件ls.main:([Ljava/lang/String;)V
0000: sget-object v0, Ljava/lang/System;.out:Ljava/索引的效果及优缺点io/PrintStream;
0002: invoke-static {v1}, LNulls;.first:([Ljava/lang/String;)Ljajvm调优面试题va/lang/String;
0005: move-result-object v1
0006: invoke-virtual {v0, v1}, Ljava/io/PrintStream;.println:(Ljava/lang安全出产月/String;)V
0009: return-void

首要,R8 再次颠倒了空查看,因而引发异常的异常状况位于索引 0006索引符号表明的意义 处办法的底部。此办法的正常实施将从索引 00000005 并回来。android体系

总归,args 及其打印的显式空检安全教育渠道登录入口查已消失。这是由于 R8 检测到 args 是进口,在那里在调用之后不或许为空。因而,在调用 first 之后发生的任何空jvm功能调优查看都不需求发生。

3. 总结

悉数这些示例都很小,并且有些人为的,可是它们演示了 R8 在可空性和空查看方面所做的数据流分析的一部分。在整个应用程序的规模内,无论是 JavaKotlin 仍是混合的,不必要的空校验和未运用的分支能够在不牺牲它们供应的安全性的状况下被消除。

下周的 R8 文章将介绍我最喜欢的工具特性。这也是一个我认为发生最好的演示,并与每个安卓开发者共字节码识。敬请期待!