大きいファイルを扱うならfile()なんか使っちゃいけません

【PHP TIPS】 82. コマンドを使ってスピードアップ

大きいファイルを扱うならfile()なんか使っちゃいけません。速い遅い以前の問題です。

本文終わり。以下蛇足。

ベンチマーク

------------------------------
30,000 lines
------------------------------
# -- time ./php_file.php 30k.txt
24640
0.38user 0.07system 0:00.49elapsed 93%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (0major+5505minor)pagefaults 0swaps

# -- time ./php_exec.php 30k.txt
24640
0.51user 0.04system 0:00.60elapsed 93%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (0major+2709minor)pagefaults 0swaps

# -- time ./php_file2.php 30k.txt
24640
0.32user 0.06system 0:00.39elapsed 97%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (0major+1691minor)pagefaults 0swaps

# -- time ./my_rb.rb 30k.txt
24640
0.11user 0.00system 0:00.11elapsed 100%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (0major+764minor)pagefaults 0swaps

# -- time ./my.sh 30k.txt
24640
0.22user 0.00system 0:00.23elapsed 96%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (0major+717minor)pagefaults 0swaps

↑3万行程度ならまだ比較対象になりうるけど、

------------------------------
300,000 lines
------------------------------
# -- time ./php_file.php 300k.txt

Fatal error: Allowed memory size of 33554432 bytes exhausted (tried to allocate                                         38623201 bytes) in /tmp/php_file.php on line 3

Call Stack:
    0.0002      57944   1. {main}() /tmp/php_file.php:0
    0.0002      57944   2. file() /tmp/php_file.php:3

Command exited with non-zero status 255
0.02user 0.00system 0:00.02elapsed 100%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (0major+1660minor)pagefaults 0swaps

# -- time ./php_exec.php 300k.txt
246400
2.13user 0.15system 0:02.46elapsed 92%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (0major+2708minor)pagefaults 0swaps

# -- time ./php_file2.php 300k.txt
246400
1.88user 0.69system 0:02.63elapsed 98%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (0major+1692minor)pagefaults 0swaps

# -- time ./my_rb.rb 300k.txt
246400
1.00user 0.00system 0:01.05elapsed 96%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (0major+794minor)pagefaults 0swaps

# -- time ./my.sh 300k.txt
246400
2.10user 0.15system 0:02.45elapsed 92%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (0major+717minor)pagefaults 0swaps

↑30万行になるとレース脱落。


------------------------------
3,000,000 lines
------------------------------
# -- time ./php_file.php 3m.txt

Fatal error: Allowed memory size of 33554432 bytes exhausted (tried to allocate 347608801 bytes) in /home/clover/tmp/php_file.php on line 3

Call Stack:
    0.0003      57940   1. {main}() /tmp/php_file.php:0
    0.0004      57940   2. file() /tmp/php_file.php:3

Command exited with non-zero status 255
0.02user 0.01system 0:00.04elapsed 89%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (0major+1663minor)pagefaults 0swaps

# -- time ./php_exec.php 3m.txt
2217600
19.76user 1.35system 0:22.80elapsed 92%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (0major+2712minor)pagefaults 0swaps

# -- time ./php_file2.php 3m.txt
2217600
15.74user 5.97system 0:24.57elapsed 88%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (0major+1689minor)pagefaults 0swaps

# -- time ./my_rb.rb 3m.txt
2217600
8.97user 0.30system 0:09.41elapsed 98%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (0major+893minor)pagefaults 0swaps

# -- time ./my.sh 3m.txt
2217600
19.25user 1.11system 0:20.76elapsed 98%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (0major+718minor)pagefaults 0swaps

↑300万でももちろん死ぬ。

これはfile()がファイルの中身を全部メモリに置くからそうなる。30万行、37MBものデータをPHPで一度に持とうとした瞬間にメモリオーバー。要するにfile()はスケールしない。

他のやりかただとその都度必要な分だけメモリに読み込むので、ログが何億行あろうとメモリがあふれることはない(タイムアウトの可能性とかはある)。

使用したソース

元のやつとは時間計測のしかたが違う。元のはPHPスクリプト内でのおはようからおやすみまで、上でやってるのはプロセスのおはようからおやすみまで。

php_file.php

#!/usr/bin/env php
<?php
$file = file($argv[1]); // ここで死ぬ予定
$counter = 0;

foreach ($file as $data) {
    if (preg_match('/02:[0-5][0-9]:[0-5][0-9]/', $data)) {
        $counter++;
    }
}

echo $counter."\n";

php_exec.php

#!/usr/bin/env php
<?php
$cmd = 'grep 02:[0-5][0-9]:[0-5][0-9] '.$argv[1].' | wc -l';
exec($cmd, $cnt);
echo $cnt[0]."\n";

php_file2.php

#!/usr/bin/env php
<?php
$counter = 0;

$fp=fopen($argv[1],"r");
while($line=fgets($fp)){
    if (preg_match('/02:[0-5][0-9]:[0-5][0-9]/', $line)) {
        $counter++;
    }
}
fclose($fp);

echo $counter."\n";

my_rb.rb

#!/usr/bin/env ruby
cnt=0
File.open(ARGV.shift){|fp|
    cnt += 1 if fp.gets.match(/02:[0-5][0-9]:[0-5][0-9]/) while !fp.eof?
}
puts cnt

my.sh

#!/bin/sh

grep "02:[0-5][0-9]:[0-5][0-9]" $1 | wc -l

レース結果

*php_file脱落

300万行だとRubyがぶっちぎりトップで、それ以外は横並び。3万と30万はばらついてるけど誤差かな。

ITProの記事を見る限り、php_execはphp_file2以上シェルスクリプト以下、って感じだと思ってたんだけどそうでもなかった。そもそも3万行のところでphp_fileに負けてるのが解せない。PHP CLIだからとか計測方法が違うからとか心当たりはあるけどどれもピンと来ないなあ。

Rubyがシェルスクリプトより速いのは意外だったけど、たぶんPerlとかPythonとかのほうが速い気がする。

結論

例えば、PHPを避ける。

PHPはApacheとイチャイチャしてなんぼだと思います。バッチ処理や解析は荷が重い。

php
名前

ほか