PHPで、CSVファイル数が大体4万ファイルくらい

ある場合に、CSVのファイルからレコードを
取得する際、以下のソースを書いてます。

$time_dirという配列の中に、ディレクトリ名の絶対パスをもった
値が入っている。
その絶対パスのディレクトリ内にある、CSVファイルを取得して、
$data_listという配列に、格納する。
この時に、24000ファイルを読み込むときに大体、1分くらい処理が
掛かるのですが、改善方法などあるでしょうか?
$data_list = array();
foreach($time_dir as $key => $val){
if (!($dir = opendir("$val"))) {
die;
}
while ($file_name_csv = readdir($dir)) {
if (ereg('.csv', $file_name_csv)) {
$fp = fopen("$val/$file_name_csv", 'r');
$line = fgets($fp);
// 読み込んだ行を出力する
list($wirte_time,$test_id, $test_num,$test_date,$ins_date) = explode("\t",$line);
$data_list[] = array('test_id' => $test_id,'test_num' => $test_num,'test_date' => $test_date);
// ファイルをクローズする
fclose($fp);
}
}
closedir($dir);

回答の条件
  • 1人5回まで
  • 登録:2006/09/13 11:11:55
  • 終了:2006/09/15 12:14:12

回答(4件)

id:Mook No.1

Mook回答回数1312ベストアンサー獲得回数3912006/09/13 12:06:41

ポイント23pt

http://q.hatena.ne.jp/1158058845

と同じ件の質問かと思いますが、こちらの方が情報が詳細なので、以降、こちらで回答いたします。


まず、PHP のように動的に情報を扱う仕組みで、40000件もの多くのファイルを扱うという方法そのものに問題があるように感じます。


データベースを利用したり、事前にファイルを一つに統合したりするということはできないのでしょうか。


4万件のファイルが動的に変化するというのであれば、データベースの利用を検討されたほうが良いと思いますが、データが変わらないというのであれば、1ファイル(あるいは意味のある分類が可能であれば、数ファイル)に統合してはどうかと思います。


OS が Linux か Windows かといった情報もあると、そのための具体的な回答もできるのですが。

id:hopefully

OS が Linux です。

データベースは利用出来ません。

事前にファイルを結合って事は

tarとかで結合するんでしょうか?

2006/09/13 12:24:19
id:Mook No.2

Mook回答回数1312ベストアンサー獲得回数3912006/09/13 12:48:47

ポイント23pt

Linux であれば、簡単なスクリプトで結合できると思います。

情報は、ファイル名には依存していないと考えてよいのでしょうか。


tar では、csv ファイルとして機能しなくなってしまうので、コマンドプロンプトで

for file in (`ls *.csv`)

do

cat $file >> addcsv.txt

done

mv addcsv.txt data.csv

のような感じで一つのファイルになります。

シェルによって若干記述が異なるので動かない場合、お使いのシェルがわかればアドバイスもできます。

id:hopefully

これをPHPのスクリプトで書くとどうなるんでしょうか?

2006/09/13 13:22:34
id:Mook No.3

Mook回答回数1312ベストアンサー獲得回数3912006/09/13 14:21:20

ポイント22pt

まず、処理を行う PHP とデータとしての CSV を分離して考えた方が良いと思います。


上記は、Linux 上のシェルで行う処理で、これによってdata.csv というファイルができます。これを使用して、PHP側では、

<?php

echo "<table border=\"1\">\n";

echo "<tr>\n";

echo "<td>ID</td><td>Number</td><td>Data</td>\n";

echo "</tr>\n";

$fp = fopen($time_dir."/data.csv", 'r');

while( list($id, $num, $data) = fgetcsv( $fp, 100, "," ) ) {

    echo "<tr>\n";

    echo "<td>$id</td>\n";

    echo "<td>$num</td>\n";

    echo "<td>$data</td>\n";

    echo "</tr>\n";

}

echo "</table>";

fclose($fp);

?>

のような感じでしょうか。


スクリプト中では、このファイルだけを検索することになるので、ファイル I/O の点で改善が見込めます。

これで不十分な場合は、Linux の grep コマンド等を使用して、読み込む前にデータを処理した方が良いかもしれません。

(あまりお勧めしませんが。)

id:hopefully

なるほど、遅くなってしまい、申し訳ありません。

ありがとうございます。

2006/09/15 12:12:45
id:a_b_y No.4

a_b_y回答回数16ベストアンサー獲得回数62006/09/13 22:56:34

ポイント22pt

ディスクIOがボトルネックであるならば、1ファイルあたり約100バイト×数万ファイル程度のサイズであれば、 Linux なら tmpfs を使う手があります。

http://www.atmarkit.co.jp/flinux/rensai/linuxtips/277usetmpfs.ht... などに倣って (このページ色々と物議を醸したこともありますが今回の件には問題ないので参照します) 例えば

# mount -t tmpfs -o size=24m tmpfs /cvsdir

としてから /cvsdir に全てのファイルをコピーし、PHP からはそちらのファイルを読み込むようにする方法です。これなら(スワップアウトしない限り)ディスクIOは発生しないので入出力に関してはかなりの高速化が期待できます。もちろんこの場合にも、データを多数のファイルに分けておくより少数のファイルに統合しておく方がより高速になります。ここまでの流れで考えると、IOに関してこれ以上に劇的な高速化の方法はないと思います。一つの欠点は、今後もデータの容量が増大しメモリが足りなくなってしまうと、この方法は破綻することでしょうか。

注意点は、tmpfsについて御存知ならこれは蛇足ですが、tmpfsは揮発性なので再起動などをすると内容が消えてしまうことです。ですから、サービス起動時にHDDからtmpfsにコピーを行ない、サービス終了時にはtmpfsの内容をHDDに退避させる必要があります。

id:hopefully

やはり多いファイル数を扱うには

サーバ側のコマンド等で一旦まとめたり

しないといけないんですね。

ありがとうございます。

2006/09/15 12:14:01
  • id:a_b_y
    # 回答しようかと思っていたけど, 考えている間に回答状況が変わっていくのでとりあえずコメントにて. (コメントってプレビューできない&はてな記法使えないんですね…^_^; 貼り直し)

    私もデータベースを導入するのが一番よい方法だと思っていたのですが
    >> データベースは利用出来ません
    とのことなので, その方向の解答を導く方針で.

    まず蛇足ですが
    >> これをPHPのスクリプトで書くとどうなるんでしょうか?
    Mook さんのスクリプトを実行するだけであれば
    system("for file in (`ls *.csv`); do cat $file >> addcsv.txt; done; mv addcsv.txt data.csv);
    とでもすればよいでしょうか. (私は PHP を知らないので間違っていたらすみません.) ただ, 念のためですが, これを質問文にあるスクリプトのどこかに挿入するのでは意味がない所か, かえって遅くなります. ディスクIOを余計に増やすだけですから. (ちなみに, このスクリプトをそのまま使うのは問題ありですね. ファイルが数万個以上ある状況では処理系によってはうまく動かない可能性がありますし, なにより2回目以降の実行では data.csv が *.csv にマッチしてしまいますから.)

    http://q.hatena.ne.jp/1158058845 から見ていて思うことですが, 肝心な情報がまだ提示されていないように思います.
    1. CSVデータの更新・追加などを行うプロセスと, 質問にあるプログラムのようにデータを参照するプロセスは独立なのかどうか.
    2. データの更新や参照などの頻度はどのようになっているのか.
    a. CSVデータ(群)は頻繁に更新されるが, データの参照はまれ
    b. CSVデータの更新頻度はそれほどでもないが, データの参照・処理が頻繁に起こる
    c. どちらも高い頻度で起こる
    3. データに対してどのような処理を行うのか
    状況によって, (開発効率・実行速度などの点で)適切な対処法は変わってくるはずですから, この辺を明確にした方が適切な回答が得られると思います. (実はファイルIOではなくその後の処理がボトルネックということはないですよね?)

    さて, Mook さんの回答に書かれている内容の肝は(誤読していたらすみません Mook さん), まず読み込むファイルの数を少なくできないかということです. Fileのopen/closeというのは結構重い処理ですから, 数万のファイルを一々開けては閉じというのは非効率です. さらに, まとめたファイルの情報をバイナリ形式で保存しておくことは高速化につながります. CSVファイルは汎用性の高い形式ではありますが, いかんせんテキスト形式なので, 入出力の効率は決っして良くないですから. 以前研究で行っていたシミュレーションで, バイナリで100MB以上のデータを最初はテキストで保存してから(テキストだと数百MBからGB単位になる)処理していたものの, さすがにIOにかかる時間を無視できないためバイナリに変更し数十パーセント改善したことがあります.
    ただ, この方針を取るとすると当然, どのタイミングでどのようにバイナリファイルを作成するのか, といった問題が出てきます. これは上記の状況による話で, どのようなシステム・運用なのかが質問文からは伺い知れないため現時点で私には回答できません.

    データ構造の工夫なども行った上でどうしてもディスクIOがボトルネックになるなら, データをメモリ空間に保持するサーバを構築し, プログラムはそのサーバと交信する形をとることになると思いますが, これを突き詰めると結局何らかのデータベースシステムを導入・開発するとなってしまうわけでして…. 「データベースは利用出来ません。」とのことですが, 何故なのかの説明もあると回答しやすくなるかも.
  • id:Mook
    コメントを書ける設定になっていたんですね。

    3回目の回答で、運用に関して触れたのですが少し不親切だったと思いましたので、コメントで情報を追加します。
    3回目の回答で提示したサンプルを4万行からのCSVファイルで実行すると、まともに表示できるとは思えませんので(説明不足でゴメンナサイ)、まずは数十行程度のファイルで試してみてください。

    実際には CSV を読み込む部分で、条件にあったものだけを表示するようにしてあげれば、目的を達成できるかと思います。
    ただし、毎回 CSV ファイルをフルスキャンすることになるので、効率としては、非常に悪いものになります。

    これに関しては、a_b_yさんのご推察の通り、上記の方法は PHP が動的に作成するのではなく、準備として一度だけ実行することを想定しています。
    なので、a_b_yさんのコメント中でも触れられていますが、個々のデータファイルが頻繁に更新されるような環境では、今回の回答は適切なものではありません。

    一刀両断されましたが、もしデータベースを導入できれば、このあたりの問題が一挙に解決します。データの読み込みの部分は SQL のクエリに置き換えることができるので、パフォーマンスやデータ管理の点からも、お勧めです。

    バイナリでのデータファイルに関しては、PHP での知見が私にはありませんので、興味があるようでしたら他の回答者の回答をお待ちください。
  • id:a_b_y
    >>4 について補足です。
    root 権限を持っているならおそらくこれが最も有効な高速化法です(ただtmpfsにするだけなので)が、 root 権限がない場合には管理者と直接交渉して許可を得ない限りほぼ間違いなく実践できません。 データベースが導入不可ということはレンタルサーバーを利用されていたりして root 権限もないのかな、と回答後に推察していたりします…
    もし現状でどこかの(機能不足な)レンタルサーバーをご利用なのなら、これだけの数のファイルを扱う事態なのですからいっそサーバーの移転を考えるのも手かと思います。昨今は機能が充実しつつ(データベースシステムなども込みで)安価なレンタルサーバーもけっこうありますので。

この質問への反応(ブックマークコメント)

「あの人に答えてほしい」「この質問はあの人が答えられそう」というときに、回答リクエストを送ってみてましょう。

これ以上回答リクエストを送信することはできません。制限について

絞り込み :
はてなココの「ともだち」を表示します。
回答リクエストを送信したユーザーはいません