perl在编辑巨大文件时的应用在实际中我们经常需要处理一些非常大的文件,这时会有两个问题我们比较关注:一个是处理程序的性能,希望越快越好;另一个就是空间的占用问题,不希望产生中间的文件,尤其是在有些空间非常紧张的磁盘上。 CODE:
# seq 10000 >file1
先说明一下,这里用lsof工具监视sed打开的文件,你也许需要su成为root才行。另外sed处理的文件不能太短,让lsof可以抓到。# sed -i ‘1,10d‘p file1|lsof -c sed COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME sed 14855 root cwd DIR 253,0 4096 5357570 /home/user1 sed 14855 root rtd DIR 253,0 4096 2 / sed 14855 root txt REG 253,0 52904 6012986 /bin/sed sed 14855 root mem REG 253,0 48508544 2559248 /usr/lib/locale/locale-archive sed 14855 root mem REG 253,0 21546 2589098 /usr/lib64/gconv/gconv-modules.cache sed 14855 root mem REG 253,0 182160 2589145 /usr/lib64/gconv/GB18030.so sed 14855 root mem REG 253,0 105080 3997927 /lib64/ld-2.3.4.so sed 14855 root mem REG 253,0 1489097 3997928 /lib64/tls/libc-2.3.4.so sed 14855 root 0u CHR 136,3 5 /dev/pts/3 sed 14855 root 1w FIFO 0,7 130511 pipe sed 14855 root 2u CHR 136,3 5 /dev/pts/3 sed 14855 root 3r REG 253,0 48894 5367617 /home/user1/file1 sed 14855 root 4u REG 253,0 28263 5367609 /home/user1/sed0cb2We 请看最后两行,倒数第二行是sed处理的目标文件,最后一行是... 哈哈,抓到了!sed偷偷地打开了一个文件。 让我们再看清楚一点: CODE:
# seq 1000000 >file1
这次处理的文件加大了,lsof每1秒钟采样一次。可以看到临时文件越来越大,最后接近原文件的大小。# sed -i ‘1,10d‘ file1|{ lsof -a +r 1 -c sed -d3,4;} COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME sed 16030 root 3r REG 253,0 6888894 5367609 /home/user1/file1 sed 16030 root 4u REG 253,0 31778 5367617 /home/user1/sedmXZuni ======= COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME sed 16030 root 3r REG 253,0 6888894 5367609 /home/user1/file1 sed 16030 root 4u REG 253,0 1613492 5367617 /home/user1/sedmXZuni ======= COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME sed 16030 root 3r REG 253,0 6888894 5367609 /home/user1/file1 sed 16030 root 4u REG 253,0 3285078 5367617 /home/user1/sedmXZuni ======= COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME sed 16030 root 3r REG 253,0 6888894 5367609 /home/user1/file1 sed 16030 root 4u REG 253,0 4959317 5367617 /home/user1/sedmXZuni ======= COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME sed 16030 root 3r REG 253,0 6888894 5367609 /home/user1/file1 sed 16030 root 4u REG 253,0 6631246 5367617 /home/user1/sedmXZuni ======= 如此我们可以推论:sed -i的处理过程是先将输出写入一个临时文件,然后自动将临时文件改名为原文件,--就像前面我们手工做的那样。 这样的话,用sed -i只是方便了一点,并没有空间占用上的优势。 那么是不是就非用C不可呢?别急,flw在本版曾给出过一个perl脚本(请参看:如何不需要更多的空间,去掉文件的首位注释行?),为了和上面的 例子对应,改写如下: CODE:
$ cat t.pl
这段代码用两个句柄打开要处理的文件,处理的结果写回原文件,最后截断文件的长度以适应处理后的结果。很明显这里没有用到任何中间文件。#!/usr/bin/perl $fn = shift; open R, "<$fn"; open W, "+<$fn"; while(<R>){ print W if $. > 10 } truncate( W, tell(W) ); 经测试,脚本工作得很好,而且性能比sed要高出一个量级。 CODE:
$ seq 100000 >file1
可以相信,性能的差距主要是临时文件的IO造成的,如果去掉-i选项,sed的性能会好很多,与perl在一个量级上:$ time ./t.pl file1 real 0m0.075s user 0m0.073s sys 0m0.002s $ seq 100000 >file1 $ time sed -i ‘1, 10d‘ file1 real 0m0.417s user 0m0.134s sys 0m0.283s CODE:
$ time sed ‘1, 10d‘ file1 >/dev/null
相应的perl代码的性能也相近:real 0m0.072s user 0m0.071s sys 0m0.001s CODE:
$ time perl -ne ‘print if $. > 10‘ file1 >/dev/null
由此可见flw的代码效率相当高,额外的磁盘IO很少。real 0m0.071s user 0m0.070s sys 0m0.001s 至此我们终于有一种方法解决了大文件的空间占用问题。似乎可以大功告成,收兵回营了。但是再等一下,flw的代码性能虽好,但还是稍微麻烦了一点。有没有性能又好编写又简单的方法呢? 上面我们已经讨论过sed的-i选项,我们知道perl也有-i选项,实际上GNU sed的-i选项应该是从perl借鉴过去的。既然sed -i性能很差,perl -i性能究竟如何呢?关键在于,它是否会使用中间文件--因为那会引入很多磁盘IO。我们来测试看看: CODE:
$seq 100000 >file1
不错!性能和flw的代码相差不大。这似乎说明没有中间文件,我们来验证一下:$ time perl -i -ne ‘print if $. > 10‘ file1 real 0m0.076s user 0m0.070s sys 0m0.006s CODE:
# perl -i -ne ‘print if $. > 10‘ file1|lsof -a -c perl -d0-9
如我们所料:perl打开了file1两次,没有中间文件。对比一下flw代码的情况:COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME perl 16279 root 0u CHR 136,3 5 /dev/pts/3 perl 16279 root 1w FIFO 0,7 136352 pipe perl 16279 root 2u CHR 136,3 5 /dev/pts/3 perl 16279 root 3r REG 253,0 6888602 5367617 /home/user1/file1 (deleted) perl 16279 root 4w REG 253,0 225280 5367609 /home/user1/file1 CODE:
# ./t.pl file1|lsof -a -c t.pl -d0-9
让我们再加大文件试试:COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME t.pl 16294 root 0u CHR 136,3 5 /dev/pts/3 t.pl 16294 root 1w FIFO 0,7 136475 pipe t.pl 16294 root 2u CHR 136,3 5 /dev/pts/3 t.pl 16294 root 3r REG 253,0 6888562 5367609 /home/user1/file1 t.pl 16294 root 4u REG 253,0 6888562 5367609 /home/user1/file1 CODE:
$ seq 10000000 >file1
如上,flw的代码sys时间较少,反映其IO耗时较少,但user时间稍长点,可能是在显式的while循环上吃亏的缘故。总的来看两者差距微小。$ time ./t.pl file1 real 0m7.810s user 0m7.524s sys 0m0.284s $ seq 10000000 >file1 $ time perl -i -ne ‘print if $. > 10‘ file1 real 0m7.825s user 0m7.189s sys 0m0.635s 至此,我们已经得到了perl处理大文件的两种方法,两者性能相差无几,但perl -i更加简单,可以写出漂亮的单行脚本--one liner,推荐大家优先使用。 |
|