2012-02-17 12 views
15

私は小さなテストフレームワークを持っています。次のループを実行します。runhaskellのスピードアップ

  1. 小さなHaskellソースファイルを生成します。

  2. runhaskellでこれを実行します。プログラムはさまざまなディスクファイルを生成します。

  3. 作成したばかりのディスクファイルを処理します。

これは数十回起こります。 runhaskellがプログラムの実行時間の大半を占めていることが判明しました。

一方で、runhaskellがディスクからファイルをロードし、それをトークン化し、解析し、依存性分析を行い、ディスクから20KB以上のテキストをロードし、トークン化し、完全な型推論を実行し、チェックするコンパイルされたマシンコードとのリンク、インタープリターでの実行、壁の時間の2秒以内のすべては、あなたがそれについて考えるとき、実際にかなり印象的です。一方、私はまだそれをより速くしたいです。 ;-)

テスター(上記のループを実行するプログラム)をコンパイルすると、小さなパフォーマンスの違いが生じました。スクリプトがリンクする20KBのライブラリコードをコンパイルすると、はるかに目立った改善が得られました。しかし、呼び出しごとに約1秒かかります。runhaskell

生成されたHaskellファイルは、それぞれ1KBを少し上回りますが、ファイルの一部だけが実際に変更されます。おそらくファイルをコンパイルし、GHCの-eスイッチを使用する方が速いでしょうか?

また、これを遅くしている多くのOSプロセスを繰り返し作成して破壊するオーバーヘッドがありますか? runhaskellを呼び出すたびに、OSはシステムの検索パスを探索し、必要なバイナリファイルを探し出し、メモリにロードします(これは既にディスクキャッシュにありますか?)、DLLとリンクして起動します。 OSプロセスを絶えず作成して破壊するのではなく、GHCの1つのインスタンスを(簡単に)実行できる方法がありますか?

最終的には、常にGHC APIがあると思います。しかし、私が理解しているように、これは悪夢の日には使いにくく、文書化されておらず、GHCの小さな点での急進的な変化に敏感です。私が実行しようとしている作業は非常に単純なものなので、必要以上に複雑なものにしたくないのです。

提案?

更新:GHC -eへの切り替え(すなわち、今すべてが実行されて一つの式を除いてコンパイルされている)は測定の性能差を作りません。この時点で、すべてのOSオーバーヘッドであることは明らかです。私は多分テスターからGHCiにパイプを作成して、ただ一つのOSプロセスを利用できるかどうか疑問に思っています...

+0

あなたの全体のワークフローは正確にパフォーマンスを重視していませんか?なぜHaskellのコードを作成しなければならないのですか? – leftaroundabout

+3

明らかにGHCデーモンが必要です! :p(ブート中にgrepを継続的に呼び出すなどのオーバーヘッドを避けるためにgrepデーモンを作成することについて冗談を言っていた人もいます) – ivanm

+1

+1で正当化され、正しく実行された最適化の試行。 – delnan

答えて

9

申し分なく、私は解決策を持っている:私は、単一GHCiのプロセスを作成し、そのstdinパイプに接続され、Iができるようにインタラクティブに評価する式を送信します。

かなり大きなプログラムリファクタリングがいくつかあり、テストスイート全体が48秒ではなく実行に約8秒かかっています。それは私のためにやります! :-D

(誰これやろうとするには:神の愛についてを、GHCiのに-v0スイッチを渡すために覚えている、またはあなたが対話的にGHCiのを実行した場合GHCiのは、変なふうにバナーを歓迎買ってあげます!配管に接続されている場合、コマンドプロンプトがまだ見えても-v0ではなく、コマンドプロンプトが消え、私はこれが参考に設計上の特徴ではなく、ランダムな偶然では想定しています)もちろん


、半分。私がこの奇妙なルートを下っていく理由は、stdoutstderrをファイルに取り込みたいということです。 RunHaskellを使用すると、それは非常に簡単です。子プロセスを作成するときに適切なオプションを渡すだけです。しかし、今度はすべてののテストケースが単一のOSプロセスで実行されているので、stdinstdoutをリダイレクトする明白な方法はありません。

私が思いついた解決策は、すべてのテスト出力を1つのファイルに送ることでした。テストの間にGHCiがテスト出力に表示されないマジックストリングを出力しました。その後、GHCiを終了し、ファイルをスラップして、マジックストリングを探して、ファイルを適切なチャンクにスナップすることができます。

+0

