12

私の Haskell のベクター グラフィックス ライブラリでは、かなり大きな状態 (ライン ストロークのパラメーター、色、クリップ パスなど) を持ち歩かなければなりません。これを行う方法は 2 つあります。Haskell-cafeからのコメントを引用すると、「可変状態のリーダー モナドか、不変状態の状態モナドを使用することをお勧めします」。

これが私の問題です。大きな不変状態を更新すると、パフォーマンスが低下します。多くの STRef を使用することは、Haskell で C を書くようなものです。冗長で醜いです。

不変の状態は次のとおりです。

data GfxState = GfxState {
  lineWidth :: Double,
  lineCap :: Int,
  color :: Color,
  clip :: Path,
  ...
}

setLineWidth :: Double -> State GfxState ()
setLineWidth x = modify (\state -> state { lineWidth = x })

私が知る限り、「state { lineWidth = x }」は新しい GfxState を作成し、古いものをガベージ コレクションできるようにします。状態が大きく、頻繁に更新される場合、これはパフォーマンスを低下させます。

変更可能な状態は次のとおりです。

data GfxState s = GfxState {
  lineWidth :: STRef s Double,
  lineCap   :: STRef s Int,
  color     :: STRef s Color,
  clip      :: STRef s Path,
  ...
  many more STRefs
}

setLineWidth :: GfxState s -> Double -> ST s ()
setLineWidth state x = writeSTRef (lineWidth state) x

(GfxState s) と (ST s) と (STRef s) がいたるところに表示されるようになりました。これは、冗長で紛らわしく、短くて表現力豊かなコードを書く精神に勝っています。C + FFI を使用して大きな状態を読み取って更新することもできますが、このパターンに頻繁に遭遇するので、もっと良い方法があることを願っています。

4

3 に答える 3

10

まず第一に、あなたはそれが遅くなるだろうと主張しているだけですか、それともあなたはプロファイリングしたか、少なくともパフォーマンスの問題に気づきましたか?そうでなければ、推測したり仮定したりすることは特に役に立ちません。とにかく、データをグループ化することをお勧めします。現時点では、行に関連するデータなどの関連データをレコードにグループ化できる場合、構造を完全にフラットにレイアウトしているように見えます。

また、実際に状態モナドにある必要があるビットと、リーダー/ライターモナドに入らない他のビットを分離し、モナド変換子を使用してそれらを結合することもできます。コードの優雅さに関しては、fclabelsのような(ファーストクラス/高次の)レコードライブラリを使用することをお勧めします。

私はいくつかの小さなプロジェクトで(モナド変換子のスタックで)状態モナドを多用しましたが、パフォーマンスの問題にはまだ気づいていません。

最後に、get/putペアの代わりにmodifyを使用できます。

于 2010-11-24T10:43:19.323 に答える
8

レコードにかなりの数のフィールドがある場合でも、「新しいフィールドを作成する」とは、ポインターをコピーすることを意味します。また、「古いものをガベージコレクションにする」とは、GHCの世代別ガベージコレクターの処理が非常に高速であるように、ポインターごとに数バイトを解放することを意味します。それはすべて、ほんの一握りの機械命令に要約されます。したがって、グラフィックアプリケーションの場合でも、パフォーマンスがまったく低下しない可能性があります。

それが実際にパフォーマンスに影響を与えることが確実な場合は、フィールドをツリーに編成します。ネストされた型を使用して固定形状のツリーを作成することdataも、単にを使用することもできますData.IntMap。これにより、平均的なlog n / 2ポインタコピーが得られます。特定のフィールドがはるかに頻繁にアクセスされることがわかっている場合は、さらにうまくいくことができます。

状態が非常に複雑で、パフォーマンス要件が非常に重いため、唯一のオプションがSTRefフィールドである非常にまれなアプリケーションです。しかし、オプションがあることを知っておくのはいいことです。

于 2010-11-24T15:36:57.503 に答える
6

余談ですが、パフォーマンスが心配な場合は、ボックス化を解除してデータ型の表現を改善する必要があります。

data GfxState = GfxState {
  lineWidth :: {-# UNPACK #-}!Double,
  lineCap   :: {-# UNPACK #-}!Int,
  color     :: {-# UNPACK #-}!Color,
  clip      :: Path,
  ...
}

コンストラクターを解凍すると、次のようなヒープ構造からデータの密度が向上します。

ここに画像の説明を入力してください

より密度が高く、より厳密に:

ここに画像の説明を入力してください

これで、すべてのアトミックタイプが連続したメモリスロットに配置されます。このタイプの更新ははるかに高速になります!ところで、461..はpiフィールドのWord表現です。これは私のビューアライブラリのバグです。

また、スペースリークの可能性を減らすことができます。

コンポーネントがレジスタに格納されるため、このような構造を渡すコストは非常に安くなります。

于 2011-05-09T01:46:18.873 に答える