分布式编译


Android工程代码量很大,在普通的个人PC上编译甚至在配置较低的服务器上的编译速度都非常慢,通常完全编译在1h~3h左右。为了加快编译速度,想到了建立distcc分布式编译环境.

本文着重记录Distcc/Ccache的使用和在Android工程上的修改方法,分布式编译环境组网方案,以及可能存在的瓶颈点。


工具

DISTCC

一个非常快且免费的分布式C/C++编译工具,它可以实现将本地机器的编译任务分配到一台或多台远程服务器上进行编译,完成后再发送回本地机器进行连接。它旨在充分利用局域网内所有PC机器的CPU和Memory资源,来辅助提升本地机器的编译速度。

distcc分成2个部分
① Client端:distcc客户端进程,运行在本地编译PC上,来分发任务到不同的server并负责预编译、链接生成可执行文件。
② Server端:distccd守护进程,运行在远程编译PC上,来接收编译任务并且只做编译生成.o文件。

distcc监视工具
① distccmon-text:运行于Terminal的监视工具,用法:distccmon-test N,这里N是秒数。
② distccmon-gnome:窗口监视工具,用法:distccmon-gnome。

  • Tasks:状态颜色标示(绿色:compiling;紫色:preprocessing;蓝色:receiving;橙色:send;白色:idle;)

distcc架构图
TODO...

distcc源码获取
主页
安装包
源码

distcc安装

1
2
sudo apt-get install distcc  //(包含distcc和distccd)
sudo apt-get install distccmon-gnome   //(该监测软件需要单独安装)

distcc配置
Server端
① 配置/prebuilts编译工具链目录:
拷贝所有android工具链到指定位置,该目录包含从gcc4.5 -> gcc4.9(aarch64)等的所有工具链,不同工程比如4.4和5.0可以用5.0的覆盖4.4的,这里假设拷贝到/usr/local/bin/下,只需要拷贝/prebuilts目录下的gcc和tools目录即可。

1
2
3
sudo mkdir -p /usr/local/bin/prebuilts
cp -r $(Abdroid_Project_Path)/prebuilts/gcc /usr/local/bin/prebuilts
cp -r $(Abdroid_Project_Path)/prebuilts/tools /usr/local/bin/prebuilts

② 配置distccd工具

  • /etc/default/distcc: (修改)
1
2
3
4
STARTDISTCC="true" 
ALLOWEDNETS="0.0.0.0/0"
LISTENER=""
ZEROCONF=””
  • /etc/init.d/distcc: (增加)
1
2
export  DISTCC_CMDLIST=/etc/distcc/cmdlist.config
export  DISTCC_CMDLIST_NUMWORDS=4
  • /etc/distcc/cmdlist.config:(新增)
1
2
3
4
5
cd /etc/distcc
sudo vim cmdlist.config  
// 填加交叉工具本机可执行路径: 这里假设/prebuilts目录 被放置到了/usr/local/bin下
// /usr/local/bin/prebuilts/gcc/.../arm-eabi-gcc
// /usr/local/bin/prebuilts/tools/.../...
  • 重启distccd服务:
1
2
sudo /etc/init.d/distcc stop
sudo /etc/init.d/distcc start

