@qidiandasheng
2021-04-21T07:36:35.000000Z
字数 6326
阅读 1927
Flutter
JIT(Just In Time):
即时编译,指的是在程序运行中,将热点代码编译成机器码,提高运行效率。常见例子有 V8 引擎和 JVM,JIT可以充分利用解释型语言的优点,动态执行源码,而不用考虑平台差异性。这里需要注意的是,对于 JVM 来说,源码指字节码,而不是 Java 源码。
JIT 模式有个显然的优点,你可以直接将代码分发给用户,而不用考虑用的机器架构。有这个,就可以为用户提供丰富和动态的内容。
但缺点也是比较明显的,大量的字符串源码,将花费JIT编译器大量的时间和内存来编译和执行,会让用户感觉应用启动缓慢。
AOT(Ahead Of Time):
运行前编译,指的是在程序运行之前,已经编译成对应平台的机器码,不需要在运行中解释编译,就可以直接运行。
AOT 的优势就是速度快,通过事先编译好的二进制代码,加载和执行的速度都会非常快,在密集计算或者图形渲染的场景下能够获得比较好的用户体验。
但 AOT 也有一些缺点,编译源代码的时候,需要注意用户的设备架构。对于不同的构架需要生成不同的二进制代码,也就会增加应用需要下载的程序包大小。而且二进制代码需要获得执行权限,所以无法在权限比较严格的系统(比如iOS)中动态更新。
这两种编译方式的主要区别在于是否在“运行时”进行编译,即在运行前是否会全部被翻译为机器码。
编译语言:可产生包含机器码的可执行文件的语言
解释语言:不可产生可执行文件的语言
程序的编译模式和具体的语言没有强制关系,比如Java,既可以JIT,也可以AOT。它需要经过编译,但编译的结果不是机器码,而是Java字节码(Java byte codes),这个时候是运行前编译是AOT。被编译的Java程序产生Java字节码,之后计算机模拟JVM对其进行解释,这个时候是JIT。
# 安装
$ brew tap dart-lang/dart
$ brew install dart
$ brew install dart --devel // 安装dev版
# 更新
$ brew update
$ brew upgrade dart
$ brew cleanup dart
# 查看安装路径等信息
$ brew info dart
最普通的JIT模式,在PC命令行调用dart vm
执行dart
源代码文件即是这种模式。
// hello.dart
main() => print('Hello, World!');
$ dart hello.dart
Hello, World!
自从 Dart 2
版本之后,VM 已经没有了直接从源代码执行 Dart 的功能,取而代之的是,VM 只能执行那些由内核抽象语法树(Kernel ASTs
)序列化成的内核二进制文件(Kernel binaries
)(又被称作 dill files
)。而将 Dart 源码翻译成内核抽象语法树的任务则交给了由 Dart 编写的通用前端(common front-end(CFE)
),这个工具被不同的 Dart 模块所使用(举个例子:虚拟机(VM),dart2js
,Dart Dev Compiler
)。
那么为了保持直接执行Dart源码的便捷性,所以有一个叫做kernel service
的isolate,负责将Dart源码处理成Kernel
,VM再将Kernel binary
拿去运行。
根据源码生成内核二进制文件:
$ dart compile kernel hello.dart -o hello.dill
//运行Kernel binaries
$ dart hello.dill
Hello, world!
反序列化获取内核抽象语法树
从github下载dart sdk源码
$ dart sdk/pkg/vm/bin/dump_kernel.dart hello.dill hello.kernel.txt
//hello.kernel.txt
main = hel::main;
library from "file:///Users/dasheng/Work/dart_sdk/Demo/hello.dart" as hel {
static method main() → dynamic
return core::print("Hello, World!");
}
VM 能够将 isolate 中的堆,或者更多具体的对象图序列化成二进制快照。当Dart VM
再次启动时,可以利用快照恢复到之前相同的状态。
快照是为了启动速度而优化的底层格式——他实质上是一个创建对象的列表,以及如何将对象关联起来的指令。快照背后的根本思想是:不需要解析Dart
源码然后逐渐创建出VM
的数据结构,而是直接将所有必要的数据从快照中解压缩出来,然后快速的生成一个isolate
。
Snapshots
包含以下三种:
为最初的JIT类型Kernel Snapshots
不包含机器码,将isolate
中的堆,或者更多具体的对象图序直接列化成二进制快照,反序列化时再快速的生成一个isolate
:
随着 AOT 编译器的加入,JIT Application Snapshots
也被引入了进来,即包含机器码的Snapshots
。开发AOT
的动机是为了让VM
能够在那些限制使用 JIT 的平台上(iOS)也能使用快照。含有代码(machine code
)的快照的运行方式几乎与普通的快照一样,只有一点不同:他们包含了一个代码区,和快照其他部分不同的是,他们不需要反序列化。代码区在映射到内存后会直接称为堆的一部分:
AOT Application Snapshots
主要就是给无法JIT的平台用的,所以Snapshots
就是经过全局静态分析(type flow analysis or TFA)的机器码:
内核快照能把dart程序打包成一个文件以减少启动时间。
$ dart --snapshot-kind=kernel --snapshot=hello.dart.snapshot hello.dart
$ dart hello.dart.snapshot arguments-for-use
Hello, world!
这份快照包含有内核抽象语法树(Kernel AST
)生成的内核二进制文件。但是缺少解析类和方法以及编译的代码。他是架构无关的,因此可以在平台之间迁移使用。
但是依旧需要Dart VM
转换抽象语法树为内部的表示,以及在每一次运行时编译对应的函数,这将花费一部分运行时的计算能力。
注:其实hello.dart.snapshot
就是上面我们提到的hello.dill
,我们使用反序列化看一下生成的AST,发现是一样的
$ dart /Users/dasheng/Work/dart_sdk/sdk/pkg/vm/bin/dump_kernel.dart hello.dart.snapshot hello.kernel2.txt
//hello.kernel2.txt
main = hel::main;
library from "file:///Users/dasheng/Work/dart_sdk/Demo/hello.dart" as hel {
static method main() → dynamic
return core::print("Hello, World!");
}
从1.21版本开始,Dart VM
支持应用级别的快照(app-jit snapshots
),这份快照包含了解析的类以及编译代码。
$ dart --snapshot=hello.dart.snapshot --snapshot-kind=app-jit hello.dart arguments-for-training
Hello, world!
$ dart hello.dart.snapshot arguments-for-use
Hello, world!
当运行这份快照时,Dart VM
将不会解析编译那些在生成快照时使用过的类或者函数。通过这个快照启动的 Dart VM
仍然可以进行 JIT
——就是当实际上的执行数据和生成快照时执行的数据不匹配的时候就会运行。
简单的说就是运行了一遍dart代码,把运行期间使用过的类或者函数做一份快照,没使用过的则在运行时进行JIT
。
AOT Application Snapshots
最初是为了无法进行JIT编译的平台而引入的,但也可以用来优化启动速度。无法进行JIT就意味着:
AOT Application Snapshots
必须包含在应用程序执行期间可以调用的每个功能的可执行代码AOT模式在2.4版本需要通过dart2aot
编译,通过dartaotruntime
运行。
$ dart2aot hello.dart hello.dart.aot
$ dartaotruntime hello.dart.aot
Hello, world!
在2.7的时候dart2aot
被dart2native
替代了:
$ dart2native hello.dart -k aot -o hello.dart.aot
$ dartaotruntime hello.dart.aot
Hello, world!
dart2native
还可以生成产物为平台相关的机器码,这时候生成的即为可执行程序,不需要依赖于dartaotruntime
运行。即在生成的机器码中,自带了精简版的Dart运行环境。
$ dart2native hello.dart -o hello.dart.machine.aot
$ ./hello.dart.machine.aot
Hello, World!
$ ls -al
-rw-r--r-- 1 dasheng staff 33 3 1 10:29 hello.dart
-rw-r--r-- 1 dasheng staff 424 3 1 14:57 hello.dart.snapshot
-rw-r--r-- 1 dasheng staff 1014768 3 1 16:06 hello.dart.aot
-rw-r--r-- 1 dasheng staff 4065280 3 1 14:59 hello.dart.app.jit.snapshot
-rwxr-xr-x 1 dasheng staff 5475328 3 1 16:11 hello.dart.machine.aot
Core Snapshot:对应上面Dart的编译模式Kernel Snapshot
,Dart的bytecode
模式,bytecode
模式是不区分架构的。bytecode模式可以归类为 AOT编译。
Core JIT:对应上面的Dart的编译模式JIT Application Snapshots
,Dart的一种二进制模式,将指令代码和 heap数据打包成文件,然后在vm和 isolate启动时载入,直接标记内存可执行,可以说这是一种 AOT模式。Core JIT
也被叫做AOTBlob
。
AOT Assembly: 即Dart的AOT模式。直接生成汇编源代码文件,由各平台自行汇编。包体积比较大,区分架构。
Android : Core Snapshot
iOS : Core Snapshot
(app-aot-blobs)
调试模式时使用虚拟机 (VM) 来运行 Dart 代码 (因此这时会显示 “Debug” 字样,以提醒开发者速度会稍微变慢),这样便可以启用有状态热重载 (Stateful Hot Reload)。
app-aot-blobs
模式使用snapshots
,snapshots
文件是需要Dart VM
去加载执行的。使用snapshots
使得动态执行代码变成可能。
上一节讲到Dart编译模式时需要使用CFE将Dart转换为Kernel binary
交给VM运行。其实我们可以将dart代码转换为Kernel binary和执行Kernel binary这两个过程也可以分离开来。
Flutter的Debug模式就是这么做的,在两个不同的机器执行,比如host机器(用户的开发机)执行编译,移动设备执行Kernel binary
,Flutter tools
负责从开发机上将Kernel binary
发送到移动设备上。
图解:
flutter tools
自身完成,而是交给另一个进程frontend_server
来执行,它包括CFE和一些flutter专有的kernel转换器。hot reload
:热重载机制正是依赖这一点,frontend_server
重用上一次编译中的CFE状态,只重新编译实际更改的部分。Android : Core JIT
iOS : AOT Assembly
(app-aot-assemble)
引擎的 C 和 C++ 代码使用 LLVM 编译。Dart 代码 (SDK 的和您的) 都是预先 (ahead-of-time, AOT) 编译成本地 ARM 库。这些库被包含在一个 iOS “runner” 项目中,然后整套内容被编译成一个 .ipa。当应用启动时,它会加载 Flutter 库。任何渲染、输入或事件处理等都会 delegate 给编译好的 Flutter 和应用代码。这个工作机制与很多游戏引擎颇为相似。
app-aot-assemble
是要快于app-aot-blobs
的,因为它不需要Dart VM
环境,只需要Dart Runtime
即可。Dart Runtime
即为预编译的运行时,该运行时是Dart VM
的特殊变体,其中不包括诸如JIT和动态代码加载工具之类的组件。
编译生成flutter_assets
:
$ flutter build bundle
下图为dart_tool
编译生成的中间产物,其中app.dill
就是我们的业务代码:
下图为最终生成的产物,其中kernel_blob.bin
即为app.dill
的拷贝:
编译生成App.framework
和Flutter.framework
,包含第一步:
$ flutter build ios-framework
编译生成.app目标文件,包含第一第二步:
flutter build ios
flutter build ios —debug
iOS编译产物在下图所示的目录中,分为Debug产物和Release产物:
产物分为以下两种:
我们能看到Debug下的产物多了isolate_snapshot_data
、kernel_blob.bin
、vm_snapshot_data
这几个文件,这几个文件主要用于
dart vm
启动的产物,业务无关代码,仅和 flutter engine版本有关Flutter 编译原理
Dart VM介绍
Snapshots
Dart构建产物与使用
浅谈Flutter构建
Dart VM 是如何运行你的代码的