从半小时到21秒 —— 一个程序的优化

风行水上 @ 2014-01-13 17:26:03
标签:

    问题可以简单描述为递归处理一个目录下的所有文件:

    find $dir -type f -name "*.tcl" -exec tcltidy {} \;
    

    自己写了一个程序,用来处理Tcl文件,程序取名tcltidy

    同事写了个perl程序,通过调用system()函数执行tidytcl程序处理每个文件。说是一万来个文件花了一小时多点处理完。我回复说,还不错,不算太糟糕,可以达到实用价值。

    虽说如此,一个小时虽然对于实用不算太差,但还是太长了些。怎么办呢?

    先看了一下perl程序的代码,分离了一些代码,测试下来,那一个小时时间里,有约一半时间是用来copy原始文件了。这个其实不必要的,或者说不是每次都需要如此的。所以真正花在tcltidy上的时间是半个小时左右。

    parallel

    时间长,那就并行呗。想起自己最近听说的parallel命令,下载下来试用了一下:

    find $dir -type f -name "*.tcl" | parallel tcltidy
    

    所用机器有12个CPU,所以默认是并发执行12个进程,处理时间大概是5~6分钟。差不多是半小时的20%左右。

    试着通过-j64指定更多的并发数,可以进一步缩短时间,但没有惊喜。应该是受限于CPU的数目。

    当然可以进一步尝试分发到多台机器上去,从而获得更多的并发数。不过觉得在这里并不是很值得。

    parallel的一个很大的开销是进程的启动,这根传统的CGI所面临的问题是一样的。为了减少进程启动的次数,必须允许tcltidy自身能够处理多个文件。改动了tcltidy程序后,借助parallel的-X选项以每次传递尽可能多的参数。

    find $dir -type f -name "*.tcl" | parallel -X tcltidy
    

    还是12个CPU,12个并发进程,所用时间减少为30秒左右,差不多是半分钟的6%左右。

    这次的测试里,parallel给每个进程传递了大概1500个参数。

    显然频繁的启动进程是一个很大的开销。既然如此,如果不用parallel这样的工具呢?

    xargs

    xargs默认也是接受尽可能多的参数,这一点除了没有并发外,和使用-X选项的parallel是相同的。

    find $dir -type f -name "*.tcl" | xargs tcltidy
    

    执行时间,2分钟左右。

    find $dir -type f -name "*.tcl" | xargs -L 1000 tcltidy
    

    显示地让xargs每次传递1000个文件,时间比默认情况略多。

    find -exec

    回到问题的开始,find -exec花的时间也是半小时左右。这和同事的perl程序的用法应该是类似的,主要问题是进程的启动开销太大。

    Builtin Directory Scan

    给"tcltidy"程序自身加上了递归扫描目录以处理文件的功能。处理全部文件所用时间也是在2分钟左右。

    Output Buffer Size

    调整输出文件的缓冲区大小从默认值4K到40K(fconfigure stdout -buffersize 40960),程序运行时间从2分钟左右减少为1分钟半左右。

    这里的例子中,11450个文件总大小为5558928字节,单个文件大小的平均值近500KB,而实际上多数文件都在10KB以下,均值大是因为个别文件的大小有2MB甚至3MB。

    重新测试parallel -X的方案,时间减少为21秒左右。

    Next

    通过修改tcltidy让其自身可以接受多个参数,处理多个文件从而大大缩短了任务时间。但tcltidy自身还是按照顺序逐个处理文件的。

    是否可以让那1000多个文件并发处理(通过事件驱动或者线程),进一步提高效率呢?

    以研究的态度,值得一试。以使用的角度,半分钟或者两分钟已经够用了。

    TODO

    标签:

      分享到:
      comments powered by Disqus

      21/23ms