質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.23%

php://temp/maxmemory に書き込むより、ローカルファイルに書き込んだ方が早い理由について

解決済

回答 3

投稿

  • 評価
  • クリップ 3
  • VIEW 5,819

al_aya_yuka

score 97

素朴な疑問です。
数百MBのCSVファイルをPHPで処理しています。
ファイルのエンコードがcp932でfgetcsvで誤作動するのでutf-8に変換しています。
ファイルそのものを変更する必要はないので、テンポラリに格納しようとしています。
そのテンポラリファイルが実ファイルより、メモリに書き込んだ方がはるかに遅いです。
想定ではメモリの方が比較にならないほど早いと思っていたのですが…
これはなぜなんでしょうか。
それとも扱い方が悪いのでしょうか。

csvのファイルサイズ: 約120MB~500MBほど
memory_limit:   システムの空きメモリを見て動的に設定(テストでは3GBほど)
TMP_FILE_MAX_MEMORY:  memory_limit の3/2

ご教授のほど、よろしくお願いします。

// 遅い!!
//$fp = fopen("php://temp/maxmemory:".TMP_FILE_MAX_MEMORY, 'r+b');
// 普通
$fp = fopen("C:/test.csv", 'w+b');

$tfp = fopen($csv_file, "rb");
while(($line=fgets($tfp))!==false){
    // この行をコメントアウトすると一瞬で終了
    // つまり、読み込みのオーバーヘッドは関係ないと思います
    fwrite($fp, mb_convert_encoding($line, 'UTF-8', 'sjis-win'));
}
fclose($tfp);
rewind($fp);

Windows7
PHP 5.6.0
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 3

+3

確信のある回答ではないことを、初めにお断りしておきます。

通常のファイルにfwriteする場合、(OS側で一定のバッファがあるとはいえ)書き出してしまえばメモリにデータは残りません。一方で、PHP側でテンポラリメモリに入れると、まさにその分だけPHP管理のメモリに入るわけで、しかもデータ量は行を追うごとに延びてきます。

つい最近どこかで見たのですが、PHPの実行時間のうち3割程度が動的メモリ操作で消費されているとのことで、この例のように「徐々に領域を延ばして100MB単位のメモリを消費する」ような使い方は、ただでさえ処理の重いメモリマネージャに必要以上の負担をかけてしまっているのだと思います(なお、PHP 7で改善予定ではありますが)。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2014/11/28 12:57

    回答有難うございます(*^_^*)

    すごく納得の行く内容でした。
    でもそうなってくるとphp://memory はどこで威力を発揮できるのでしょうね。。。

    キャンセル

checkベストアンサー

+2

直接の回答では無いですが

ファイルのエンコードがcp932でfgetcsvで誤作動するのでutf-8に変換しています。

このアプローチ自体が誤っていると思います。以下のようにロケール設定をすることで正しい読み出しが可能です。

【php】fgetcsv()はロケールの設定に依存する

setlocale(LC_ALL, 'ja_JP.sjis');
$fp = fopen('C:\test.csv', 'rb');
while ($row = fgetcsv($fp)) {
    mb_convert_variables('UTF-8', 'SJIS-win', $row);
    // $rowで何かする
}

…しかし、この方法だと結局何回も mb_convert_variables 関数を呼び出すことになってオーバーヘッドが大きいので、以下のようにストリームフィルタを使う方が理想的かもしれません。こっちだとOSに依存もしないので、移植性の面からも優れていると言えると思います。

ストリームフィルタで文字コード変換してみる

$fp = fopen('C:\test.csv', 'rb');
stream_filter_prepend($fp, 'convert.iconv.cp932/utf-8', STREAM_FILTER_READ);
while ($row = fgetcsv($fp)) {
    // $rowで何かする
}

もしテンポラリファイルを使うにしても、tmpfile() 関数を使うのがおすすめで

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2014/11/28 13:04

    間違えて、BAを押してしまいました。。。

    ロケール依存の話は多少知識がありまして、仰るとおりなのですが、それではいわゆる5C問題が解決できなかったのです。
    CSVのデータは「表」「貼」「ソ」を含んでおり、その行自体がおかしなことになってしまうのです。

    しかしながらmb_convert_variablesやストリームフィルタは知らなかったことなので、実践してみようと思います。
    ありがとうございました。

    キャンセル

0

気になったので試してみた感じ php://memory などが異様に遅くなるのは Windows 特有のようです。

下記はいずれも PHP 5.6.3 で、CentOS 6 は remi-56 のバイナリ、Windows 7 は公式の x64 nts のバイナリで、-n オプション付きで実行しています。

(CentOS 6 はディスクイメージが iSCSI の先にある HDD 上のファイルな仮想環境なので I/O が普通より遅いかもです )

## 変数に追記

<?php
$m = microtime(true);

$a = "";
for ($i=0; $i<100; $i++) {
    $a .= str_repeat("x", 1024*1024);
}
unset($a);

var_dump(microtime(true) - $m);
var_dump(memory_get_peak_usage());

// CentOS 6
// float(0.051472187042236)
// int(106134088)

// Windows 7
// float(3.6982111930847)
// int(106144824)

## php://memory に追記

<?php
$m = microtime(true);

$fp = fopen("php://memory", "r+b");
for ($i=0; $i<100; $i++) {
    fwrite($fp, str_repeat("x", 1024*1024));
}
fclose($fp);
unset($fp);

var_dump(microtime(true) - $m);
var_dump(memory_get_peak_usage());

// CentOS 6
// float(0.049097061157227)
// int(106135176)

// Windows 7
// float(14.518830060959)
// int(106146232)

## HDD上のファイルに追記

<?php
$m = microtime(true);

$fp = tmpfile();
for ($i=0; $i<100; $i++) {
    fwrite($fp, str_repeat("x", 1024*1024));
}
fclose($fp);
unset($fp);

var_dump(microtime(true) - $m);
var_dump(memory_get_peak_usage());

// CentOS 6
// float(1.1221830844879)
// int(1277448)

// Windows 7
// float(0.20701193809509)
// int(1288560)

## tmpfs上のファイルに追記(CentOS のみ)

<?php
$m = microtime(true);

$fn = tempnam("/dev/shm/", "xxx");
$fp = fopen($fn, "r+b");
for ($i=0; $i<100; $i++) {
    fwrite($fp, str_repeat("x", 1024*1024));
}
fclose($fp);
unset($fp);
unlink($fn);

var_dump(microtime(true) - $m);
var_dump(memory_get_peak_usage());

// CentOS 6
// float(0.046942949295044)
// int(1278472)

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.23%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る

関連ワード: php://temp

  • トップ
  • PHPに関する質問
  • php://temp/maxmemory に書き込むより、ローカルファイルに書き込んだ方が早い理由について