パスワードを忘れた? アカウント作成
11982168 story
プログラミング

メモリー上で処理するよりもディスクに直接書き込む方が高速? 86

ストーリー by headless
効率 部門より
ソフトウェア開発者の間では一般的に、ディスクアクセスを避けてできるだけメモリー上で処理することが実行時間の短縮につながると考えられているが、これに逆行する研究結果をカナダ・カルガリー大学とブリティッシュコロンビア大学の研究チームが発表した(論文: PDFITworldの記事本家/.)。

実験はJavaおよびPythonを使い、WindowsおよびLinux上で1バイト、10バイト、1,000バイトの文字列をそれぞれ100万バイトになるまで繰り返し結合し、結果をディスクに書き込むというもの。比較対象としては同じく1バイト、10バイト、1,000バイトの文字列を結合せずに計100万バイトになるまで直接ディスクに書き込んでいる。また、同じコードにより100万バイトの文字列を処理する実験も行っている。

その結果、Javaで1バイトの文字列を結合処理してからディスクに書き込んだ場合、ディスクへの直接書き込みと比べて約9,000倍の時間がかかったという。PythonではJavaほどの速度低下は見られなかったものの、直接書き込みの方がメモリー上での処理よりも数百倍高速だったとのこと。また、Linux上で実行したPythonのコードでは、元の文字列に新しい文字列を結合する方が新しい文字列に元の文字列を結合するよりも高速だったとしている。

