4

別のマイクロベンチマーク:この「ループ」はなぜですか(ghc -O2 -fllvm、7.4.1、Linux 64ビット3.2カーネルでコンパイルされ、にリダイレクトされます/dev/null

mapM_ print [1..100000000]

バッファリングされていないシステムコールCを使用したプレーンの単純なfor-cycleよりも約5倍遅いですか?write(2)Haskellの落とし穴を集めようとしています。

この遅いCソリューションでさえ、Haskellよりもはるかに高速です

int i;
char buf[16];
for (i=0; i<=100000000; i++) {
    sprintf(buf, "%d\n", i);
    write(1, buf, strlen(buf));
}
4

3 に答える 3

10

さて、私のボックスでは、コンパイルされたCコードのgcc -O3実行には約21.5秒かかりますが、元のHaskellコードは約56秒かかります。したがって、5倍ではなく、2.5より少し上です。

最初の重要な違いは

mapM_ print [1..100000000]

sを使用します。これは、事前のIntegerチェックが含まれるため少し遅くなり、ボックス化されIntたsで機能しますが、のShowインスタンスはボックス化されていないsで機能します。IntInt#

型署名を追加して、HaskellコードがIntsで機能するようにします。

mapM_ print [1 :: Int .. 100000000]

時間を47秒に短縮します。これは、Cコードにかかる時間の2倍を少し上回ります。

さて、もう1つの大きな違いは、バイトの連続したバッファを埋めるだけでなくshow、のリンクリストを生成することです。Charそれも遅いです。

次に、そのリンクされたsのリストを使用して、ハンドルCharに書き込まれるバイトバッファを埋めます。stdout

したがって、HaskellコードはCコードよりも多くの、そしてより複雑なことを行うので、それがより長くかかることは驚くべきことではありません。

確かに、そのようなものをより直接的に(したがってより速く)出力する簡単な方法があることが望ましいでしょう。ただし、これを処理する適切な方法は、より適切なアルゴリズムを使用することです(Cにも適用されます)。への簡単な変更

putStr . unlines $ map show [0 :: Int .. 100000000]

所要時間はほぼ半分になります。本当に高速にしたい場合は、より高速なI / Oを使用して、アプリケーションの回答ByteStringに示されているように効率的に出力を構築します。

于 2012-11-11T19:04:03.200 に答える
7

私の(かなり遅くて時代遅れの)マシンでは、結果は次のとおりです。

$ time haskell-test > haskell-out.txt
real    1m57.497s
user    1m47.759s
sys     0m9.369s
$ time c-test > c-out.txt
real    7m28.792s
user    1m9.072s
sys     6m13.923s
$ diff haskell-out.txt c-out.txt
$

(CとHaskellの両方が0で始まるようにリストを修正しました)。

はい、あなたはこの権利を読みます。HaskellはCよりも数倍高速です。むしろ、通常バッファリングされたHaskellは、write(2)非バッファリングsyscallを使用したCよりも高速です。

(実際のディスクファイルではなく/ dev / nullへの出力を測定する場合、Cは約1.5倍高速ですが、/ dev / nullのパフォーマンスを気にするのは誰ですか?)

技術データ:Intel E2140 CPU、2コア、1.6 GHz、1Mキャッシュ、Gentoo Linux、gcc4.6.1、ghc7.6.1。

于 2012-11-11T22:46:34.467 に答える
5

巨大なバイト文字列をオペレーティングシステムに渡す標準のHaskellの方法は、ビルダーモノイドを使用することです。

import Data.ByteString.Lazy.Builder  -- requires bytestring-0.10.x
import Data.ByteString.Lazy.Builder.ASCII -- omit for bytestring-0.10.2.x
import Data.Monoid
import System.IO

main = hPutBuilder stdout $ build  [0..100000000::Int]

build = foldr add_line mempty
   where add_line n b = intDec n <> charUtf8 '\n' <> b

それは私に与えます:

 $ time ./printbuilder >> /dev/null
 real   0m7.032s
 user   0m6.603s
 sys    0m0.398s

使用したHaskellアプローチとは対照的に

$ time ./print >> /dev/null
real    1m0.143s
user    0m58.349s
sys 0m1.032s

mapM_ printつまり、ダニエル・フィッシャーの驚くべき敗北とは対照的に、9倍上手くやるのは子供の遊び です。あなたが知る必要があるすべてはここにあります:http://hackage.haskell.org/packages/archive/bytestring/0.10.2.0/doc/html/Data-ByteString-Builder.html私はそれをあなたのCと比較しません結果はDanielやnmよりもはるかに遅いので、何かがうまくいかなかったと思います。

編集:インポートをすべてのバージョンと一貫性のあるものbytestring-0.10.xにしました。次のことがより明確になる可能性があります。Builderunlines . map show

main = hPutBuilder stdout $ unlines_ $ map intDec [0..100000000::Int]
 where unlines_ = mconcat . map (<> charUtf8 '\n')
于 2012-11-12T08:37:39.627 に答える