背景:
Android工程代码量很大,在普通的个人PC上编译甚至在配置较低的服务器上的编译速度都非常慢,通常完全编译在1h~3h左右。为了加快编译速度,想到了建立distcc分布式编译环境。
工具:
-
DISTCC:
一个非常快且免费的分布式C/C++编译工具,它可以实现将本地机器的编译任务分配到一台或多台远程服务器上进行编译,完成后再发送回本地机器进行连接。它旨在充分利用局域网内所有PC机器的CPU和Memory资源,来辅助提升本地机器的编译速度。
distcc分成2个部分:
(1)client端:
distcc客户端进程,运行在本地编译PC上,来分发任务到不同的server并负责预编译、链接生成可执行文件;
(2)server端:
distccd守护进程,运行在远程编译PC上,来接收编译任务并且只做编译生成.o文件;
distcc监视工具:
(1)distccmon-text:运行于Terminal的监视工具,用法:distccmon-test N,这里N是秒数
(2)distccmon-gnome:窗口监视工具,用法:distccmon-gnome
Tasks:状态颜色标示(绿色:compiling;紫色:preprocessing;蓝色:receiving;橙色:send;白色:idle;)
distcc架构图:
distcc源码获取:
主页:https://code.google.com/p/distcc/
安装包:https://code.google.com/p/distcc/downloads/list
源码:https://code.google.com/p/distcc/source/checkout
distcc安装:
sudo apt-get install distcc(分布式软件,包含distcc和distccd)
sudo apt-get install distccmon-gnome (该监测软件需要单独安装)
distcc配置:
Server端:
1)配置/prebuilts编译工具链目录:
(1)能够包含从gcc4.5 -> gcc4.9(aarch64)等的所有工具链:
拷贝所有android工具链到指定位置: (不同工程,比如4.4和5.0可以用5.0的覆盖4.4的)
这里假设拷贝到
/usr/local/bin/
下:
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
这里只需要拷贝
/prebuilts
目录下的gcc和tools目录即可
2)配置distccd工具:
(1)/etc/default/distcc: (修改)
STARTDISTCC=
"true"
ALLOWEDNETS=
"0.0.0.0/0"
LISTENER=
""
ZEROCONF=””
(2)/etc/init.d/distcc: (增加)
export
DISTCC_CMDLIST=
/etc/distcc/cmdlist
.config
export
DISTCC_CMDLIST_NUMWORDS=4
(3)/etc/distcc/cmdlist.config:(新增)
cd
/etc/distcc
sudo
vi
cmdlist.config
填加交叉工具本机可执行路径: 这里假设
/prebuilts
目录 被放置到了
/usr/local/bin
下
/usr/local/bin/prebuilts/gcc/
...
/arm-eabi-gcc
/usr/local/bin/prebuilts/tools/
.../...
(4)重启distccd服务:
sudo
/etc/init
.d
/distcc
stop
sudo
/etc/init
.d
/distcc
start
Client端:
1)配置Android工程的Makefile:Android + Kernel + Uboot + ... (见本文最下面“android工程修改”)
(1)Host,Target
(2)gcc,g++,as,ar,strip...
(3)动态开关:切换分布式编译和普通编译(通过shell环境变量实现,见本文最下面“android工程修改”)
2)配置distcc工具:
(1)/etc/distcc/hosts
输入分布式机器的IP以及任务数,例如:10.5.41.99
/16
其中前面是IP地址,后面16是最大分配任务数。
IP也可以通过配置前面的zeroconf为
true
开启自动搜索可用IP功能,不过该功能只能搜到同网段的IP机器,网段以外的只能通过上面的方法手动填加IP地址。
CCACHE:
编译缓存,能极大的提高再次编译的速度。
Android工程本身也支持ccache,只需要打开export USE_CCACHE=1即可,但是:
(1)只能给Android代码编译用,Kernel/Uboot等等代码无法使用;
(2)版本很陈旧;
(3)需要另外设置ccache缓存大小,默认最大1G,远远不够用的;
解决办法:安装PC上的ccache,使用本地PC的ccache,而不用android自带的;
sudo
apt-get
install
ccache
使用方法:
ccache -M 50G
//
设置ccache空间大小50G
watch
-n1 ccache -s
//
监控ccache命中等数据
配合distcc编译使用:(详细修改方式,见本文最下面"Android工程修改")
DMUCS:
负载平衡,帮助Client端PC通过负载选择适合的服务器去分发任务
CCONTROL:
并行的*控制、缓冲和分配,甚至编译时也可以
CROSSTOOL:
自动构建交叉编译工具链
TCPBALANCE:
负责平衡的TCP代理
DistccWebView:
显示你的哪个服务端在运行和编译的CGI
RANT:
Java Code的远程编译服务
数据分析: 用android4.4+kernel3.10做测试
个人PC:编译时间下降60%,从100m~180m左右à36m~50m左右
服务器PC:编译时间下降60%,从50m左右à19m左右
分析:不论服务器还是个人PC,对于编译时间长短,实测数据均支持以下结论
结论:
1)第一次编译时,distcc分布式加速明显;
2)第二次及以上编译时,ccache加速要比distcc分布式更加有效,但是会耗费非常多的磁盘I/O,一般会造成个人PC非常卡顿;
3)distcc的动态IP检测zeroconf会造成读写磁盘频繁,造成PC非常卡顿,非SSD硬盘不建议使用;
4)服务器PC整体编译,极限速度一般在19m左右;
5)个人PC由于磁盘I/O读写限制,编译速度极限一般在34m左右;更换SSD硬盘后,编译速度应该能够达到服务器水平;
6)从实测看,测试环境的网速、网络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而言,也有类似问题,但是表现并不明显而已;
-
调优:
1)project 工程中未配置distcc的工具链:(查缺补漏)
2)project 工程中不能用分布式的工具链:(是否能想办法改成可用分布式的)
3)server 之间的CPU Loadbalance:(dmucs + server配置可接纳线程数目)
4)server 数目:(PC越多越好,PC的core数目越多越好)
5)client 磁盘I/O瓶颈:(硬伤,增加固态硬盘? 或者调整文件系统?磁盘缓存?)
6)client make可并发的最大线程数目:(distcc可以配置)
7)client 使用的make线程数-jN:(N值的确定,需要实测所处网络数据,找到最优值)
8)client 不配置本机编译:(只对client PC性能太差的环境有用处)
9)web 网络I/O,网速BW:(更换网卡、更新网卡驱动、更换交换机100M以上)
10)web server端动态IP问题:(zeroconf会造成电脑卡顿明显)
11)java的分布式编译:(rant java远程编译工具)
Android工程修改:
为了配合使用distcc和ccache,需要:
增加对distcc的支持,对android工程中涉及C/C++编译工具链的目录中某些makefile做修改:
(1)$(android_projetcs)/build/core/combo/TARGET_...mk & HOST_...mk:
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
(2)$(android_projetcs)/kernel/Makefile:
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
(3)$(android_projetcs)/chipram/config.mk:
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
(4)$(android_projetcs)/u-boot/config.mk:
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
(5)Others:(如果有的话)
TODO...
修改对ccache的支持,对android工程中配置工具链的makefile做修改:
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编译缓存:
首次整体编译:
make
clean
export
WRAPPER=ccache
export
CCACHE_PREFIX=distcc
source
build
/envsteup
.sh
lunch XXX
kheader
time
make
-jN
其它多次编译:
export
WRAPPER=ccache
export
CCACHE_PREFIX=distcc
source
build
/envsteup
.sh
lunch XXX
kheader
time
make
-jN
若之前编译过一次,则同一shell环境下编译:make
-jN
只用distcc做分布式编译:
首次整体编译:
make
clean
export
WRAPPER=distcc
source
build
/envsteup
.sh
lunch XXX
kheader
time
make
-jN
其它多次编译:make
-jN
只用ccache编译缓存:
首次整体编译:
make
clean
export
WRAPPER=ccache
ccache -M 20G
source
build
/envsteup
.sh
lunch XXX
kheader
time
make
-jN
其它多次编译:
export
WRAPPER=ccache
source
build
/envsteup
.sh
lunch XXX
kheader
time
make
-jN
若之前编译过一次,则同一shell环境下编译:make
-jN