Client端
① 配置Android工程的Makefile:Android + Kernel + Uboot + ... (见章节Android工程修改

  • Host,Target
  • gcc,g++,as,ar,strip...

② 动态开关
切换分布式编译和普通编译(通过shell环境变量实现,见章节Android工程修改

③ 配置distcc工具

  • /etc/distcc/hosts
1
2
// 输入分布式机器的IP以及任务数,例如:10.5.41.99/16 其中前面是IP地址,后面16是最大分配任务数。
// IP也可以通过配置前面的zeroconf为true开启自动搜索可用IP功能,不过该功能只能搜到同网段的IP机器,网段以外的只能通过上面的方法手动填加IP地址.

CCACHE

编译缓存,能极大的提高再次编译的速度。Android工程本身也支持ccache,只需要打开export USE_CCACHE=1即可。

Android自带ccache缺陷

  • 只能给Android代码编译用,Kernel/Uboot等等代码无法使用(使用的不同工具链导致)
  • 需要另外设置ccache缓存大小,默认最大1G,远远不够用的
  • 版本很陈旧

解决办法
安装PC上的ccache,使用本地PC的ccache,而不用android自带的

1
sudo apt-get install ccache

使用方法

1
2
ccache -M 50G                      //设置ccache空间大小50G
watch -n1 ccache -s                //监控ccache命中等数据

配合distcc编译使用:(详细修改方式,见章节Android工程修改

DMUCS

负载平衡,帮助Client端PC通过负载选择适合的服务器去分发任务
TODO...

CCONTROL

并行的中央控制、缓冲和分配,甚至编译时也可以
TODO...

CROSSTOOL

自动构建交叉编译工具链
TODO...

TCPBALANCE

负责平衡的TCP代理
TODO...

DistccWebView

显示你的哪个服务端在运行和编译的CGI
TODO...

RANT

Java Code的远程编译服务
TODO...


数据分析

用android4.4+kernel3.10做测试

个人PC

编译时间下降60%,从100m~180m左右下降到36m~50m左右,如下图:

服务器PC

编译时间下降60%,从50m左右下降到19m左右,如下图:

分析结论

不论服务器还是个人PC,对于编译时间长短,实测数据均支持以下结论

  • 第一次编译时,distcc分布式加速明显;
  • 第二次及以上编译时,ccache加速要比distcc分布式更加有效,但是会耗费非常多的磁盘I/O,一般会造成个人PC非常卡顿;
  • distcc的动态IP检测zeroconf会造成读写磁盘频繁,造成PC非常卡顿,非SSD硬盘不建议使用;
  • 服务器PC整体编译,极限速度一般在19m左右;
  • 个人PC由于磁盘I/O读写限制,编译速度极限一般在34m左右;更换SSD硬盘后,编译速度应该能够达到服务器水平;
  • 从实测看,测试环境的网速、网络I/O没有对分布式产生影响;

组网方案

从测试所用组网数据以及编译用途看,采取服务器PC、个人PC混合组网方式,采用开启distcc+开启ccache+关闭zeroconf;

以服务器PC之间的分布式编译为主

  • 目的是加快编译服务器、hudson、verify服务器的编译速度,提高效率;
  • 磁盘I/O效率要比个人PC高很多,直观感受编译加速明显,大概19min完成编译;

以个人PC与服务器之间的分布式编译为辅

  • 在一定程度上加快个人PC的编译速度;
  • 磁盘I/O影响较大,直观感受加速不明显,大概40min左右完成编译;
  • 不受服务器磁盘容量、远程登录等不方便因素的影响;

以个人PC之间的分布式编译为补充

  • 服务器数目有限,可以尝试配置一些相邻区域个人PC作为server端用;
  • 可能会影响到个人PC的使用速度,在一定程度上可能会影响到其他人的办公效率;
  • 但是会在服务器负载较高时配合提高编译速度;

性能调优

实测缺陷

① 针对个人PC而言:

  • 实测发现磁盘I/O是主要block点,编译在35m左右时很难再下降,而且编译进程D状态、I/Owait很高、PC卡顿现象明显;

可考虑方向:

  • 在软件方面的优化就比较难进行了,可以考虑:调整磁盘缓存大小、调整脏页回写频率、调整磁盘文件系统等;
  • 而硬件方面的优化就比较简单,更换ssd固态硬盘,能够极大程度上提高磁盘I/O读写效率,猜测能使个人PC在分布式中达到服务器速度;

② 针对服务器PC而言,也有类似问题,但是表现并不明显而已

调优方向

  • project 工程中未配置distcc的工具链:(查缺补漏)
  • project 工程中不能用分布式的工具链:(是否能想办法改成可用分布式的)
  • server 之间的CPU Loadbalance:(dmucs + server配置可接纳线程数目)
  • server 数目:(PC越多越好,PC的core数目越多越好)
  • client 磁盘I/O瓶颈:(硬伤,增加固态硬盘? 或者调整文件系统?磁盘缓存?)
  • client make可并发的最大线程数目:(distcc可以配置)
  • client 使用的make线程数-jN:(N值的确定,需要实测所处网络数据,找到最优值)
  • client 不配置本机编译:(只对client PC性能太差的环境有用处)
  • web 网络I/O,网速BW:(更换网卡、更新网卡驱动、更换交换机100M以上)
  • web server端动态IP问题:(zeroconf会造成电脑卡顿明显)
  • java的分布式编译:(rant java远程编译工具)

Android工程修改

这里是针对非原生ccache的修改,为了配合使用distcc和ccache,需要增加对distcc的支持,对android工程中涉及C/C++编译工具链的目录中某些makefile做修改:

$(android_projetcs)/build/core/combo/TARGET_...mk & HOST_...mk

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
ifneq (,$(strip $(wildcard $(HOST_TOOLCHAIN_PREFIX)/gcc)))
+HOST_CC  := $(WRAPPER) $(HOST_TOOLCHAIN_PREFIX)/gcc
+HOST_CXX := $(WRAPPER) $(HOST_TOOLCHAIN_PREFIX)/g++
+ifneq ($(USE_CCACHE),)
HOST_CC  := $(HOST_TOOLCHAIN_PREFIX)/gcc
HOST_CXX := $(HOST_TOOLCHAIN_PREFIX)/g++
+endif
 
+TARGET_CC := $(WRAPPER) $(TARGET_TOOLS_PREFIX)gcc$(HOST_EXECUTABLE_SUFFIX)
+TARGET_CXX := $(WRAPPER) $(TARGET_TOOLS_PREFIX)g++$(HOST_EXECUTABLE_SUFFIX)
+ifneq ($(USE_CCACHE),)
TARGET_CC := $(TARGET_TOOLS_PREFIX)gcc$(HOST_EXECUTABLE_SUFFIX)
TARGET_CXX := $(TARGET_TOOLS_PREFIX)g++$(HOST_EXECUTABLE_SUFFIX)
+endif

$(android_projetcs)/kernel/Makefile

1
2
3
4
5
6
7
8
9
AS             = $(CROSS_COMPILE)as
LD             = $(CROSS_COMPILE)ld
-CC             = $(CROSS_COMPILE)gcc
+CC             = $(WRAPPER) $(strip (ARM_EABI_TOOLCHAIN))/$(strip (CROSS_COMPILE))gcc
+ifneq ($(USE_CCACHE),)
+CC             := $(CROSS_COMPILE)gcc
+endif
CPP            = $(CC) -E
AR             = $(CROSS_COMPILE)ar

$(android_projetcs)/chipram/config.mk

1
2
3
4
5
6
7
8
9
AS     = $(CROSS_COMPILE)as
LD     = $(CROSS_COMPILE)ld
-CC     = $(CROSS_COMPILE)gcc
+CC     = $(WRAPPER) $(strip $(ARM_EABI_TOOLCHAIN))/$(strip $(CROSS_COMPILE))gcc
+ifneq ($(USE_CCACHE),)
+CC     := $(CROSS_COMPILE)gcc
+endif
CPP    = $(CC) -E
AR     = $(CROSS_COMPILE)ar

$(android_projetcs)/u-boot/config.mk

1
2
3
4
5
6
7
8
9
AS     = $(CROSS_COMPILE)as
LD     = $(CROSS_COMPILE)ld
-CC     = $(CROSS_COMPILE)gcc
+CC     = $(WRAPPER) $(strip $(ARM_EABI_TOOLCHAIN))/$(strip $(CROSS_COMPILE))gcc
+ifneq ($(USE_CCACHE),)
+CC     := $(CROSS_COMPILE)gcc
+endif
CPP    = $(CC) -E
AR     = $(CROSS_COMPILE)ar

修改对ccache的支持,对android工程中配置工具链的makefile做修改

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
    ifneq ($(USE_CCACHE),)
        ...
        ...
             endif
             ccache =
           endif
        +else
        +  ifeq ($(WRAPPER),ccache)
        +    export CCACHE_COMPILERCHECK := content
        +    export CCACHE_SLOPPINESS := time_macros,include_file_mtime,file_macro
        +  endif
     endif

Android工程编译

配合使用distcc分布式和ccache编译缓存

首次整体编译

1
2
3
4
5
6
7
make clean
export WRAPPER=ccache
export CCACHE_PREFIX=distcc
source build/envsteup.sh
lunch XXX
kheader
time make -jN

其它多次编译

1
2
3
4
5
6
export WRAPPER=ccache
export CCACHE_PREFIX=distcc
source build/envsteup.sh
lunch XXX
kheader
time make -jN

若之前编译过一次,则同一shell环境下编译:

1
make -jN

只用distcc做分布式编译

首次整体编译

1
2
3
4
5
6
make clean
export WRAPPER=distcc
source build/envsteup.sh
lunch XXX
kheader
time make -jN

其它多次编译

1
make -jN

只用ccache编译缓存

首次整体编译

1
2
3
4
5
6
7
make clean
export WRAPPER=ccache
ccache -M 20G
source build/envsteup.sh
lunch XXX
kheader
time make -jN

其它多次编译

1
2
3
4
5
export WRAPPER=ccache
source build/envsteup.sh
lunch XXX
kheader
time make -jN

若之前编译过一次,则同一shell环境下编译:

1
make -jN

参考文档

暂无

Comments
Write a Comment
  • Apple reply

    写得挺好的,大佬怎么不更新文章了啊

    • Jackywei22222 reply

      @Apple 因为,没有什么实用价值,这个只是针对android 4.4的,现在,已经是android 13了.