23

まず、私は Haskell の初心者です。

リアルタイム ゲーム用に Haskell を C に統合することを計画しています。Haskell はロジックを行い、C はレンダリングを行います。これを行うには、ティックごとに (少なくとも 1 秒あたり 30 回) 巨大で複雑に構造化されたデータ (ゲームの状態) を相互にやり取りする必要があります。したがって、渡すデータは軽量でなければなりません。この状態データは、メモリ上のシーケンシャル スペースに置くことができます。Haskell と C の両方のパーツは、状態のすべての領域に自由にアクセスする必要があります。

最良の場合、データを渡すコストは、ポインタをメモリにコピーすることです。最悪の場合、変換でデータ全体をコピーします。

Haskell の FFI ( http://www.haskell.org/haskellwiki/FFICookBook#Working_with_structs ) を読んでいます。Haskell コードは、メモリ レイアウトを明示的に指定しているように見えます。

いくつかの質問を聞きたいんです。

  1. Haskellはメモリレイアウトを明示的に指定できますか? (C 構造体と正確に一致する必要があります)
  2. これは実メモリ レイアウトですか。または、何らかの変換が必要ですか?(パフォーマンスのペナルティ)
  3. Q#2 が true の場合、メモリ レイアウトを明示的に指定するとパフォーマンスが低下しますか?
  4. 構文は何#{alignment foo}ですか? これに関するドキュメントはどこにありますか?
  5. 巨大なデータを最高のパフォーマンスで渡したい場合、どのようにすればよいですか?

*PS 私が言った明示的なメモリ レイアウト機能は、C# の [StructLayout] 属性です。これは、メモリ内の位置とサイズを明示的に指定しています。 http://www.developerfusion.com/article/84519/mastering-structs-in-c/

Haskell が C 構造体のフィールドと一致する言語構造を持っているかどうかはわかりません。

4

3 に答える 3

25

プリプロセッサの使用を強くお勧めします。私は c2hs が好きですが、hsc2hs は ghc に含まれているため非常に一般的です。グリーンカードは放棄されたようです。

質問に答えるには:

1) はい、Storable インスタンスの定義を通じて。Storable を使用することは、FFI を介してデータを渡すための唯一の安全なメカニズムです。Storable インスタンスは、Haskell 型と生メモリ (Haskell Ptr、ForeignPtr、StablePtr、または C ポインター) の間でデータをマーシャリングする方法を定義します。次に例を示します。

data PlateC = PlateC {
  numX :: Int,
  numY :: Int,
  v1   :: Double,
  v2   :: Double } deriving (Eq, Show)

