28

C プログラムから呼び出されるライブラリとして Haskell を使用している場合、それを呼び出すとパフォーマンスにどのような影響がありますか? たとえば、たとえば 20kB のデータのワールド データ セットに問題があり、次のようなものを実行したい場合:

// Go through my 1000 actors and have them make a decision based on
// HaskellCode() function, which is compiled Haskell I'm accessing through
// the FFI.  As an argument, send in the SAME 20kB of data to EACH of these
// function calls, and some actor specific data
// The 20kB constant data defines the environment and the actor specific
// data could be their personality or state
for(i = 0; i < 1000; i++)
   actor[i].decision = HaskellCode(20kB of data here, actor[i].personality);

ここで何が起こるでしょうか - その 20kB のデータを、Haskell コードによってアクセスされるグローバルな不変参照としてどこかに保持することは可能でしょうか? それとも毎回そのデータのコピーを作成する必要がありますか?

懸念されるのは、このデータがさらに大きくなる可能性があることです。また、Haskell コードの複数の呼び出しで使用される不変データの同じパターンを使用して、はるかに大きなデータ セットに作用するアルゴリズムを書きたいと考えています。

また、dispatch_apply() GCD や Parallel.ForEach(..) C# のように、これを並列化したいです。Haskell の外で並列化する私の論理的根拠は、常に多くの個別の関数呼び出し、つまり 1000 のアクターを操作することを知っているためです。そのため、Haskell 関数内で細粒度の並列化を使用することは、C レベルで管理することに勝るものはありません。FFI Haskell インスタンスを「スレッドセーフ」で実行していますか?これを実現するにはどうすればよいですか? 並列実行を開始するたびに Haskell インスタンスを初期化する必要がありますか? (私がしなければならない場合は遅いようです..) 良いパフォーマンスでこれを達成するにはどうすればよいですか?

4

4 に答える 4

20

それに呼び出しを行うことのパフォーマンスへの影響は何ですか

Haskell ランタイムを 1 回だけ (このように)起動すると仮定すると、私のマシンでは、C から Haskell に関数呼び出しを行い、境界を越えて Int をやり取りするのに約80,000 サイクルかかります(私の Core 2 では31,000 ns ) - - rdstcレジスタを介して実験的に決定

その 20kB のデータを、Haskell コードによってアクセスされるグローバルな不変参照として保持することは可能でしょうか?

はい、それは確かに可能です。データが実際に不変である場合は、次のいずれかを行っても同じ結果が得られます。

  • マーシャリングにより、言語境界を越えてデータを前後にスレッド化します。
  • データへの参照を前後に渡します。
  • IORefまたはHaskell 側でキャッシュします。

どの戦略が最適ですか? データ型によって異なります。最も慣用的な方法は、Haskell 側でByteStringorとして処理して、C データへの参照を前後に渡すことです。Vector

これを並列化したい

次に、制御を反転し、Haskell ランタイムから並列化を行うことを強くお勧めします。そのパスは十分にテストされているため、はるかに堅牢になります。

スレッド セーフに関してforeign exportedは、同じランタイムで実行されている関数に対して並列呼び出しを行うことは明らかに安全ですが、並列性を得るためにこれを試みた人は誰もいないことは確かです。の呼び出しは機能を取得しますが、これは本質的にロックであるため、複数の呼び出しがブロックされる可能性があり、並列処理の可能性が低下します。マルチコアの場合 (たとえば-N4、そのような場合)、結果は異なる場合があります (複数の機能が利用可能です) が、これはパフォーマンスを改善するための悪い方法であることはほぼ確実です。

繰り返しになりますが、Haskell から多くの並列関数呼び出しをforkIO行うことは、C 側で作業を行うよりもオーバーヘッドが少なく、最終的にコードが少なくて済み、より適切に文書化され、より適切にテストされたパスです。

Haskell 関数を呼び出すだけで、多くの Haskell スレッドを介して並列処理が行われます。簡単!

