引用相同的数据和内存分配

时间:2013-07-14 21:19:57

标签: pointers haskell memory

请考虑以下数据模型:

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

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

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

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

很明显,Catalogue会被Artist所引用的Song的相同值引用填充,从而通过不存储这些值的副本来节省内存

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

我看到的唯一解决方案是先对这组艺术家进行反序列化,然后在强制性地将Artist的值替换为第一组中的值时反序列化这组歌曲。

所以我的问题是:

  1. 我怀疑是否正确?
  2. 我认为解决方案有效吗?
  3. 有没有更好的方法可以解决这个问题?

3 个答案:

答案 0 :(得分:6)

  1. 听起来似乎有道理。
  2. 如果做得对,它应该有效。特别是,您必须确保对所有内容进行热切评估,以避免从thunk中引用旧的Text值。
  3. 您可以选择更智能的序列化格式。例如,序列化歌曲时,请将艺术家的索引存储在艺术家列表中,而不是完整的艺术家名称。然后在反序列化期间查找。
  4. 请注意,如果您对字符串进行任何类型的计算,也会丢失共享(即使artist1artist2相同并且共享,f artist1和{{1可能不是)。如果这成为问题,您也可以对数据结构进行类似的更改。

答案 1 :(得分: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)

这样,如果我们加载几个类型为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)

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

答案 2 :(得分:1)

我偶然发现了一个接近问题的项目。请参阅RefSerialize