instance Storable PlateC where
  alignment _ = alignment (undefined :: CDouble)
  sizeOf _ = {#sizeof PlateC#}
  peek p =
    PlateC <$> fmap fI ({#get PlateC.numX #} p)
           <*> fmap fI ({#get PlateC.numY #} p)
           <*> fmap realToFrac ({#get PlateC.v1 #} p)
           <*> fmap realToFrac ({#get PlateC.v2 #} p)
  poke p (PlateC xv yv v1v v2v) = do
    {#set PlateC.numX #} p (fI xv)
    {#set PlateC.numY #} p (fI yv)
    {#set PlateC.v1 #}   p (realToFrac v1v)
    {#set PlateC.v2 #}   p (realToFrac v2v)

{# ... #}フラグメントは c2hs コードです 。fIですfromIntegral。get フラグメントと set フラグメントの値は、同名の Haskell 型ではなく、インクルードされたヘッダーから次の構造体を参照します。

struct PlateCTag ;

typedef struct PlateCTag {
  int numX;
  int numY;
  double v1;
  double v2;
} PlateC ;

c2hs はこれを次の単純な Haskell に変換します。

instance Storable PlateC where
  alignment _ = alignment (undefined :: CDouble)
  sizeOf _ = 24
  peek p =
    PlateC <$> fmap fI ((\ptr -> do {peekByteOff ptr 0 ::IO CInt}) p)
           <*> fmap fI ((\ptr -> do {peekByteOff ptr 4 ::IO CInt}) p)
           <*> fmap realToFrac ((\ptr -> do {peekByteOff ptr 8 ::IO CDouble}) p)
           <*> fmap realToFrac ((\ptr -> do {peekByteOff ptr 16 ::IO CDouble}) p)
  poke p (PlateC xv yv v1v v2v) = do
    (\ptr val -> do {pokeByteOff ptr 0 (val::CInt)}) p (fI xv)
    (\ptr val -> do {pokeByteOff ptr 4 (val::CInt)}) p (fI yv)
    (\ptr val -> do {pokeByteOff ptr 8 (val::CDouble)})   p (realToFrac v1v)
    (\ptr val -> do {pokeByteOff ptr 16 (val::CDouble)})   p (realToFrac v2v)

もちろん、オフセットはアーキテクチャに依存するため、プリプロセッサを使用すると、移植可能なコードを記述できます。

これを使用するには、データ型 ( 、 など) にスペースを割り当てnewmallocデータpokeを Ptr (または ForeignPtr) に入れます。

2) これは実メモリ レイアウトです。

peek3) /による読み書きにはペナルティがありpokeます。大量のデータがある場合は、配列全体を Haskell リストにマーシャリングする代わりに、C 配列から 1 つの要素だけを読み取るなど、必要なものだけを変換する方が適切です。

4) 構文は、選択したプリプロセッサによって異なります。 c2hs ドキュメント. hsc2hs ドキュメント. 紛らわしいことに、hsc2hs は構文#stufforを使用しますが、c2hs は#{stuff}を使用します{#stuff #}

5) @sclv の提案は、私も同様です。Storable インスタンスを作成し、データへのポインタを保持します。C 関数を記述してすべての作業を行い、それらを FFI を介して呼び出すか、(あまり良くない) peek と poke を使用して低レベルの Haskell を記述し、必要なデータの部分だけを操作することができます。全体をマーシャリングする (つまり、データ構造全体を呼び出すpeek、または呼び出すpoke) とコストがかかりますが、ポインターを渡すだけであれば、コストは最小限に抑えられます。

FFI を介してインポートされた関数を呼び出すと、「安全でない」とマークされていない限り、重大なペナルティがあります。インポートを「安全ではない」と宣言することは、関数が Haskell または未定義の動作結果にコールバックしてはならないことを意味します。同時実行または並列処理を使用している場合、同じ機能 (つまり CPU) 上のすべての Haskell スレッドが呼び出しが戻るまでブロックされることも意味するため、呼び出しはかなり迅速に戻るはずです。これらの条件が受け入れられる場合、「安全でない」呼び出しは比較的高速です。

Hackage には、この種のことを扱うパッケージがたくさんあります。hsndfilehCsoundは、c2hs の優れた実践例としてお勧めできます。ただし、慣れ親しんだ小さな C ライブラリへのバインディングを見れば、おそらく簡単です。

于 2010-12-21T22:47:55.070 に答える
7

ボックス化されていない厳密な Haskell 構造の決定論的なメモリ レイアウトを取得できますが、保証はなく、本当に悪い考えです。

変換を受け入れる気があるなら、Storeable があります: http://www.haskell.org/ghc/docs/6.12.3/html/libraries/base-4.2.0.2/Foreign-Storable.html

私がしたいことは、C 構造を構築してから、Haskell の「等価物」を生成しようとするのではなく、FFI を使用してそれらを直接操作する Haskell 関数を構築することです。

あるいは、ゲームの状態全体ではなく、どのオブジェクトが世界のどこにあるかに関するいくつかの情報と、その方法に関する実際の情報だけを C に渡す必要があると判断することもできます。方程式の C 側だけに住んでいるそれらを描きます。次に、すべてのロジックを Haskell で実行し、ネイティブの Haskell 構造で操作し、C が実際にレンダリングする必要があるデータの小さなサブセットのみを C の世界に投影します。

編集: マトリックスやその他の一般的な c 構造には、c 側の重労働を維持する優れたライブラリ/バインディングが既にあることを追加する必要があります。

于 2010-12-21T17:44:04.527 に答える
2

hsc2hsc→hs、およびGreen Cardはすべて、自動化された Haskell⇆C 構造のピーク/ポークまたはマーシャリングを提供します。サイズとオフセットを手動で決定し、Haskell でポインター操作を使用するよりも、それらを使用することをお勧めしますが、それも可能です。

  1. 私があなたを正しく理解しているなら、私の知る限りではありません。Haskell には、外部集計データ構造の組み込み処理がありません。
  2.  
  3.  
  4. そのページで説明されているように、hsc2hsC マジックを使用しています。
于 2010-12-21T17:49:34.717 に答える