論文ではこのような結果になった原因として、OSによるバッファリングがあるためにディスクへ直接書き込んでも速度がそれほど低下しないと指摘。また、OSのメモリー管理がメモリー上での処理を低下させる原因になる可能性もあるとし、開発者はOSやライブラリーなどについてより多くの知識を持つことでパフォーマンスを改善できるなどと結論付けている。ただし、論文の最後に掲載されているコードを見ればわかるように、文字列の結合には「+=」または「+」を使用しており、効率の良い処理を選択しているとはいえない。論文には「JavaではStringBuilderやStringBufferといったミュータブルなデータ型を使用すれば結果が大幅に改善する」といった記述もある。本家/.では結論に合わせた結果が出るように実験したのではないかとの指摘もみられるが、皆さんはどう思われるだろうか。
この議論は賞味期限が切れたので、アーカイブ化されています。 新たにコメントを付けることはできません。
  • ソースはarXiv (スコア:5, 参考になる)

    by Anonymous Coward on 2015年03月28日 18時25分 (#2786501)

    こんな釣りが査読を通ったの? と思ったらarXivでした。
    しかも、1st authorが生物学で、last authorが化学生物工学。何がどうしてこうなったのか教えてほしい。

    • Re:ソースはarXiv (スコア:3, おもしろおかしい)

      by Anonymous Coward on 2015年03月28日 19時06分 (#2786521)

      たぶんセンサーから得たデータをCSVで書き込む時かなんかに大発見しちゃったのでは?

      親コメント
      • by Anonymous Coward

        そんな感じでしょうね。
        書き出し処理が遅いや、メモリ上で処理するように書き換えよう→あれ? なんだか余計に遅くなったぞー、とか。

        文字列結合なんて、適当にやってると、運悪くパフォーマンスの悪いやり方を踏むかどうかでかなり差が出る激戦区。

    • by Anonymous Coward

      ソースがarXivでも問題ないし、専門が生物学や化学生物工学の著者が別の分野で論文書いても問題ない。
      この論文が眉唾なのは他のコメントでも指摘されてるとおりだけど、問題なのは論文の内容の妥当性であって、
      arXivだとか著者の専門がどうとかいうことじゃないだろう。それじゃ低レベルな野次だ。

      • by Anonymous Coward

        まあ正論なんだが。しかしこの内容を見るとarXivから拾ってきたものを論文と言うなと言いたくなるわな。
        論文というからにはある程度ちゃんとした査読プロセスを経ていないと。

  • Java版のコード見たら (スコア:5, すばらしい洞察)

    by Junos (14052) on 2015年03月28日 19時15分 (#2786524) ホームページ 日記

    ディスク書き込みにBufferedWriterクラスを使ってました。なのでJava版に関しては「直接ディスクに書き込んでいる」わけではありません。

    • by Anonymous Coward on 2015年03月28日 20時16分 (#2786553)

      C 言語的にいうとこんな感じでしょうか。だとすれば後者の方が断然速いですね。(ちょっとコードが意地悪かな)

      #define MAX 1000000

      void memory()
      {
        unsigned char* ptr;
        size_t i;
        FILE* f;

        ptr = NULL;

        for (i = 0; i MAX; i++) {
          ptr = realloc(ptr, i + 1);
          ptr[i] = '1';
        }

        f = fopen("tstm", "wb");
        fwrite(ptr, 1, MAX, f);
        fclose(f);

        free(ptr);
      }

      void disk()
      {
        unsigned char buf[8096];
        FILE* f;
        size_t i, c;

        f = fopen("tstd", "wb");

        for (i = 0, c = 0; i MAX; i++, c++) {
          if (c == sizeof(buf)) {
            fwrite(buf, 1, c, f);
            c = 0;
          }
          buf[c] = '1';
        }

        fwrite(buf, 1, c, f);
        fclose(f);
      }

      親コメント
    • https://gist.github.com/taku0/e68851bf357dda0f989c [github.com]

      実際測ってみました。100万文字を書き込んでいます。

      FileWriter: 約50 ms
      BufferedWriter + FileWriter: 最初のうちは約30 ms、120回目ぐらいからJITが効いて13 ms
      StringBuilder + FileWriter: 14 ms

      CPU: Core i7 4500U
      ストレージ: CFD販売 CSSD-M256HLHG5Q
      OS: Linux 3.14.35 x86_64
      JVM: Oracle JVM 1.8.0_40, server VM

      途中sleep入れてるのはCPUが熱くなって遅くなるのを防ぐためです(本当は設定を変えてTurbo Boost等を無効にするのが正しいのですが手抜きです)。

      親コメント
  • by vax730 (32985) on 2015年03月28日 18時01分 (#2786491)

     文字列にあわせて新しい領域をとるために遅いことはわかりますが、Javaのほうが遅いというのは、ヒープ領域が小さすぎて、
    GCが多数回動いているからでしょうか?

    • by t-wata (10969) on 2015年03月28日 21時33分 (#2786593) 日記

      コード見ればわかりますが、毎回書き込みはバッファリング効かせてるんでシステムコールの回数はかなり抑えられてます。
      一方でメモリ上で結合の方は、Stringで+を使っての結合なので、内部では new StringBuffer(orig).append(str).toString()が毎回走るので遅くて当たり前です。
      GC以前に「そりゃそうなるよな」としか言いようがないです。

      親コメント
    • by Anonymous Coward

      今回の結果は極端にしても、メモリとディスクアクセスの早さが逆転する分岐点はどこでしょうね。有名どころのコードでみつかると話が面白いんですが(人任せ)。

    • by Anonymous Coward

      PythonのGCは基本参照カウント、循環参照を別途マーク・アンド・スイープで回収するので。、
      文字列のような内部に参照を持たないインスタンスの場合GCが動かない。

    • Re: (スコア:0, 参考になる)

      by Anonymous Coward

      論文のソースコードがダメダメですね。遅くて当たり前です。
      JavaではStringは変更不可のオブジェクトなので文字列を効率よく編集するにはStringBuilderを使うのが常識になってます。

      String concatString = "";
      for (int i=0; i < numIter; i++) {
              concatString += addString;
      }

      上のコードは次のコードと等価です。毎回大量のオブジェクトを生成して破棄する富豪的なコードなのでGCが大量に発生して最悪のパフォーマンスになっていますね。

      String concatString = "";
      for (int i = 0; i < n

      • by Anonymous Coward on 2015年03月28日 20時59分 (#2786579)

        うん、みんな知ってる

        >論文には「JavaではStringBuilderやStringBufferといったミュータブルなデータ型を使用すれば結果が大幅に改善する」といった記述もある

        親コメント
        • by Anonymous Coward on 2015年03月28日 22時48分 (#2786624)

          さらにいうと、リバースコンパイラでチェックしたけど、
          最近のコンパイラなら hoge += fuga 的にシンプルに記述していれば、
          勝手にテンポラリな StringBuilder に置き換えてくれる。

          # 当然、StringBuffer とか使って現在では微妙な最適化した古いコードは、コンパイラで最適化してくれない。
          # 何でも感でも人間が最適化するのは考え物ですね。

          親コメント
        • by t-wata (10969) on 2015年03月28日 21時39分 (#2786596) 日記

          さらに言えば、BufferedWriterをやめて、FileWriterで毎回writeするならもっと遅くなるでしょうね。完全に逆転するでしょう。
          BufferedWriter使ったらバッファが一杯になるまでIOは走らないので、いわばこっちこそが「文字列を結合してから書き込む」に相当する処理なので。

          親コメント
  • by duenmynoth (34577) on 2015年03月28日 21時24分 (#2786589) 日記
    どうやったらメモリアクセスが遅くなるかを研究しました!ってタイトルならフルボッコにならずに済むのに

    #普通より低速化させる方法を必死で考えたのかと思うと、ある意味凄いな
  • 事前に直前までを書き込み済み相当になってて、非同期に処理できた、みたいな要素もあるのかな...?

    たぶん
      new x
      for{
      x.something()
      }
      write(x)
    みたいな処理だったら
      new x
      for{
      x.something()
      async write(x)
      }
      write(x)
      的なことが破綻なくできたら、 それは効率を上げれて、差を隠蔽できるんじゃなかろうかな、とは思う

    --
    M-FalconSky (暑いか寒い)
  • by nim (10479) on 2015年03月28日 18時31分 (#2786507)

    >OSによるバッファリングがあるためにディスクへ直接書き込んでも速度がそれほど低下しないと指摘。

    これ、「ディスクに直接書き込む」って言えんのか?

    • by Anonymous Coward on 2015年03月28日 18時39分 (#2786511)

      バッファリンがどうこう言ってるあたりからして、計測時間中には全く書き込んでいない可能性すらある。

      親コメント
      • by Anonymous Coward

        でも、「ファイルから直接読み込む」ことができるなら問題ないんですよ。

        事の本質は、メモリは断片化できないけど、ファイルは断片化しててもいい、ってことだと思いますけどね。ファイルは断片化上等だから、領域の再確保でコピーが発生しません。精々が飛んでる部分のアドレスを書くくらい。一方、メモリは確保した領域が足りなくなれば、そこをあきらめて新しい場所に移動して既存の部分はコピーするから、ですよね。

        だから、ファイルっぽいメモリ確保ができるメモリマネージャーを誰かが作ればいいんだと思いますよ。ポインタで直接足し算引き算できないのがネックですけど、ファイルと同等のインターフェイスを備えるくらいならできるでしょうから、そこを経由させて最後に一気にディスクアクセスした方が速いと思います。

        • by Anonymous Coward

          RAMディスクとかってそういう仕組みじゃないの?
          そもそもディスク上でデータ処理なんかしたら、IO待ちだけで死ねると思うんだけどね。

          多分この研究者達は、DBシステムの先人達の苦労を、貶めたいんだろうね。

        • by Anonymous Coward

          それって、memory-mapped fileのことでは?

    • by Anonymous Coward on 2015年03月28日 20時15分 (#2786552)

      Linuxなら、Direct I/OやRAW I/Oを利用した場合のみがディスクへ直接書き込みになります。仮想ファイルシステムレベルで扱いが違いますので一緒に議論することはできません。それにmemory-mapped fileについても議論する必要があるだろうしね。

      Windowsの場合、memory-mapped fileが一番高速みたいですね。最近のWindowsならOSによるバッファリングも間接的にmemory-mapped fileを使っているようですしね。

      あと、メモリ空間に余裕があるなら、Laege Page Supportを利用するかどうかによっても違いが出てくるけれど、ここでの議論とは趣旨が変わってくる。

      親コメント
    • by Anonymous Coward

      str1=str1+str2 より str1+=str2 の方が速い、みたいな話ですね。

  • by Anonymous Coward on 2015年03月28日 19時19分 (#2786527)

    SSD最強って事で。

  • by Anonymous Coward on 2015年03月28日 18時18分 (#2786499)
    こんな単純なテストではあてにならない。 実際はもっと複雑な処理するだろ。
  • by Anonymous Coward on 2015年03月28日 18時33分 (#2786508)

    IOの速度が気になる時は各層でのバッファリング意識するのは当然だと思ってた。
    一番驚いたのは、こんな新入社員の暇つぶしにやらせるような内容の調査が(なんの目的であれ)論文として存在するということ。

  • by Anonymous Coward on 2015年03月28日 18時34分 (#2786509)

    A. 1MBの文字列処理をする + ディスクに1MB書き込む
    B. ディスクに1MB書き込む
    そりゃ、Bのほうが早いのは当然。

    • 論文中では何故かBufferedWriterを使っています。8192バイトずつ書き込んでいるのに気付いていないのでしょうか。

      親コメント
    • by Anonymous Coward

      あからさまにディスク優位なほどのシンプルすぎるプログラムでしたね。
      これが、文字列を分割して間に別の文字列を挟み込んで...とかやりだせば一気にメモリ優位になっていくはず。

    • by Anonymous Coward

      ディスクの方も「読み込み+結合+書き込み」でループすべき

    • by Anonymous Coward

      一応、実験で言いたいことは、
      メモリでデータ処理してからまとめてディスクに書くより、
      ディスクに直接書いたほうがいいということなのだと思う。

      たださ、データをシーケンシャルにディスクに書くだけの単純処理で、それが示されるわけないじゃない。

  • by Anonymous Coward on 2015年03月28日 18時36分 (#2786510)

    何度もメモリ確保し直している時点でダメ
    そもそも、テキストに書き出すだけなら、文字列を結合する必要ないよね?

  • by Anonymous Coward on 2015年03月28日 18時44分 (#2786514)

    つまり、例えばRRDファイルを管理する場合、対象ファイルやノードが膨大であっても、tmpfs上にRRDファイルを直接展開するよりもrrdcachedを使った方が速く、更にそれよりもRRDファイルを毎回ディスクに書き出す方が速いというわけですねわかります。
    RRDtoolもJavaやPythonで作りなおせばそうなるんですかね?
    実に興味深い。

  • by Anonymous Coward on 2015年03月28日 19時43分 (#2786540)

    > ディスクアクセスを避けてできるだけメモリー上で処理することが実行時間の短縮につながると考えられているが、これに逆行する研究結果を

    逆行してない。
    それするのがBufferedWriter。
    二重にバッファリングしてるから遅くなる。しかも非効率なコードで。

  • StringBuilder buff = new StringBuilder();
    buff.append("select col1,col2,col3");
    buff.append(" from table1");
    buff.append(" where id = 123456");
    -------------------------------------
    String buff = "select col1,col2,col3"
    + " from table1"
    + " where id = 123456";
    -------------------------------------
    以外とわかってない人が多い。

    • by Anonymous Coward

      それは一つの式で複数結合している場合はコンパイラが最適化してくれるだけで、
      ループの中で結合処理をしてたら普通にStringBuilder使ったほうが高速。

      • by Anonymous Coward

        だから、「高速 」なんだって。

        速い遅い語る前に仕組みを理解しましょうってこと。

      • by Anonymous Coward

        コンパイラの最適化とかまで考え及ばず(無知なだけ)で、「StringBuilderを使えば問題ない!!」って
        「やり方だけを覚えている」アホが多いって言っているんじゃないの?

        実際に数回の限られた文字列結合でStringBuilder使って指摘されても逆切れする人は多い
        当然、彼らはStringBuilderを使用すること自体にもコストがかかることや、そのコスト以上の恩恵が得られるのはどの程度からなのか?など考えたことすらない

        とりあえず噛み付きたいだけだったんだろうけど、銀の弾丸なんてないってことは素直に認めた方が貴方の将来に+だよ

      • by Anonymous Coward

        言語だけでなく型や命令だって銀の弾丸は無いし。
        てかC#に至っては文字列探して連結するループなんか記述不要でLINQとString.Join、
        Javaだとstreamである程度は近づけられるが末端foreachなのでStringBuilderかな。

  • by Anonymous Coward on 2015年03月28日 20時28分 (#2786562)

    ミスリードを誘うための恣意的なプログラミング、というだけに見えますね。

    for文の中身を
            concatString = addString + concatString
    こうするだけで、メモリ版の方が高速になります。(結果として同じ答えが得られる)
            concatString += addString

    addStringを逆順に入れることに拘りたい or 古いPythonでも速くしたいなら下記のようにすればOKで、やはりメモリ版の方が高速。
    conlist=[]
    for i in range(0, numIter):
            conlist.append(addString)
    conlist.reverse()
    concatString = "".join(conlist)

    • by t-wata (10969) on 2015年03月28日 21時54分 (#2786602) 日記

      ディスクに直接書き込むって方は、

      for i in range(0, numIter):
          f.write(addString)

      というコードを使っているので、メモリで結合も

      concatString += addString

      で良いはず。遅くするために無理やりやってますよね。
      あとこれ書いた人はまずバッファリングについて調べたほうがいいですね。pythonにしろJavaにしろ、IO操作にバッファリング効かせたら何を比較したいのやらさっぱりわからないですね。pythonも
      open('file.txt', 'w', 0)
      して比較するべきでしょう。

      親コメント
  • by Anonymous Coward on 2015年03月28日 20時32分 (#2786563)

    評価方法が間違っている
    でFA?

    • by Anonymous Coward

      この論文に関してはそうなんだろうなぁ…

      でも、非アクティブなタブや使いもしないキャッシュをオンメモリで保持してメモリとページファイルバカ食いするWebブラウザとかみるとディスク活用したほうが軽量高速になるケースは少なくない気がする。
      非アクティブなタブのデータは全部ディスクに吐き出して、アクティブ化の際に一括で読み込みとかしてくれたほうがコミットサイズ的にも負荷的にも有利な気がしてならない。
      物理メモリを溢れまくってると、タブ切り替えでページフォルトが落ち着くまでの時間がタブあたりの消費メモリ量を読み込む時間を圧倒的に超えるだろアレ。

typodupeerror

アレゲはアレゲを呼ぶ -- ある傍観者

読み込み中...