于 2011-04-14T17:00:13.163 に答える
9

アプリケーションの 1 つで C スレッドと Haskell スレッドを組み合わせて使用​​していますが、この 2 つを切り替えてもパフォーマンスが大幅に低下することに気づいていません。そこで、単純なベンチマークを作成しました...これは、ドンのものよりもかなり高速/安価です。これは、2.66GHz i7 で 1000 万回の反復を測定しています。

$ ./foo
IO  : 2381952795 nanoseconds total, 238.195279 nanoseconds per, 160000000 value
Pure: 2188546976 nanoseconds total, 218.854698 nanoseconds per, 160000000 value

OSX 10.6 で GHC 7.0.3/x86_64 および gcc-4.2.1 でコンパイル

ghc -no-hs-main -lstdc++ -O2 -optc-O2 -o foo ForeignExportCost.hs Driver.cpp

ハスケル:

{-# LANGUAGE ForeignFunctionInterface #-}

module ForeignExportCost where

import Foreign.C.Types

foreign export ccall simpleFunction :: CInt -> CInt
simpleFunction i = i * i

foreign export ccall simpleFunctionIO :: CInt -> IO CInt
simpleFunctionIO i = return (i * i)

また、それを駆動する OSX C++ アプリは、Windows または Linux に合わせて簡単に調整できる必要があります。

#include <stdio.h>
#include <mach/mach_time.h>
#include <mach/kern_return.h>
#include <HsFFI.h>
#include "ForeignExportCost_stub.h"

static const int s_loop = 10000000;

int main(int argc, char** argv) {
    hs_init(&argc, &argv);

    struct mach_timebase_info timebase_info = { };
    kern_return_t err;
    err = mach_timebase_info(&timebase_info);
    if (err != KERN_SUCCESS) {
        fprintf(stderr, "error: %x\n", err);
        return err;
    }

    // timing a function in IO
    uint64_t start = mach_absolute_time();
    HsInt32 val = 0;
    for (int i = 0; i < s_loop; ++i) {
        val += simpleFunctionIO(4);
    }

    // in nanoseconds per http://developer.apple.com/library/mac/#qa/qa1398/_index.html
    uint64_t duration = (mach_absolute_time() - start) * timebase_info.numer / timebase_info.denom;
    double duration_per = static_cast<double>(duration) / s_loop;
    printf("IO  : %lld nanoseconds total, %f nanoseconds per, %d value\n", duration, duration_per, val);

    // run the loop again with a pure function
    start = mach_absolute_time();
    val = 0;
    for (int i = 0; i < s_loop; ++i) {
        val += simpleFunction(4);
    }

    duration = (mach_absolute_time() - start) * timebase_info.numer / timebase_info.denom;
    duration_per = static_cast<double>(duration) / s_loop;
    printf("Pure: %lld nanoseconds total, %f nanoseconds per, %d value\n", duration, duration_per, val);

    hs_exit();
}
于 2011-04-15T17:53:39.233 に答える
3

ポインタを渡すと、Haskell はその 20k の blob を覗くことができます。

于 2011-04-14T16:57:59.803 に答える
1

免責事項: 私は FFI の経験がありません。

しかし、20 Kb のデータを再利用して毎回渡す必要がない場合は、単に「パーソナリティ」のリストを取得し、「決定」のリストを返すメソッドを作成できます。 .

だからあなたが機能を持っているなら

f :: LotsaData -> Personality -> Decision
f data p = ...

それでは、ヘルパー関数を作成してみませんか

helper :: LotsaData -> [Personality] -> [Decision]
helper data ps = map (f data) ps

そしてそれを呼び出す?ただし、この方法を使用して並列化する場合は、並列リストと並列マップを使用して Haskell 側で行う必要があります。

C配列をHaskellリスト(または同様の構造)に簡単にマーシャリングできるかどうか/どのようにマーシャリングできるかについては、専門家に説明してもらいます。

于 2011-04-14T17:51:46.673 に答える