c - 카드 - 카페 24 구조화 된 데이터




Haskell과 C 사이의 구조화 된 데이터 교환 (2)

엄격한 박스 화되지 않은 하스켈 구조에 대한 결정 론적 메모리 레이아웃을 얻을 수는 있지만 보장은 없으며 정말 나쁜 생각입니다.

전환으로 거주하려는 경우 Storeable이 있습니다. http://www.haskell.org/ghc/docs/6.12.3/html/libraries/base-4.2.0.2/Foreign-Storable.html

내가 할 수있는 것은 C 구조를 생성 한 다음 Haskell "equivalent"를 생성하려고 시도하는 대신 FFI를 사용하여 직접 작동하는 Haskell 함수를 생성하는 것입니다.

또는 전체 게임 상태가 아니라 일부 오브젝트 정보를 C에 전달하기 만하면되는 정보를 결정할 수 있습니다. 방정식의 C면에서만 전적으로 그려라. 그런 다음 하스켈에서 모든 논리를 수행하고 하스켈 고유의 구조를 조작하고 실제로 C에서 렌더링해야하는 데이터의 작은 부분 집합 만 C 프로젝트에 투영합니다.

편집 : 나는 그 매트릭스를 추가해야하고 다른 일반적인 C 구조는 이미 훌륭한 라이브러리 / 바인딩 C면에 무거운 리프팅을 유지해야합니다.

우선 저는 하스켈 초보자입니다.

나는 실시간 게임을 위해 Haskell을 C에 통합 할 계획이다. Haskell은 논리를 수행하고 C는 렌더링을 수행합니다. 이를 위해, 나는 진드기마다 (적어도 초당 30 회) 엄청나게 복잡하게 구조화 된 데이터 (게임 상태)를 서로주고받습니다. 따라서 전달되는 데이터는 가볍습니다. 이 상태 데이터는 메모리의 순차 공간에 배치 될 수 있습니다. Haskell과 C 부분은 자유롭게 각 주마다 접근해야한다.

최상의 경우, 데이터를 전달하는 비용은 포인터를 메모리로 복사하는 것일 수 있습니다. 최악의 경우 변환을 통해 전체 데이터를 복사합니다.

나는 Haskell의 FFI ( http://www.haskell.org/haskellwiki/FFICookBook#Working_with_structs )를 읽고있다. 메모리 레이아웃을 명시 적으로 지정하는 Haskell 코드 모양.

몇 가지 질문이 있습니다.

  1. 하스켈이 명시 적으로 메모리 레이아웃을 지정할 수 있습니까? (C 구조체와 정확히 일치 함)
  2. 이 실제 메모리 레이아웃입니까? 또는 어떤 종류의 변환이 필요합니까? (성능 패널티)
  3. Q # 2가 참이면 메모리 레이아웃이 명시 적으로 지정된 경우 성능이 저하됩니까?
  4. #{alignment foo} 구문은 무엇입니까? 이에 관한 문서는 어디에서 찾을 수 있습니까?
  5. 최상의 성능으로 거대한 데이터를 전달하려면 어떻게해야합니까?

* 추측 한 명시 적 메모리 레이아웃 기능은 C #의 [StructLayout] 특성입니다. 메모리 내 위치와 크기를 명시 적으로 지정합니다. http://www.developerfusion.com/article/84519/mastering-structs-in-c/

하스켈이 C 구조체의 필드와 일치하는 언어 구조와 일치하는지 모르겠습니다.


전 전처리기를 사용하는 것이 좋습니다. 나는 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 코드입니다. fIfromIntegral . get과 set 조각의 값은 같은 이름의 하스켈 유형이 아니라 포함 된 헤더에서 다음 구조체를 참조합니다.

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)

물론 오프셋은 아키텍처에 따라 다르므로 전처리기를 사용하면 이식 가능한 코드를 작성할 수 있습니다.

데이터 유형 ( new , malloc 등)에 대한 공간을 할당하고 Ptr (또는 ForeignPtr)에 데이터를 입력하여이를 사용합니다.

2) 이것이 실제 메모리 레이아웃입니다.

3) peek / poke 읽기 / 쓰기에 대한 벌칙이 있습니다. 많은 양의 데이터를 가지고 있다면, 필요한 것만 변환하는 것이 낫습니다. 예를 들어 전체 배열을 Haskell 목록으로 정렬하는 대신 C 배열에서 하나의 요소 만 읽는 것입니다.

4) 구문은 선택한 전처리기에 달려 있습니다. c2hs 워드 프로세서 . hsc2hs 워드 프로세서 . 혼란스럽게도 hsc2hs는 #stuff 또는 #{stuff} 구문을 사용하고 c2hs는 {#stuff #} 합니다.

5) @ sclv의 제안은 내가하는 것입니다. Storable 인스턴스를 작성하고 데이터에 대한 포인터를 유지하십시오. 모든 작업을 수행하는 C 함수를 작성하고 FFI를 통해 호출하거나, peek 및 poke를 사용하여 저수준의 Haskell을 작성하여 필요한 데이터 부분 만 조작 할 수 있습니다. 앞뒤로 모든 것을 마샬링 (즉, 전체 데이터 구조에서 peek 또는 poke )하는 것은 비용이 많이 들지만 포인터를 전달하는 것만으로는 비용을 최소화 할 수 있습니다.

FFI를 통해 가져온 기능을 호출하면 "안전하지 않음"으로 표시되지 않는 한 큰 페널티가 발생합니다. import "unsafe"를 선언하면 함수가 Haskell 또는 정의되지 않은 동작 결과를 다시 호출하면 안됩니다. 동시성이나 병렬 처리를 사용한다면, 같은 기능 (예 : CPU)에있는 모든 하스켈 스레드가 호출이 반환 될 때까지 블록되므로 상당히 빨리 반환되어야합니다. 이러한 조건이 수용 가능한 경우 "안전하지 않은"통화는 상대적으로 빠릅니다.

이런 종류의 것을 다루는 많은 패키지가 Hackage에 있습니다. 나는 hsndfilehCsoundhCsound 로 좋은 연습을하는 것으로 추천 할 수있다. 비록 당신이 잘 알고있는 작은 C 라이브러리에 대한 바인딩을 살펴보면 아마 더 쉽습니다.





ffi