优化Android Studio在AMD 2990WX上的编译速度

Posted 袁国正_yy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了优化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官方的优化方向主要在以下几个方面:

  1. 垃圾回收(GC)

  2. 合理利用NUMA

  3. 编译器(主要指JIT)和内核设置(Windows也调不成,放弃)

  4. 运行时设置

由于这篇文章针对的操作系统环境是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工程构建依赖的各类子模块进行参数调优,主要分布在以下几种地方:

  1. Android Studio(IntelliJ IDEA)的VM Options

    这个主要用于调优Android Studio的项目加载速度、Indexing速度、代码阅读、查找速度等

  2. Gradle的Properties

    由于现在的Android项目都使用Gradle进行构建,调优Gradle的Properties有助于加速Gradle的Sync、Build等过程

  3. 各类Compiler的命令行参数

    这些Compiler主要用于将Java、Kotlin、XML等代码、资源进行编译、打包,其实际都为一个个Java程序,如:Java Compiler、Kotlin Compiler及Android Compiler(包括DEX和Proguard)

对于AMD Threadripper 2990WX,我尝试添加以下几种命令行参数,这里先以Android Studio的studio64.vmoptions文件为例

-server                        # 以server模式运行JVM,以达到更高的吞吐量
-XX:+BackgroundCompilation     # 使用后台进行机器码编译,优化JIT性能
-XX:+AggressiveOpts            # 优化编译性能
-XX:+AggressiveHeap            # 优化堆性能
-XX:+UseNUMA                   # 使用NUMA,默认是不使用的,对于2990WX尤为关键
-XX:+UseParallelOldGC          # 使用并行老生代GC
-XX:+UseParallelGC             # 使用并行新生代GC
-XX:-UseConcMarkSweepGC        # 停用CMS(并发标记清除)GC,因为会与NUMA所需要的并行GC冲突,导致AS无法启动
-XX:ParallelGCThreads=64       # 并发GC线程数,这里根据逻辑CPU数量设定,也可以稍高一些,可以遵循阿姆达尔定律
-XX:CICompilerCount=64         # 编译器线程数,这里根据逻辑CPU数量设定,也可以稍高一些,可以遵循阿姆达尔定律
-XX:SurvivorRatio=28           # Survivor空间占内存的比例,暂时没搞懂具体的意思,从AMD的文档抄来的,推测是为了减少GC次数
-XX:TargetSurvivorRatio=95     # Survivor空间对象的目标生存率(最大100%),也是抄来的,推测是为了减少GC次数
-XX:MaxTenuringThreshold=15    # 设置最大自适应GC的阈值,最大15,为了和ParallelGC配合使用
-XX:MaxGCPauseMillis=500       # 设置理想的最大GC暂停时间,这样是为了提高Android Studio的响应速度,尽量防止GC造成卡顿
-Xms4g                         # 最小内存值,设高点有助于提升吞吐量
-Xmx4g                         # 最大内存值,设置为4GB

有关于几种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文件进行全局设置,我的配置如下:

# 开启Gradle的Build Cache,减少不必要的编译
org.gradle.caching=true
# 开启Gradle的后台守护进程编码模式,能够在后台自动进行构建,节省点击运行后的编译时间
org.gradle.daemon=true
# 开启Gradle的并行构建模式
org.gradle.parallel=true
# 指定并发数量
org.gradle.parallel.threads=64
# Gradle的JVM命令行参数,可以参考上面AS的JVM参数,但由于是单行字符串的形式,需要在特殊符号(:和=等)前加反斜杠进行转义
org.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工作线程数量
org.gradle.workers.max=64
# Kotlin开启增量编译,节省时间
kotlin.incremental=true
# Kotlin编译开启Cache,节省编译时间
kotlin.caching.enabled=true

同时,也需要在Android Studio的设置中,以命令行参数的形式,配置Gradle的优化项,如图所示:

其中的“Command-line Options”内容如下:

--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 parametersVM Options框中即可,内容参考Android Studio的VM Option,这里也给出具体的内容:

-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略好一些

具体更换步骤:

  1. 设置JAVA_HOME的环境变量,指向Oracle JDK

  2. 删除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倾注的心血!

以上是关于优化Android Studio在AMD 2990WX上的编译速度的主要内容,如果未能解决你的问题,请参考以下文章

无法在AMD Ryzen(Android Studio)上使用HAXM

Android Studio 未在 AMD Ryzen 上启动 Android 模拟器

如何在 AMD 处理器计算机上使用 Android Studio 模拟器?

AMD处理器 配置Android studio以及vs模拟器

android studio 无法正常安装Android Emulator Hypervisor Driver For AMD Processors

amdmacosandroidstudio开不起虚拟