当前位置: 代码迷 >> Android >> Android基于distcc的分布式编译及负载均衡的兑现
  详细解决方案

Android基于distcc的分布式编译及负载均衡的兑现

热度:29   发布时间:2016-05-01 13:32:47.0
Android基于distcc的分布式编译及负载均衡的实现

distcc简介

distcc

distcc源起于著名开源项目samba,是一款有着较长历史的跨平台开源分布式编译解决方案。

对于大多数c语言及其衍生语言来说,编译过程主要分为三个步骤:

  1. 预编译
  2. 编译
  3. 链接

distcc的作用就是将第二步的编译(3.0版本后通过pump支持部分第一步)过程采用网格计算的模式,将编译任务分配至其它主机,并在编译结束后回传,以供第三步链接使用。并由此降低了发起编译的机器的负载,并提升了编译效率。

distcc本身事实上并不参与任何编译过程,而只是一个编译器(gcc等)的前端。为编译器加入分布式特性,并参与部分管理和简单的负载均衡的功能。根据官方说法,其性能提升的极限阈值是3倍速度,即根据编译池的扩大,无限接近但无法达到原始编译速度的三分之一。

截至目前为止,distcc仅支持c语言及其衍生的c++,Objective-c等的编译。无法支持Java的分布式编译。

pump

自distcc版本3.0开始,加入了基于python的新工具pump。其功能是将头文件也随同源码一起发送至编译服务器。将部分预编译工作也进行分布式处理。从而进一步的提升了编译效率。官方给出的数字是对于文件传输、编译过程有10倍的效率提升。

但pump模式的局限之处在于:在整个编译过程中,源代码(尤其是头文件)不能进行改动,否则会造成错误的编译结果。

ccache

ccache同样产生于samba项目,其作用是将编译过程中的中间文件根据预编译结果,通过hash表索引进行缓存。Ccache具有以下特性:

?

  1. 在命中/缺失上是静态的
  2. 自动的缓存大小管理
  3. 可以缓存产生的编译警告
  4. 容易安装
  5. 很低的开销
  6. 可以使用硬联接来避免复制与distcc不同,ccache是android官方直接支持的编译加速方案。

dmucs

dmucs是为distcc服务的分布式负载均衡解决方案。原始的distcc仅根据服务器指定的顺序来分配编译任务,无法根据编译池中服务器的负载情况进行动态的编译任务分配。而dmucs则会将编译池中主机的负载情况通知到dmucs服务主机。并由其根据负载及编译机承担能力,动态的对编译任务进行分配。

?

基准测试结果及分析

构建结果分析

图1展示了不同编译条件下的编译时间。其中,单机的编译参数使用了-j8, 而双机、三机的编译参数则分别为: -j12,-j15

基准测试结果

?

由此图,大致可以总结出以下结论:

  1. distcc对android系统编译起到显著的加速作用,编译时间随着编译池中编译机器数目的增加而基本呈线性下降趋势。因为暂时无法获得更多的主机进行测试。故无法获知边界情况。
  2. 分布式编译的效果在android 4.0的编译过程中体现相对于 2.3更为明显。
  3. pump工具在编译过程中起到的速度提升作用并不明显。估计是因为android的预编译过程牵涉了大量的头文件和依赖关系,分布式得到的速度提升被文件传输过程所抵消。
  4. ccache的加入极大的改善了编译时间。对ccache的使用命中率,针对2.3.7平台,统计结果如下:
    cache hit 878cache miss 11494called for link 691not a C/C++ file 307unsupported compiler option 48files in cache 22988cache size 1.0 Gbytesmax cache size 20.0 Gbytes

    ccache 初次编译 2.3.7 统计结果

    cache hit 12127cache miss 16called for link 691not a C/C++ file 307unsupported compiler option 48files in cache 23020cache size 1.0 Gbytesmax cache size 20.0 Gbytes

    ccache 二次编译 2.3.7 统计结果

    由此可见,在第二次编译时(代码未改动),cache的命中率超过了90%。而速度也因此得到大幅度的提升,基本接近初始速度的三倍。对android 4.0版本的速度提升尤为显著。但此数据对平时开发过程中代码有变化的情况仅能起到参考作用。

  5. 在测试过程中,dmucs的加入并未在时间上有效的缩短单编译请求的时间。原因是因为java/c的混合编译,无法根据distcc的推荐方法增加编译job的数量,也就无法充分体现分布式编译均衡的优势。但根据编译时观察,dmucs确实能有效的控制编译池中编译机器的负载,并根据编译能力进行平均分配。
  6. 此外,在图表中未能体现的一个结果为:发起编译的客户机平均负载在采用了分布式编译之后,在c类语言编译阶段,均有较为可观的降低。

?

图2展示了在三机分布式条件下编译android 4.0代码,job数目与编译时间的大致关系。

分布式条件下增加Job数目对编译时间的影响


由此可知,单纯增加job数目并不能获取更好的编译时间。具体原因如下述。

存在的问题及后续解决方案的方向

由于android系统的编译过程中同时存在java的编译和c的编译,而distcc又仅支持c类语言的分布式编译。故对于android的分布式编译获得的效果不如普通纯c类语言项目明显。随着jobs数目的增加,仅能在本机处理的java编译过程反而由于机器硬件机能的限制而拖慢了整体的编译速度,甚至抵消了由distcc分布式编译获取的益处。

