以前从未在意过makefile的执行效率,直到最近,由于需要将代码前前后后编译几十遍甚至上百遍,才发现原来的方式实在太慢。
以前的方式:
ALL ?=
default: END
target1:
cd . && make clean ready ....
ALL += target1
target2:
cd . && make clean ready .....
ALL += target2
很多的target
done:
@echo done
END: $(ALL) done
这样前前后后编译下来,天哪,竟然要2个小时,想想这样的操作需要3遍,基本上一天就没有了。这实在忍受不了。
make命令支持-j参数,可以并行执行,但上述模式是不可以的。由于调用make命令会将-j参数设置为默认的,因此在最外部调用-j参数无实质性作用
测试一下大致的目标,将所有生成的中间文件的命令完全的写入到一个临时文件中,并在原来的目标中生成临时文件,以便检查依赖性通过,然后在最后一步运行中间文件的命令时使用并行执行。这样时间大幅度的降低为40分钟左右。
但这样还是太慢,研究代码,发现时间在重新执行make命令时对makefile的解析上,那么,我们就要想办法让makefile解析的更快些,避免无意义的解析。这就用到了makefile中的eval函数。首先,定义一个模板:
define make_target_one
.PHONY $(1) $(1)_ready $(2) .o .s .c .cpp .obj
$(1): $(1)_ready $(2)
$(2): $(2).$(MODULE)
$(2).$(MODULE): $(OUTDIR)/$(LOCALOBJS)
生成$(2).$(MODULE)的指令
$(OUTDIR)%.o: %.c
$(CC) $(CFLAGS) $(OBJCMD) $$(@) $$<
ALL += $(1)
这样,如果我们需要新的目标,只用加入:
#设置好LOCALOBJS内容
LOCALOBJS :=
$(eval $(call make_target_one $(TARGET), $(DISTDIR)/$(TARGET)))
接下来测试,这下即使不使用并行执行也在40分钟以内,效果相比中间的临时测试也要好了,然后加上并行测试的选项,使用-j2,时间减半,只有20分钟了,对于并行执行的数量,超过CPU内核的话,意义就不明显了。
最明显的优化就是上面些了,我发现我原来的写法中有一个问题,经常使用find命令,大致如下:
LIBRARY_LIST := $(shell cd lib_dir && find . -name *.a)
LIBRARY_LIST :=$(foreach temp, $(LIBRARY_LIST), $(dirname $(temp)))
LIBRARY_LIST := $(shell echo $(LIBRARY_LIST) | sed -e 's/.\///g')
逻辑很简单,查找后缀.a的文件,然后取每一个的目录名,然后去掉之前的./,我想,find命令总是执行慢的,sed的这一步我也不想要,我想起了echo的自动展开方式,于是,变成了下面的方式:
LIBRARY_LIST := $(shell cd lib_dir && echo */*/*/*.a)
LIBRARY_LIST :=$(foreach temp, $(LIBRARY_LIST), $(dirname $(temp)))
echo要比find更高效,而且,没有之前讨厌的./的问题。这样,基本上满意些了。