make学习笔记之优化makefile篇

2011-11-23 22:03 by hackerzhou

以前都只是用make,一直没好好研究makefile的写法。最近由于忍受不了每次3小时的编译过程,于是主动请缨对makefile进行了些优化来提高编译速度,试验了下效果很好,可以节省1-1.5小时。makefile可以简单的认为是make所识别的规则文件,描述了每个target之间的依赖关系以及编译方法,语法又和Shell Script类似,它也可以检查依赖关系,因此make被广泛的应用于编译源代码。写一些小项目的makefile都不会有太大的问题,而随着项目越来越大,其中的部件越来越多,编译过程越来越复杂。每个人都往里面加自己需要编译的东西,时间一长,理清楚各个makefile之间的调用以及依赖关系就变得越来越困难,因此每个小改动都得谨小慎微,改不好就是大灾难。很难想象一个脉络不清楚的体系能够有比较好的效率,本着不怕折腾的原则我这次就客串了下清道夫。由于是make的初学者,本文所述难免有所瑕疵,请各位多多包涵。

makefile通过指定target之间的依赖关系构造了一颗编译树,树的叶子节点最先编译,而后一层层向上,直到根节点被编译。因此,这棵树的构造方法会显著的影响到编译的效率。下面我们来看一个例子,看看一个简单的优化,假设你有两个子项目B和C,编译B和C都比较耗时而且它们互相不依赖,但它们都有一个共同的依赖项目A。
目录结构如下所示:

|-A
| |-makefile
|-B
| |-makefile
|-C
| |-makefile
|-makefile

B的makefile内容大致如下,C和A也类似,sleep时间分别为1s和10s:

all:
	@echo "Target B sleep 5s"
	sleep 5
	@echo "Target B complete"

一个未经优化的./makefile内容可能是:

all: A
	cd B && $(MAKE)
	cd C && $(MAKE)
	@echo "Complete"

A:
	cd A && $(MAKE)

.PHONY: A all

这样构造的makefile将会耗时(1+5+10)秒,make过程输出如下:

cd A && make
make[1]: Entering directory `/home/hackerzhou/test/A'
Target A sleep 1s
sleep 1
Target B complete
make[1]: Leaving directory `/home/hackerzhou/test/A'
cd B && make
make[1]: Entering directory `/home/hackerzhou/test/B'
Target B sleep 5s
sleep 5
Target B complete
make[1]: Leaving directory `/home/hackerzhou/test/B'
cd C && make
make[1]: Entering directory `/home/hackerzhou/test/C'
Target C sleep 10s
sleep 10
Target C complete
make[1]: Leaving directory `/home/hackerzhou/test/C'
Complete

大家可以发现,此时make的执行是顺序的,就算使用make –jobs=2也不能使用多job并行编译,究其原因是因为make的并行编译是依靠依赖关系来进行的,因此cd B && $(MAKE)和cd C && $(MAKE)两句不会被并行执行。于是我们就可以把该makefile改写成:

all: publish

publish: B C
	@echo "Complete"

C: A
	cd C && $(MAKE)

B: A
	cd B && $(MAKE)

A:
	cd A && $(MAKE)

.PHONY: A B C publish all

此时make编译的输出如下:

cd A && make
make[1]: Entering directory `/home/hackerzhou/test/A'
Target A sleep 1s
sleep 1
Target B complete
make[1]: Leaving directory `/home/hackerzhou/test/A'
cd B && make
cd C && make
make[1]: Entering directory `/home/hackerzhou/test/C'
make[1]: Entering directory `/home/hackerzhou/test/B'
Target C sleep 10s
sleep 10
Target B sleep 5s
sleep 5
Target B complete
make[1]: Leaving directory `/home/hackerzhou/test/B'
Target C complete
make[1]: Leaving directory `/home/hackerzhou/test/C'
Complete

可以看出稍稍修改一下就可以使用–jobs=2使得编译时间缩短到(10+1)s,B和C可以并行的完成,这只是我一个抛砖引玉的例子。

诚然通过–jobs来优化make编译也有其局限性:

  1. 比如–jobs的数量过大(超过cpu核的个数的时候性能就会直线下降)
  2. 子makefile不一定能很好的支持–jobs,可能也需要修改,会使得工程量变大
  3. 如果编译过程中牵涉到大量的IO操作,并发的IO会成为瓶颈使得性能反而更慢

因此,优化make的方法应该因地制宜,分析出哪里是瓶颈才能对症下药。

本文基于 署名 2.5 中国大陆 许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名 hackerzhou 并包含 原文链接
发表评论

本文有 1 条评论

  1. ArithXu
    2013-04-19 10:56

    受教了,今天回去学一下make的写法。哈哈

发表评论