コード見ればわかりますが、毎回書き込みはバッファリング効かせてるんでシステムコールの回数はかなり抑えられてます。 一方でメモリ上で結合の方は、Stringで+を使っての結合なので、内部では new StringBuffer(orig).append(str).toString()が毎回走るので遅くて当たり前です。 GC以前に「そりゃそうなるよな」としか言いようがないです。
> 一方的に書くだけだとバッファの許す限り応答待ちは特に必要ないよ。 読むだけならなおさらですね。 なので実際のデバイスからの読み書きの処理時間より、システムコールのオーバーヘッドの方が大きくなるはずなんだよね。 これはmmap/DirectByteBuffer使って読み書きするのとread/writeの読み書きとでは同じディスクからの読み書きだけど速度が桁違いに違うことからも明らか。 JavaのDirectByteBufferなら、1バイトずつ100万回読み書きしても、100万バイトを1度に読み込むのも速度は大して変わらないのに、 read/writeだと1バイトずつ100万回と100万バイトを1回で読み込むのとでは2桁くらい性能が悪くなるからね。 例えば dd if=/dev/urandom of=test.dat BS=1024 count=1000で1Mくらいのファイル作って、IOPerfTest.java [github.com]みたいなテストを実行してみればわかるよ。自分の環境だと Test IO::730 ms Test IOAll::3 ms Test NIO::8 ms Test NIOAll::0 ms
という結果になった。 同じことをpythonでやるとmmapの性能がJavaほど出ないけど、1バイトずつ100万回読み込むのと、100万バイトを一度に読み込むのではmmapにせよreadにせよ非常に大きな差が出た。 ioperf_test.py [github.com]を実行すると、 test_io: 1734.96699333 ms test_io_all: 0.364065170288 ms test_mmap: 186.017990112 ms test_mmap_all: 0.677824020386 ms
Javaのヒープ領域 (スコア:2)
文字列にあわせて新しい領域をとるために遅いことはわかりますが、Javaのほうが遅いというのは、ヒープ領域が小さすぎて、
GCが多数回動いているからでしょうか?
Re:Javaのヒープ領域 (スコア:2)
コード見ればわかりますが、毎回書き込みはバッファリング効かせてるんでシステムコールの回数はかなり抑えられてます。
一方でメモリ上で結合の方は、Stringで+を使っての結合なので、内部では new StringBuffer(orig).append(str).toString()が毎回走るので遅くて当たり前です。
GC以前に「そりゃそうなるよな」としか言いようがないです。
Re: (スコア:0)
文字列の結合に+を使用しているプログラマーはディスクアクセスが遅いなんてことは気にしないはずなので、
メモリ上で処理しようなどとは思ないんじゃなかろうか。
結果、直接書きこもうとするのでパフォーマンスの問題は無くなる。
めでたしめでたし。
Re: (スコア:0)
今回の結果は極端にしても、メモリとディスクアクセスの早さが逆転する分岐点はどこでしょうね。有名どころのコードでみつかると話が面白いんですが(人任せ)。
Re: (スコア:0)
メモリとディスクアクセスの速さが逆転する点は多分ない。
賢いメモリの使い方と賢くないメモリの使い方だとサイズによっては逆転する。程度
Re: (スコア:0)
PythonのGCは基本参照カウント、循環参照を別途マーク・アンド・スイープで回収するので。、
文字列のような内部に参照を持たないインスタンスの場合GCが動かない。
Re: (スコア:0, 参考になる)
論文のソースコードがダメダメですね。遅くて当たり前です。
JavaではStringは変更不可のオブジェクトなので文字列を効率よく編集するにはStringBuilderを使うのが常識になってます。
String concatString = "";
for (int i=0; i < numIter; i++) {
concatString += addString;
}
上のコードは次のコードと等価です。毎回大量のオブジェクトを生成して破棄する富豪的なコードなのでGCが大量に発生して最悪のパフォーマンスになっていますね。
String concatString = "";
for (int i = 0; i < n
Re:Javaのヒープ領域 (スコア:1)
うん、みんな知ってる
>論文には「JavaではStringBuilderやStringBufferといったミュータブルなデータ型を使用すれば結果が大幅に改善する」といった記述もある
Re:Javaのヒープ領域 (スコア:4, 参考になる)
さらにいうと、リバースコンパイラでチェックしたけど、
最近のコンパイラなら hoge += fuga 的にシンプルに記述していれば、
勝手にテンポラリな StringBuilder に置き換えてくれる。
# 当然、StringBuffer とか使って現在では微妙な最適化した古いコードは、コンパイラで最適化してくれない。
# 何でも感でも人間が最適化するのは考え物ですね。
Re: (スコア:0)
Javaの場合、実行中にさらに最適化される可能性もあるし。
Pythonの場合でも処理系の実装によっては同様の最適化が可能と思う。
プログラマー同士の雑談レベルの話題を論文にしたことに意味はあるのだろうか。
Re:Javaのヒープ領域 (スコア:3)
さらに言えば、BufferedWriterをやめて、FileWriterで毎回writeするならもっと遅くなるでしょうね。完全に逆転するでしょう。
BufferedWriter使ったらバッファが一杯になるまでIOは走らないので、いわばこっちこそが「文字列を結合してから書き込む」に相当する処理なので。
Re: (スコア:0)
どうせI/Oは裏(OSやHDD)でバッファリングされて動くから、一方的に書くだけだとバッファの許す限り応答待ちは特に必要ないよ。
キャッシュ制御してなければAPIだけぶっ叩いてアプリケーションを終了しても大丈夫な筈。
えーと論文の実験では…1MB!?
その程度OSやライブラリのバッファどころかHDDのキャッシュにすら収まるゴミみたいなサイズっすね。
生物系(特にゲノム関係)ではGB単位のデータが飛び交ってる [hatena.ne.jp]んで何寝ぼけたこと言ってるんだみたいなデータ量。
これでは時間分解能からして怪しいレベルというか、せめて反復実行くらい掛けろと。
Re:Javaのヒープ領域 (スコア:2)
> 一方的に書くだけだとバッファの許す限り応答待ちは特に必要ないよ。
読むだけならなおさらですね。
なので実際のデバイスからの読み書きの処理時間より、システムコールのオーバーヘッドの方が大きくなるはずなんだよね。
これはmmap/DirectByteBuffer使って読み書きするのとread/writeの読み書きとでは同じディスクからの読み書きだけど速度が桁違いに違うことからも明らか。
JavaのDirectByteBufferなら、1バイトずつ100万回読み書きしても、100万バイトを1度に読み込むのも速度は大して変わらないのに、
read/writeだと1バイトずつ100万回と100万バイトを1回で読み込むのとでは2桁くらい性能が悪くなるからね。
例えば dd if=/dev/urandom of=test.dat BS=1024 count=1000で1Mくらいのファイル作って、IOPerfTest.java [github.com]みたいなテストを実行してみればわかるよ。自分の環境だと
Test IO::730 ms
Test IOAll::3 ms
Test NIO::8 ms
Test NIOAll::0 ms
という結果になった。
同じことをpythonでやるとmmapの性能がJavaほど出ないけど、1バイトずつ100万回読み込むのと、100万バイトを一度に読み込むのではmmapにせよreadにせよ非常に大きな差が出た。
ioperf_test.py [github.com]を実行すると、
test_io: 1734.96699333 ms
test_io_all: 0.364065170288 ms
test_mmap: 186.017990112 ms
test_mmap_all: 0.677824020386 ms
となった。