@emac
2015-11-25T15:08:13.000000Z
字数 3370
阅读 3606
架构
持续集成的速度是衡量一个团队敏捷水平的核心指标之一。无法快速集成,就无法快速响应变化(Agile Manifesto: Responding to change)。极端情况下,持续集成的速度甚至决定了一个产品的生死存亡。
持续集成一般包含两部分,打包和发布。在XR,这个过程一般在4~20分钟,其中打包占用了绝大多数时间,而发布一般不超过30秒。我们的目标是全部加起来不超过1分钟。Sounds impossible?可以看一下这篇文章(PPT)。
作为第一阶段目标,先从优化打包速度开始。
打包命令:
act reload clean mobile/universal:packageZipTarball
命令解释:
更多信息参考 http://www.scala-sbt.org/0.13/docs/Command-Line-Reference.html
执行reload命令时,从各个Ivy库和Maven库下载各类play/sbt插件和项目所依赖的类库占用了绝大部分时间。
优化前的远程仓库列表(运行inspect fullResolvers
查看):
由于sbt会严格按照顺序逐一查询每一个仓库以获得每一个插件或者类库的下载地址,因此仓库的出现顺序非常重要。正确的排序依据是速度快的排在速度慢的前面,频次高的排在频次低的前面。而上述列表存在4个问题:
前3个仓库是由sbt默认添加,所有用户添加的仓库只能出现在它们后面,如果要让用户仓库出现在它们之前,需要启用sbt的Proxy Repositories机制(运行sbt时添加-Dsbt.override.build.repos=true属性)。
优化后的远程仓库列表:
在搜索远程仓库之前,sbt会先查找本地仓库,默认包括~/.ivy2,activity_home/repository, ~/.activity/repository。而activity安装包中默认打包了很多play和sbt的核心类库和插件库。如果将应用依赖的play和sbt版本设置为和activity内嵌的一样,那么就省去了下载这些库的时间。
查看sbt日志,不难发现,打包过程中除了jar包,还打了-sources和-javadoc包,这两类包其实是完全不需要的。在build.sbt中添加如下设置即可禁止生成。
// prevent producing src jar
publishArtifact in (Compile, packageSrc) := false,
// prevent generating api doc
sources in (Compile, doc) := Seq.empty,
// prevent producing doc jar
publishArtifact in (Compile, packageDoc) := false
跟maven一样,sbt本身是一个Java应用,理论上存在一定的调优空间。实际测试发现,sbt对常规的调优手段免疫,无论是调整内存大小还是使用不同的GC策略,或者是两者的各种组合,对结果影响甚微,总的GC时间稳定在5秒左右。究其原因,一方面,下载和编译是构建过程中占时最多的两个步骤,这两者分别是典型的IO型和CPU型任务,对内存占用有限;另一方面,由于scala的编译性能广为诟病,sbt官方应该做了大量的性能优化,留给用户的优化空间已经很小。
全量(清空~/.ivy2目录):
之前 | 之后 |
---|---|
超时(>1小时) | 342s |
增量(加速25%):
次数 | 之前 | 之后 |
---|---|---|
第一次 | 189s | 142s |
第二次 | 192s | 138s |
下载插件和类库决定了首次打包的速度,而对于日常打包,编译占用了最多的时间(>80%)。为了减少编译时间,常见的手段有使用更快的编译器,优化项目代码,或者采用增量编译。当应用运行在开发模式(直接从源码运行)下时,sbt支持动态加载源码的特性,即侦听任何源文件的改动,并在不重启应用的前提下实时进行编译和加载。使用这个特性可以免去冗长的打包和全量编译的过程,将发布的时间从10分钟降到1分钟,在牺牲一定的性能为代价的前提下。由于存在性能问题和增量编译带来的不确定性,这个模式非常适用于测试环境,但不适用于生产环境。