Dart编译技术在服务端的探索和应用

前言

最近闲鱼技术团队在Flutter+Dart的多端一体化的基础上,实现了FaaS研发模式。Dart吸收了其它高级语言设计的精华,例如Smalltalk的Image技术、JVM的HotSpot和Dart编译技术又师出同门。由Dart实现的语言容器,它能够在启动速度、运行性能有不错的表现。Dart提供了AoT、JIT的编译方式,JIT拥有Kernel和AppJIT的运行模式,此外服务端应用有各自不一样的运行特色,那么如何选择合理的编译方法来提高应用的性能?接下来咱们用一些有典型特色的案例来引入咱们在Dart编译方案的实践和思考。前端

案例详情

相应的,咱们准备了短周期应用(EmptyMain & Fibonnacci & faas_tool),长周期应用(HttpServer分别来讲明不一样的编译方法在各类场景下的性能表现java

测试环境参考

#实验机1
Mac OS X 10.14.3 
Intel(R) Core(TM) i7-4770HQ CPU @ 2.20GHz * 4 / 16GB RAM

#实验机2
Linux x86_64
Intel(R) Xeon(R) CPU E5-2650 v2 @ 2.60GHz * 4 / 8GB RAM

#Dart版本
Dart Ver. 2.2.1-edge.eeb8fc8ccdcef46e835993a22b3b48c0a2ccc6f1 

#Java HotSpot版本
Java build 1.8.0_121-b13 
Java HotSpot(TM) 64-Bit Server VM (build 25.121-b13, mixed mode)

#GCC版本
Apple LLVM version 10.0.1 (clang-1001.0.46.3)
Target: x86_64-apple-darwin18.2.0
Thread model: posix

短周期应用git

Case1. EmptyMain

例子是一个空函数实现,以此来评估语言平台自己的启动性能,咱们使用默认参数编译一个snapshotgithub

#1.默认条件下的app-jit snapshot生成
dart snapshot-kind=app-jit snapshot=empty_main.snapshot empty_main.dart

测试结果json

  • 做为现代高级语言Dart和Java在启动速度上在同一水平线
  • C语言的启动速度是其它语言的20x,基本缘由是C没有Java、Dart语言平台的Runtime
  • Kernel和AppJIT方式运行有稳定的微小差别,整体AppJIT优于Kernel

Case2. Fibonacci数列后端

咱们分别用C、Java、Dart用递归实现Fibonacci(50)数列,来考察编译工做对性能的影响。服务器

long fibo(long n){
  if(n < 2){
    return n;
  }
  return fibo(n - 1) + fibo(n - 2);
}

AppJIT使用优化阈值实现激进优化,这样编译器在Training Run中当即得到生成Optimized代码数据结构

#2.执行激进优化          
dart --no-background-compilation \
     --optimization-counter-threshold=1 \ 
     --snapshot-kind=app-jit \
     --snapshot=fibonacci.snapshot
     fibonacci.dart

将Fibonacci编译成Kernel架构

#3.生成Kernel snapshot
dart --snapshot=fibonacci.snapshot fibonacci.dart

AoT的Runtime不在Dart SDK里,须要自行编译AoT Runtime并发

#4.AoT编译
pkg/vm/tools/precompiler2 fibonacci.dart fibonacci.aot

#5.AoT的方式执行
out/ReleaseX64/dart_precompiled_runtime fibonacci.aot

测试结果

  • Dart JIT对比下,AppJIT在激进优化后性能稍好于Kernel,差距微小,编译的成本占比能够忽略不计
  • Dart AoT模式下的性能约为JIT的1/6不到
  • JIT运行模式下,HotSpot的执行性能最优,优于Dart AppJIT 25%以上
  • 包括C语言在内的AoT运行模式性能均低于JIT,Dart AppJIT性能优于25%

问题

AoT因为自身的特性(和语言无关),没法在运行时基于Profile实现代码优化,峰值性能在此场景下要差不少,可是为什么Dart VM比HotSpot有25%的差距?接下来咱们针对Fibonacci作进一步优化

#6.编译器调优,调整递归内联深度
dart --inlining_recursion_depth_threshold=5 fibonacci.snapshot 50

#7.编译器调优,HotSpot调整递归内联深度
java -XX:MaxRecursiveInlineLevel=5 Fabbonacci 50

测试结果

  • HotSpot VM性能全面领先于Dart VM;二者在最优状况下HotSpot VM的性能优于Dart 9%左右
  • Dart VM 借助JIT调优,性能有大幅提高,相比默认状况有40%左右的提高
  • Dart AppJIT 性能微弱领先Kernel

也许也不难想象JVM HotSpot目前在服务器开发领域上的相对Dart成熟,相比HotSpot,DartVM的“出厂设置”比较保守,固然咱们也能够大胆猜想,在服务端应用下应该还有除JIT的其它优化空间;
和Case1相同,Kernel模式的性能依然低于AppJIT,主要缘由是Kernel在运行前期须要把AST转换为堆数据结构、经历Compile、Compile Optimize等过程,而在适当Training run后的AppJIT snapshot在VM启动时以优化后的IL(中间代码)执行,但很快Kernel会追上App-jit,最后性能保持持平。有兴趣的读者能够参阅Vyacheslav Egorov Dart VM的文章。

Case3. FaaS容器编译工具

在前面咱们提到过Dart版本的FaaS语言容器,为追求极致的研发体验,咱们须要缩短用户Function打包到部署运行的时间。就语言容器层面而言,Dart提供的Snapshot技术能够大大提高启动速度,可是从用户Function到Snapshot(以下图)生成所产生的编译时间在不作优化的状况下超过10秒,还远远达不到极致体验的要求。咱们这里经过一些测试,来寻找提高性能的途径

faas_tool是一个彻底用Dart编写的代码编译、生成工具。依托于faas_tool, Function的编写者不用关心如何打包、接入中间件,faas_tool提供一系列的模版及代码生成工具能够将用户的使用成本下降,此外faas_tool还提供了HotReload机制能够快速响应变动。

此次咱们提供了基于AoT、Kernel、AppJIT的用例来执行Function构建流程,分别记录时间消耗、中间产物大小、产物生成时间。为了验证在JIT场景下DartVM是否可经过调整Complier的行为带来性能提高,咱们增长了JIT的测试分组

测试结果

FaaS编译工具-执行状况柱形图 

  • AoT>AppJIT>kernel,其中AoT比优化后的AppJIT有3倍左右性能提高,性能是Source的1000倍
  • JIT(Kernel, AppJIT)分组下,经过在运行时减小CompilerOptimize或暂停PGO能够提高性能

很显然faas_tool最终选择了AoT编译,可是性能结果和Case2截然不同,为了搞清楚缘由咱们进一步作一下CPU Profile

CPU Profile

AppJIT

Dart App-jit模式 43%以上的时间参与编译,固然取消代码优化,可让编译时间大幅降低,在优化状况下能够将这个比率降低到13%

Kernel

Kernel模式有61%以上的CPU时间参与编译工做, 若是关闭JIT优化代码生成,性能有15%左右提高,反之进行激进优化将有1倍左右的性能损耗

AoT下的编译成本

AoT模式下在运行时几乎编译和优化成本(CompileOptimized、CompileUnoptimized、CompileUnoptimized 占比为0),直接以目标平台的代码执行,所以性能要好不少。

P.S. DartVM 的Profile模块在后期的版本升级更改了Tag命名, 有须要进一步了解的读者参考VM Tags

附:DartVM调优和命令代码

#8.模拟单核并执行激进优化           
dart --no-background-compilation \
     --optimization-counter-threshold=1 \ 
      tmp/faas_tool.snapshot.kernel 
      
#9.JIT下关闭优化代码生成
dart --optimization-counter-threshold=-1 \ 
      tmp/faas_tool.snapshot.kernel 

#10. Appjit verbose snapshot
dart --print_snapshot_sizes \
     --print_snapshot_sizes_verbose \
     --deterministic  \
     --snapshot-kind=app-jit \
     --snapshot=/tmp/faas_tool.snapshot faas_tool.dart \
     
#11.Profile CPU 和 timeline 
dart --profiler=true \
     --startup_timeline=true \
     --timeline_dir=/tmp \
     --enable-vm-service \
     --pause-isolates-on-exit faas_tool.snapshot

长周期应用

HttpServer

咱们用一个简单的Dart版的HttpServer做为典型长周期应用的测试用例,该用例中有JsonToObject、ObjectToJson的转换,而后response输出。咱们分别用Source、Kernel以及AppJIT的方式在必定的并发量下运行一段时间

void processReq(HttpRequest request){
  try{
    final List<Map<String,dynamic>> buf = <Map<String,dynamic>>[];
    final Boss boss = new Boss(numOfEmployee: 10);
    //Json反序列化对象
    getHeadCount(max: 20).forEach((hc){
      boss.hire(hc.idType, hc.docId);
      buf.add(hc.toJson());
    });
    request.response.headers.add('cal','${boss.calc()}');
    //Json对象转JsonString
    request.response.write(jsonEncode(buf));
    request.response.close()
      .then((v) => counter_success ++)
      .timeout(new Duration(seconds:3))
      .catchError((e) => counter_fail ++));
  }  
  catch(e){
    request.response.statusCode = 500;
    counter_fail ++;
    request.response.close();
  }
}

