优化 Android Studio 在 AMD 2990WX 上的编译速度
由来
一个月前,剁手了 AMD Ryzen Threadripper 2990WX(官网),这个处理器的参数着实牛逼,32 核心 64 线程,总共 80MB 的缓存,可以说秒杀目前所有的桌面级处理器了!狠了狠心,搞了一台,辅以 32GB DDR4 3200MHz 内存和 970 EVO NVMe SSD,经过一番折腾(无 CPU 刷 BIOS、装 Windows/Linux 系统),最终确定使用 Windows 10 专业工作站版作为日常开发使用。
Cinebench R15 跑分:
CPU-Z 跑分:
虽然分数不像网上那些人跑得那么高,但也是相当猛了,再加上任务管理器 64 个框框,心里十分舒坦!
于是乎,安装 Android Studio 等一系列开发工具,心想编译速度总算能够爽很多了……
然鹅!!
在开启 Android Studio 进行 Build 的时候,CPU 使用率最高不超过 20%,基本处于划水状态,偶尔会跑满几个线程,最多不超过 16 个线程
划水状态的 CPU:
但如果在 Linux 上使用 GCC 或者在 Windows 上使用 VSBuild 编译如 Node.js 这样的 C/C++源码,就能够达到下图的状态
满血状态的 CPU:
心里总觉得很不甘、很蹊跷,于是开始折腾……
了解一下牛逼的架构
这张图用的太多了,几乎谈论到 AMD 的 EPYC 和 Threadripper 处理器,都会拿它说事
AMD 的 EPYC 和 Threadripper 处理器都是采用 4 个 Die 的形式,加上优秀的 12nm 工艺控制功耗和温度,从而实现超多的核心数量,不得不说能想出这样的设计的人,真的是天才!
不同的 CPU 型号,启用的 Die 的数量不一样,但实际都是 4 个,只是有的型号上,关闭的 Die 作为辅助计算使用
内存访问的不足
但 EPYC 对内存的访问是完整的 8 个通道,而 2990WX 和 2970WX 则阉割成了 4 个通道(据说是为了兼容 X399 芯片组),这样一来就会导致其中 2 个 Die 可以直接访问内存,而另外 2 个 Die 则需要通过特定的 Infinity Fabric 来间接地访问内存,一旦操作系统的调度出现问题,可能会导致内存性能骤减,CPU 执行一会儿,就要等待一下内存,使得 Threadripper 对内存密集型进程的性能,同 Intel 的 i9 相比表现不佳(如:Photoshop)
相关文章:https://zhuanlan.zhihu.com/p/45606819
NUMA
由于采用了 4 Die+4 通道内存访问的设计,2990WX 即变成了 NUMA(Non Uniform Memory Access Architecture,非统一内存访问架构),摇身一变成为一个 4 路 CPU(可以从 Windows 的任务管理器中看到)
虽然这种设计能够使计算机的扩展性更好,但由于内存访问、缓存数据同步等方面的问题,这种架构对操作系统和应用程序的调度设计考验较大,如果没有进行专门的调优,可能并不能完全发挥出硬件的性能
推测&调优
了解了 Threadripper 的基本架构,于是猜测是不是 NUMA 限制了 Android Studio、JVM 在 2990WX 上的性能,开始进一步的尝试……
注:以下的调优环境,全部基于 64 位操作系统和 JVM 虚拟机,32 位不在考虑范围内
查到一篇官方资料
在 Google 各类 NUMA、Java 和 AMD 相关的内容时,偶然发现一篇 AMD 官方的文档,题为:Java Application Performance Tuning for AMD EPYC™ Processors
虽然这篇文章主要是为了用于为多路的 EPYC 服务器进行 Java 服务的调优,但综合 EPYC 和 Threadripper 的特征来看,Threadripper 也是需要调优的!
AMD 官方的优化方向主要在以下几个方面:
垃圾回收(GC)
合理利用 NUMA
编译器(主要指 JIT)和内核设置(Windows 也调不成,放弃)
运行时设置
由于这篇文章针对的操作系统环境是 Linux,所以诸如numactl
这样的配置,在 Windows 上就无法使用了,如果你使用 Ubuntu,可以参考这篇文章进行更具体的调控:https://linux.die.net/man/8/numactl
了解 JVM 调优参数
阅读了 AMD 的官方文档,发现这些优化的主要表现,就是 Java 程序的运行参数调整,即所谓的Java HotSpot VM Options,所以需要深入了解 JVM 的 Options,比较有用的是以下两篇 Oracle 文档
JVM Options 说明:https://www.oracle.com/technetwork/articles/java/vmoptions-jsp-140102.html
Java 命令行参数(Windows 系统):https://docs.oracle.com/javase/8/docs/technotes/tools/windows/java.html
有人给出了一套比较完整的 Java 程序调优流程思想,这里盗个图(原文链接):
从上图的红线部分可以看到,JVM 性能优化的核心思想就是对Hotspot虚拟机和GC进行调优
Android Studio 调优
那么对于 Android Studio 的优化,不光是 AS 自身的性能调优,还需要对 Android 工程构建依赖的各类子模块进行参数调优,主要分布在以下几种地方:
Android Studio(IntelliJ IDEA)的 VM Options
这个主要用于调优 Android Studio 的项目加载速度、Indexing 速度、代码阅读、查找速度等
Gradle 的 Properties
由于现在的 Android 项目都使用 Gradle 进行构建,调优 Gradle 的 Properties 有助于加速 Gradle 的 Sync、Build 等过程
各类 Compiler 的命令行参数
这些 Compiler 主要用于将 Java、Kotlin、XML 等代码、资源进行编译、打包,其实际都为一个个 Java 程序,如:Java Compiler、Kotlin Compiler 及 Android Compiler(包括 DEX 和 Proguard)
对于 AMD Threadripper 2990WX,我尝试添加以下几种命令行参数,这里先以 Android Studio 的 studio64.vmoptions 文件为例
1 | -server # 以 server 模式运行 JVM,以达到更高的吞吐量 |
有关于几种 GC 类型的说明,可以参考这篇文章:http://www.importnew.com/14086.html
由于需要使用 NUMA,所以我强制让 Android Studio 使用了 ParallelGC,而 Android Studio 默认使用的是 ConcMarkSweepGC,只可以而选一,否则会导致 Android Studio 无法启动的问题
由于同时使用 ParallelGC/ConcMarkSweepGC/G1GC 导致无法启动(报错:Failed to create JVM: error code -1):
Gradle 调优
Gradle 也是使用 Java 开发的,所以对 Gradle 的优化,原理和 Android Studio 是一致的,只不过 Gradle 自身也有一些特定的参数,具体可以参考 Gradle 的官网文档:
gradle.properties 文件参数文档:https://docs.gradle.org/current/userguide/build_environment.html
命令行参数文档:https://docs.gradle.org/current/userguide/command_line_interface.html
我们可以通过用户主目录(Unix-Like 系统上为~/,Windows 系统上为 C:/Users/用户名)下的.gradle
目录中的gradle.properties
文件进行全局设置,我的配置如下:
1 | # 开启 Gradle 的 Build Cache,减少不必要的编译 |
同时,也需要在 Android Studio 的设置中,以命令行参数的形式,配置 Gradle 的优化项,如图所示:
其中的“Command-line Options”内容如下:
1 | --parallel --daemon --build-cache --max-workers=128 -Dorg.gradle.jvmargs="-server -XX\:+BackgroundCompilation -XX\:+AggressiveOpts -XX\:+AggressiveHeap -XX\:+UseNUMA -XX\:+UseParallelOldGC -XX\:+UseParallelGC -XX\:-UseConcMarkSweepGC -XX\:ParallelGCThreads\=64 -XX\:CICompilerCount\=64 -XX\:SurvivorRatio\=28 -XX\:TargetSurvivorRatio\=95 -XX\:MaxTenuringThreshold\=15 -XX\:MaxGCPauseMillis\=500 -Xms4g -Xmx4g -XX\:-HeapDumpOnOutOfMemoryError -Dfile.encoding\=UTF-8" |
如果需要对不同项目进行不同配置,在项目根目录中的gradle.properties
文件中配置即可
Java Compiler、Kotlin Compiler、Android Compiler 调优
其中,Java Compiler 和 Kotlin Compiler 分别负责 Java 代码和 Kotlin 代码的虚拟机(JVM 或 Dalvik)字节码翻译工作,而 Android Compiler 负责将字节码打包为 DEX 文件,以及 Proguard 的代码混淆工作
在上图所示的界面中,填入Additional command line parameters
或VM Options
框中即可,内容参考 Android Studio 的 VM Option,这里也给出具体的内容:
1 | -server -XX:+BackgroundCompilation -XX:+AggressiveOpts -XX:+AggressiveHeap -XX:+UseNUMA -XX:+UseParallelOldGC -XX:+UseParallelGC -XX:-UseConcMarkSweepGC -XX:ParallelGCThreads=64 -XX:CICompilerCount=64 -XX:SurvivorRatio=28 -XX:TargetSurvivorRatio=95 -XX:MaxTenuringThreshold=15 -XX:MaxGCPauseMillis=500 -Xms4g -Xmx4g -XX:-HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 |
至此,Android Studio 和其构建工具链在参数配置方面的优化,基本就已经完成了
其他优化
选择合适的 JRE
Android Studio 自带的 JRE 来自 JetBrains 自己编译的 64 位 OpenJDK(和 IDEA、WebStorm 等 IDE 一致),可以满足大部分的应用场景,但我仍然推荐使用 Oracle 的 JDK,性能优化的效果要比 OpenJDK 略好一些
具体更换步骤:
设置
JAVA_HOME
的环境变量,指向 Oracle JDK删除 Android Studio 目录中的jre文件夹
如果不做 2 中的删除操作,那么使用 Android Studio 的快捷方式或 studio64.exe 开启 Android Studio 时,仍会使用自带的 OpenJDK,除非在 bin 目录下执行 studio.bat
相对独立 Module
Gradle 的并行构建能力,对于比较独立的 Project、Module 来说优化较好。这也比较容易理解,如果各 Module 之间的耦合、依赖过强,那么构建过程基本就变成了串行执行,多核 CPU 的能力自然无法完全发挥出来,可以参考 Gradle 对 Decoupled Project 的定义:https://docs.gradle.org/current/userguide/multi_project_builds.html#sec:decoupled_projects
大致优化的思路就是减少模块的耦合程度,尽量独立,特别是要减少链式的依赖
操作系统
由于 Windows 10 操作系统对 AMD 的这种多 Die 形式调度优化不佳,所以可以尝试使用 Ubuntu 18.04,如果比较依赖 Windows,也推荐使用 Windows 10 Pro for Workstation,即专业工作站版,这个比专业版的调度优化更好,能够尽可能多地发挥多 Die 的性能,比如开启“卓越性能”的电源计划
其次,需要为 Threadripper(2990WX、2970WX)安装最新版的 X399 芯片组驱动和 Ryzen Master,并开启 Dynamic Local 模式,从而优化内存访问效率
总结
自 2013 年 Android Studio 第一个版本推出以来,由于 Gradle 的引用,导致其全量编译速度比以前的 ADT(Eclipse)慢(但增量很快),经过几年的迭代,Gradle 的性能也在不断地提升,Android 的构建也越来越强大了,但 AMD Ryzen Threadripper 2990WX 的出现,使得消费级 CPU 的核心数量大幅提升,一些应用程序、操作系统针对这么多核 CPU 的优化还没有及时跟上,导致 CPU 的性能不能完全发挥。为了性能(也为了血汗钱),我们需要不断地折腾,压榨 CPU,让它为我们节省时间,提高开发效率,毕竟,时间就是金钱!
最后,放一张优化后再跑 Android Studio 构建时的 CPU 利用率图,以此来表现我对这块 CPU 倾注的心血!