テスト関数を変更して、stdoutとstderrに直接書き込むのではなく、出力やエラーを処理することができますか? – Alex

2

ソースファイルの大部分が変更されない場合は、おそらくGHCの-fobject-code with -outputdir)フラグを使用して、ライブラリファイルの一部をコンパイルします。

+0

私が言ったように、私はすでに20KBのライブラリコードをコンパイルしました。これにより実行時間が2秒から1秒に短縮されました。しかし、簡単な方法があればこれをさらに減らしたいと思います。 – MathematicalOrchid

+0

@MathematicalOrchidああ、そのビットを逃したごめんなさい: – ivanm

0

runhaskellに電話をかけても時間がかかる場合は、完全に削除する必要がありますか?

実際にハスケルコードを変更する必要がある場合は、以下を試すことができます。

  1. さまざまな内容のモジュールを必要に応じて作成します。
  2. 各モジュールは主な機能をエクスポートする必要があります
  3. 追加のラッパーモジュールは、入力引数に基づいてセットから正しいモジュールを実行する必要があります。単一のテストを実行するたびに、異なる引数を使用します。
  4. プログラム全体を静的にコンパイルされ

例のモジュール:

module Tester where 

import Data.String.Interpolation -- package Interpolation 

submodule nameSuffix var1 var2 = [str| 
module Sub$nameSuffix$ where 

someFunction x = $var1$ * x 
anotherFunction v | v == $var2$ = v 
        | otherwise = error ("anotherFunction: argument is not " ++ $:var2$) 

|] 

modules = [ let suf = (show var1 ++ "_" ++ show var2) in (suf,submodule suf var1 var2) | var1 <- [1..10], var2 <- [1..10]] 

writeModules = mapM_ (\ (file,what) -> writeFile file what) modules 
+0

それは本当にうまくいかない。いくつかのテストプログラムがクラッシュする可能性があります。全体が1つの巨大なプログラムであれば、それは実行を停止します。また、各テストから 'stdout'と' stderr'を取得してファイルに記録したいと思います。それでは、そうはしませんでした、私はちょうど1つの巨大なHaskellプログラムとして全体を生成することができます。それははるかに簡単になるでしょう... – MathematicalOrchid

+0

@MathematicalOrchid:各テストのためにプログラムを再実行するので、すべてがコンパイルされれば大丈夫です。リダイレクトに関して: './testRunner testNumber123 2> stderr.txt 1> stdout.txt'で何が問題になっていますか? – Tener

+0

「クラッシュ」とはどういう意味ですか?すべてのテストを1つのプログラムに統合し、 'stdout'と' stderr'のリダイレクトとクラッシュからのリカバリを扱う最上位のテストランナーを呼び出すことができます。 – pat

0

テストが互いによく分かれている場合は、すべてのテストコードを1つのプログラムに入れて、runhaskellを1回呼び出すことができます。一部のテストが他のテストの結果に基づいて作成されている場合、またはいくつかのテストでunsafeCrashが呼び出された場合、これは機能しない可能性があります。

私はあなたの生成されたコードを推定するには、この

module Main where 
boilerplate code 
main = do_something_for_test_3 

のように見えるあなたは一つのファイルにすべてのテストのコードを置くことができます。各テストコードジェネレータは、do_something_for_test_Nの書き込みを担当します。

module Main where 
boilerplate code 

-- Run each test in its own directory 
withTestDir d m = do 
    cwd <- getCurrentDirectory 
    createDirectory d 
    setCurrentDirectory d 
    m 
    setCurrentDirectory cwd 

-- ["test1", "test2", ...] 
dirNames = map ("test"++) $ map show [1..] 
main = zipWithM withTestDir dirNames tests 

-- Put tests here 
tests = 
    [ do do_something_for_test_1 
    , do do_something_for_test_2 
    , ... 
    ] 

runhaskellへの1回の呼び出しのオーバーヘッドのみが発生します。

3

有用なコードはTBCです。これには、さまざまな野望があります。特に、テストボイラープレートやテストプロジェクトを完全にコンパイルできない場合がありますが、ウォッチ・ディレクトリ機能で拡張することができます。テストはGHCiで実行されますが、cabalによって正常に構築されたオブジェクト( "runghc Setup build")が使用されます。

複雑なタイプのハッカーでEDSLをテストするために開発しました。つまり、重い計算リフトが他のライブラリによって実行される場所です。

私は現在、最新のHaskell Platformにアップデートしており、コメントやパッチを歓迎しています。

関連する問題