测试结果


 
Y轴表示成功的请求量,X轴为时间_

  • 上面三种不管是何种方式启动,最终的运行时性能趋向一致,编译成本在后期能够忽略不计,这也是JIT的运行特色
  • 在AppJIT模式下在应用启动起初就有接近峰值的性能,即便在Kernel模式下也须要时间预热达到峰值性能,Source模式下VM启动须要2秒以上,所以须要相对更长时间达到峰值性能。从另外一方面看应用很快完成了预热,不久达到了峰值性能

P.S. 长周期的应用Optimize Compiler会通过Optimize->Deoptimize->Reoptimize的过程, 因为此案例比较简
单,没体现Deoptimize到Reoptimize的表现

附:VM调优脚本

#12.调整当前isolate的新生代大小,默认2M最大32M的新生代大小形成频繁的YGC
dart --new_gen_semi_max_size=512  \
     --new_gen_semi_initial_size=512  \ 
     http_server.dart \
     --interval=2

总结和展望

Dart编译方式的选择

  • 编译成本为主导的应用,应优先考虑AoT来提升应用性能
  • 长周期的应用在启动后期编译成本可忽略,应该选择JIT方式并开启Optimize Compiler,让优化器介入
  • 长周期的应用能够选择Kernel的方式来提高启动速度,经过AppJIT的方式进一步缩短warmup时间

AppJIT减小了编译预热的成本,这个特性很是适合对一些高并发应用在线扩容。Kernel做为Dart编译技术的前端,其平台无关性将继续做为整个Dart编译工具链的基础。

在FaaS构建方案的选择

经过CPU Profile得出faas_tool是一个编译成本主导的应用,最终选择了AoT编译方案,结果大大提高了语言容器的构建的构建速度,很好知足了faas对开发效率的诉求

仍需改进的地方

从JIT性能表现来看,DartVM JIT的运行时性和HotSpot相比有提高余地,因为Dart语言做为服务端开发的历史不长,也许随着Dart在服务端的技术应用全面推广,相信DarVM在编译器后端技术上对服务器级的处理器架构作更多优化

 

原文连接 本文为云栖社区原创内容,未经容许不得转载。

相关文章
相关标签/搜索