Haskellのメモリ効率-より良いアプローチはどれですか?


11

修正された2次元文法構文に基づいて、マトリックス圧縮ライブラリを実装しています。データ型に対して2つのアプローチがあります-メモリ使用量の場合、どちらが良いでしょうか?(何かを圧縮したい;))。

文法には、プロダクションが4つだけの非ターミナル、または右側にターミナルが含まれています。同等性チェックと文法最小化のためにプロダクションの名前が必要になります。

最初:

-- | Type synonym for non-terminal symbols
type NonTerminal = String

-- | Data type for the right hand side of a production
data RightHandSide = DownStep NonTerminal NonTerminal NonTerminal NonTerminal | Terminal Int

-- | Data type for a set of productions
type ProductionMap = Map NonTerminal RightHandSide

data MatrixGrammar = MatrixGrammar {
    -- the start symbol
    startSymbol :: NonTerminal,
    -- productions
    productions :: ProductionMap    
    } 

ここで、RightHandSideデータは、次の制作を決定するために文字列名のみを保存します。ここでは、Haskellがこれらの文字列を保存する方法を知りません。たとえば、[[0、0]、[0、0]]マトリックスには2つの生成があります。

a = Terminal 0
aString = "A"
b = DownStep aString aString aString aString
bString = "B"
productions = Map.FromList [(aString, a), (bString, b)]

ここでの質問は、ストリング「A」が実際に保存される頻度です。aStringに1回、bに4回、プロダクションに1回、またはaStringに1回だけで、他は単に「安い」参照を保持していますか?

二番目:

data Production = NonTerminal String Production Production Production Production
                | Terminal String Int 

type ProductionMap = Map String Production

ここで、「ターミナル」という用語は、実際にはターミナルが右側にあるプロダクションであるため、少し誤解を招く可能性があります。同じマトリックス:

a = Terminal "A" 0
b = NonTerminal "B" a a a a
productions = Map.fromList [("A", a), ("B", b)]

同様の質問:制作物はHaskellによって内部的にどのくらいの頻度で保存されますか?名前が必要なければプロダクション内に名前をドロップするかもしれませんが、これについては今のところわかりません。

約1000のプロダクションを含む文法があるとします。どのアプローチがより少ないメモリを消費しますか?

最後に、Haskellの整数に関する質問:現在、文字列として名前を持つことを計画しています。しかし、1000個のプロダクションでは4文字を超える名前を使用するため、整数名に簡単に切り替えることができます(これは32ビットですか?)。Haskellはこれをどのように処理しますか。Intは常に32ビットで、Integerは本当に必要なメモリを割り当てますか?

私もこれを読みます:Haskellの値/参照セマンティクスのテストを考案します -しかし、それが私たちにとって正確に何を意味するのか理解できません-私はより良い命令型のJavaの子であり、優れた機能的なプログラマーです:P

回答:


7

マトリックスグラマーをADTに拡張して、少しの工夫で完全に共有できます。

