7

请考虑以下数据模型:

data Artist = Artist Text
data Song = Song Artist Text
data Catalogue = Catalogue (Set Artist) (Set Song)

您可以看到s 和Artists 都引用了Songs CatalogueCatalogue包含从 s 引用的所有艺术家的列表,因此从两个地方引用Song相同的值。Artist

Catalogue假设我们要使用以下函数的多个应用程序来生成值:

insertSong :: Song -> Catalogue -> Catalogue
insertSong song@(Song artist title) (Catalogue artists songs) =
  Catalogue (Set.insert artist artists) (Set.insert song songs)

很明显, s 将通过引用与s 所指Catalogue的相同值来填充,从而通过不存储这些值的副本来节省内存。ArtistSong

问题是,当我尝试通过分别反序列化一组艺术家和一组歌曲来从序列化数据中重新创建目录时,应用程序占用的内存比它生成相同的Cataloguewith值时要多得多insertSong。我怀疑这是由于s 和Artists 引用的相同 s 之间的关系丢失引起的,这就是为什么我得到占用额外内存的值的副本。SongCatalogueArtist

我看到的唯一解决方案是首先反序列化一组艺术家,然后反序列化一组歌曲,同时用Artist第一组中的值强制替换 的值。

所以我的问题是:

  1. 我的怀疑是对的吗?
  2. 我看到的解决方案会起作用吗?
  3. 有没有更好的方法来解决这个问题?
4

3 回答 3

6
  1. 这听起来很有道理。
  2. 如果操作正确,它应该可以工作。特别是,您必须确保对所有内容进行热切评估,以避免从 thunk 中引用旧的 Text 值。
  3. 您可以选择更智能的序列化格式。例如,当您序列化歌曲时,将艺术家的索引存储在艺术家列表中,而不是完整的艺术家名称。然后在反序列化过程中查找它。

请注意,如果您对字符串进行任何类型的计算,共享也会丢失(即,即使artist1artist2相同且共享,f artist1f artist2可能不共享)。如果这成为问题,您也可以对数据结构进行类似的更改。

于 2013-07-14T22:07:59.603 回答
3

一个简单的解决方案似乎使用有点退化的地图来缓存数据:

{-# LANGUAGE DeriveDataTypeable, RankNTypes #-}
import Control.Monad
import Control.Monad.State
import Data.Map (Map)
import qualified Data.Map as M

type Cache a = Map a a

如果已经有一个与这个相等的条目,我们可以查询这个缓存,并用缓存的那个替换它:

cached :: (Ord a) => a -> State (Cache a) a
cached x = state $ \m ->
    case M.lookup x m of
        Just x'     -> (x', m)
        Nothing     -> (x, M.insert x x m)

这样,如果我们加载几个相同的 type 元素a,我们会将它们转换为一个。这可以在反序列化期间或结束时完成。


也许可以进一步概括它并使用 SYB 通过缓存映射数据结构中某个给定类型的所有值:

import Data.Data (Data)
import Data.Generics.Aliases (mkM)
import Data.Generics.Schemes (everywhereM)
import Data.Typeable (Typeable)

replaceFromCache
    :: (Ord a, Typeable a, Data b)
    => b -> State (Cache a) b
replaceFromCache = everywhereM (mkM cached)

然后我们可以替换一些数据结构中的所有艺术家,比如

data Artist = Artist String
  deriving (Eq, Ord, Typeable)

cacheAllArtists :: (Data b) => b -> b
cacheAllArtists b = evalState (replaceFromCache b) (M.empty :: Cache Artist)

或者我们可以使用Proxy幻像类型来创建一个通用版本:

cacheAll :: (Ord a, Typeable a, Data b)
      => Proxy a -> b -> b
cacheAll p = flip evalState (emptyOf p) . replaceFromCache
  where
    emptyOf p = asTypeOf2 M.empty p
    asTypeOf2 :: f a -> Proxy a -> f a
    asTypeOf2 = const

cacheAllArtists :: (Data b) => b -> b
cacheAllArtists = cacheAll (Proxy :: Proxy Artist)

(免责声明:我没有测试任何上述代码。)

于 2013-07-15T00:59:13.583 回答
1

我不小心偶然发现了一个解决问题的项目。请参阅RefSerialize

于 2014-04-13T10:12:28.730 回答