为了解决这个问题,可从以下几个方面入手,进行进一步的改造:

  1. 分割c类语言的编译过程:将整体android编译过程划分为c语言为主和java为主的两个过程,其中c语言的编译过程采用distcc编译,而java语言的编译则因循原来的编译方式。 此方法涉及android编译脚本的修改,实现较为简单。
  2. 修改make的实现,根据调用java/c编译器的不同,动态的调整作业数目。此改动可能涉及编译脚本修改甚至make源代码修改,实现稍微复杂。
  3. 将java编译过程同样进行分布式处理。可能的方式是在不同的主机上对不同的java项目进行编译。但需要物理的共享存储以及编译脚本修改。实现更为复杂。但仍在可行的范围内。

分布式负载均衡编译环境部署i

为支持分布式编译,单一编译主机需要进行以下部署步骤:

  1. 安装支持包:?编译主机需要安装distcc、distcc-pump、dmucs包
    1
    apt-get install distcc distcc-pump dumcs
  2. 部署交叉编译工具链至特定位置:
    将android源码自带的prebuilt目录复制到固定地点,比如:/opt/。
  3. 修改distcc的配置文件:
    修改/etc/default/distcc,将
    STARTDISTCC设置为true;
    ALLOWEDNETS为连接的客户端范围,按需设置;
    LISTENER为侦听地址,设置为本机ip地址;
    JOB为同时可承担的编译任务数目,一般设置为CPU个数+2。
    修改/etc/init.d/distcc,将
    PATH参数加入步骤2的交叉编译工具链编译器位置,以使distcc在接到编译任务时,能找到对应的编译器。
  4. 启动distcc服务:?sudo service distcc start

?

发出编译请求的客户端还需要进行如下部署步骤:

?

  1. 创建交叉编译工具至distcc的链接:
    将交叉编译工具全部链接至distcc:

    ?

    cd /usr/lib/distccln -s ../../bin/distcc arm-eabi-addr2lineln -s ../../bin/distcc arm-eabi-arln -s ../../bin/distcc arm-eabi-asln -s ../../bin/distcc arm-eabi-c++ln -s ../../bin/distcc arm-eabi-c++filtln -s ../../bin/distcc arm-eabi-cpp……
  2. 修改android编译脚本,使其支持distcc编译:
    将build/core/combo/TARGET_linux-arm.mk中的TARGET_TOOLS_PREFIX变量改为/usr/lib/distcc/arm-eabi-
    修改build/core/definition.mk, 修改transform-d-to-p为:

    ?

    define transform-d-to-p$(hide) if [ -e $(@:%.o=%.d) ];then cp $(@:%.o=%.d) $(@:%.o=%.P); \	sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \		-e '/^$$/ d' -e 's/$$/ :/' < $(@:%.o=%.d) >> $(@:%.o=%.P); \	rm -f $(@:%.o=%.d);else cp $(notdir $(@:%.o=%.d)) $(@:%.o=%.P); \	sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \		-e '/^$$/ d' -e 's/$$/ :/' < $(notdir $(@:%.o=%.d)) >> $(@:%.o=%.P); \	rm -f $(notdir $(@:%.o=%.d));fiendef
  3. 修改环境变量:
    修改PATH,将distcc编译器路径加入PATH的第一个,并加入交叉编译器路径:
    export PATH=/usr/lib/distcc:<上述第二步交叉编译工具链位置>:$PATH
    此命令可写入profile。
    修改DISTCC_HOSTS,指明支持分布式编译的主机地址:
    export DISTCC_HOSTS=’localhost … … ‘
  4. 发起编译过程:
    make -j8 CC=distcc 根据编译池主机数和本机资源情况调整job数目,以获取最佳性能。

与ccache配合使用

因android官方已经在构建环境中加入了ccache支持,故可以很简单的打开ccache功能以提高编译效率。具体方法为:

$ export USE_CCACHE=1$ export CCACHE_DIR=/path_of_your_choice/.ccache$ prebuilt/linux-x86/ccache/ccache -M 20G

其中,20G为推荐值,事实上可以根据编译机器的具体情况进行调整。

在编译期或编译结束后,可以用“ccache -s”命令查看缓存命中情况。

用dmucs实现负载均衡

选中某台主机作为dmucs服务器。进行配置:

  1. 修改/etc/default/dmucs,启用dmucs:
    将SERVER改为yes
  2. 修改/etc/dmucs.conf?文件,配置编译池:
    根据文件原有说明格式,加入编译池中的主机。
  3. 启动dmucs服务:
    service start dmucs
  4. 在每个编译主机上执行负载发布客户端:
    loadavg -s 主机地址 &

此时,即可以在发起编译时使用

make -j8 CC=gethost distcc

来启用支持负载均衡的分布式编译过程了。

?

FAQ

  1. 执行pump时报python错误这是一个已知的bug,解决方法参见https://bugs.launchpad.net/distcc/+bug/511585
  2. 编译时pump报主机不支持cpp
    在创建环境变量时需要指明主机支持的编译类型如:
    export DISTCC_HOSTS=’localhost compilehost,lzo,cpp’

参考文献

  1. distcc官方网站:http://code.google.com/p/distcc/
  2. distcc官方文档:http://distcc.googlecode.com/svn/trunk/doc/web/index.html
  3. 《分布式编译环境中的负载均衡 》:http://www.ibm.com/developerworks/cn/aix/library/0905_yangyi_distcc/index.html
  4. dmucs官方网站:http://dmucs.sourceforge.net
  相关解决方案