{-# LANGUAGE DeriveFunctor, DeriveFoldable, DeriveTraversable #-}

import Data.Map
import Data.Foldable
import Data.Functor
import Data.Traversable

-- | Type synonym for non-terminal symbols
type NonTerminal = String

-- | Data type for the right hand side of a production
data RHS a = DownStep NonTerminal NonTerminal NonTerminal NonTerminal | Terminal a
  deriving (Eq,Ord,Show,Read,Functor, Foldable, Traversable)

data G a = G NonTerminal (Map NonTerminal (RHS a))
  deriving (Eq,Ord,Show,Read,Functor)

data M a = Q (M a) (M a) (M a) (M a) | T a
  deriving (Functor, Foldable, Traversable)

tabulate :: G a -> M a
tabulate (G s pm) = loeb (expand <$> pm) ! s where
  expand (DownStep a11 a12 a21 a22) m = Q (m!a11) (m!a12) (m!a21) (m!a22)
  expand (Terminal a)               _ = T a

loeb :: Functor f => f (f b -> b) -> f b
loeb x = xs where xs = fmap ($xs) x

ここでは、Intだけでなく任意のデータ型を許可するように文法を一般化し、を使用して文法をtabulate取得し、それを折り畳んで展開しloebます。

loebDan Piponiの記事に記載されています

結果として生じるADTとしての拡張は、元の文法よりも物理的に多くのメモリを必要としません。実際には、Mapスパイン用の追加のlog-factorを必要とせず、格納する必要がないので、かなり少なくなります。文字列。

単純な展開とは異なり、を使用loebすると、「結び目を作る」ことができ、同じ非端末のすべての出現に対してサンクを共有できます。

このすべての理論をさらに詳しく調べたいRHS場合は、基本ファンクターに変換できることがわかります。

data RHS t nt = Q nt nt nt nt | L t

それから私のMタイプはそれの不動点ですFunctor

M a ~ Mu (RHS a)

一方G a、選択された文字列と文字列からへのマップで構成されます(RHS String a)

次に、展開GMれた文字列のマップでエントリを遅延検索することにより、展開できます。

これは、data-reifyパッケージで行われるものの二重の一種であり、このような基本ファンクターを使用することができMますG。非終端名には異なるタイプを使用しますが、これは基本的に単なるInt

data Graph e = Graph [(Unique, e Unique)] Unique

コンビネータを提供します

reifyGraph :: MuRef s => s -> IO (Graph (DeRef s))

上記のデータ型の適切なインスタンスで使用して、任意の行列からグラフ(MatrixGrammar)を取得できます。同一であるが別々に保存された象限の重複排除は行いませんが、元のグラフに存在するすべての共有を回復します。


8

Haskellでは、文字列型は[Char]のエイリアスであり、ベクトルまたは配列ではなく、Charの通常のHaskell リストです。Charは、単一のUnicode文字を保持するタイプです。文字列リテラルは、言語拡張機能を使用しない限り、文字列型の値です。

上記から、Stringは非常にコンパクトな、または効率的な表現ではないと推測できると思います。文字列の一般的な代替表現には、Data.TextおよびData.ByteStringによって提供される型が含まれます。

さらに便利なように、-XOverloadedStringsを使用して、Data.ByteString.Char8で提供されるような代替の文字列型の表現として文字列リテラルを使用できます。これはおそらく、文字列を識別子として便利に使用するための最もスペース効率の高い方法です。

Intに関する限り、これは固定幅タイプですが、値[-2 ^ 29 .. 2 ^ 29-1]を保持するのに十分な幅でなければならないことを除いて、それがどのくらいの幅であるかについての保証はありません。これは、少なくとも32ビットであることを示唆していますが、64ビットであることを排除するものではありません。Data.Intには、特定の幅が必要な場合に使用できるInt8-Int64というより具体的な型がいくつかあります。

編集して情報を追加

Haskellのセマンティクスがデータ共有について何かを指定しているとは思わない。2つの文字列リテラル、または構築されたデータのうちの2つがメモリ内の同じ「標準」オブジェクトを参照することを期待しないでください。構築された値を(let、パターンマッチなどを使用して)新しい名前にバインドする場合、両方の名前は同じデータを参照する可能性が高くなりますが、実際に表示されるかどうかは不変の性質により実際には表示されませんHaskellデータ。

ストレージの効率を上げるために、文字列をインターンすることができます。これは、基本的に、ある種のルックアップテーブル(通常はハッシュテーブル)にそれぞれの正規表現を格納します。オブジェクトをインターンすると、その記述子が返されます。また、それらの記述子を他の記述子と比較して、文字列よりもはるかに安価で同じかどうかを確認できます。

インターンを行うライブラリの場合、https://github.com/ekmett/intern/を使用できます

実行時に使用する整数サイズの決定に関しては、具体的な数値型ではなく、IntegralまたはNum型のクラスに依存するコードを書くのはかなり簡単です。型推論は、自動的に可能な最も一般的な型を提供します。その後、特定の数値型に明示的に絞り込まれた型を持ついくつかの異なる関数を使用して、実行時に初期設定を行うことができ、その後、他のすべての多態性関数はそれらのいずれでも同じように機能します。例えば:

polyConstructor :: Integral a => a -> MyType a
int16Constructor :: Int16 -> MyType Int16
int32Constructor :: Int32 -> MyType Int32

int16Constructor = polyConstructor
int32Constructor = polyConstructor

編集:インターンに関する詳細情報

文字列だけをインターンしたい場合は、文字列(できればTextまたはByteString)と小さな整数を一緒にラップする新しい型を作成できます。

data InternedString = { id :: Int32, str :: Text }
instance Eq InternedString where
    {x, _ } == {y, _ }  =  x == y

intern :: MonadIO m => Text -> m InternedString

「intern」は、テキストがキーであり、InternedStringsが値である弱参照HashMapで文字列を検索します。一致が見つかった場合、「intern」は値を返します。そうでない場合、元のTextと一意の整数idで新しいInternedString値を作成します(これがMonadIO制約を含めた理由です。一意のidを取得する代わりにStateモナドまたは安全でない操作を使用できます。多くの可能性があります)返す前にマップに保存します。

これで、整数IDに基づいて高速比較を取得し、一意の各文字列のコピーを1つだけ保持できます。

Edward Kmettのインターンライブラリは、構造化されたデータ用語全体がハッシュされ、一意に保存され、高速な比較操作が行われるように、ほぼ同じ方法でほぼ同じ原理を適用します。それは少し気が遠く、特に文書化されていませんが、あなたが尋ねるなら彼は喜んで助けてくれるかもしれません。または、最初に独自の文字列インターンの実装を試して、それが十分に役立つかどうかを確認できます。


これまでお答えいただきありがとうございます。実行時に使用するintサイズを決定することは可能ですか?他の誰かがコピーに関する問題について何らかの情報を提供できることを願っています:)
デニスIch

追加情報をありがとう。見てみましょう。ちょうどそれを正しくするために、この記述子が言っているのは、ハッシュされて比較できる参照のようなものですか?これであなたは自分で働きましたか?一見すると、文法を定義する際に非常に注意しなければならないように見えるので、これでどのように「より複雑」になるのか、言うことができます;)
デニスIch

1
そのライブラリの作成者は、質の高い仕事で知られる非常に高度なHaskellユーザーですが、私はその特定のライブラリを使用していません。それは非常に汎用的で保存し、での表現の共有を可能にする「ハッシュ短所」の実装、任意の構成されたデータタイプ、文字列だけでなく。似たような問題については、彼のサンプルディレクトリをご覧ください。等式関数がどのように実装されているかがわかります。
レヴィピアソン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.