【问题标题】:References to same data and memory allocation对相同数据和内存分配的引用
【发布时间】:2013-07-14 21:19:57
【问题描述】:

请考虑以下数据模型:

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

您可以看到Artists 被Songs 和Catalogue 引用。 Catalogue 包含从Songs 引用的所有艺术家的列表,因此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 的相同值来填充 Songs 所引用的值,因此通过不存储这些值的副本来节省内存。

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

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

所以我的问题是:

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

【问题讨论】:

    标签: pointers haskell memory


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

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

    【讨论】:

      【解决方案2】:

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

      {-# 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)
      

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

      【讨论】:

      • 关于泛型的想法很有趣。这个问题对于开发这样一个库来说已经足够普遍了。谢谢。我会调查的。
      • @NikitaVolkov 我很乐意参与。
      • 太好了!虽然我必须承认我还没有准备好参与这样的项目,但如果我能回来,我会与你保持联系。
      • 我偶然发现了一个项目,它解决了这个问题。见RefSerialize
      【解决方案3】:

      我偶然发现了一个解决问题的项目。见RefSerialize

      【讨论】:

        猜你喜欢
        • 2020-05-23
        • 1970-01-01
        • 1970-01-01
        • 2015-12-18
        • 2021-05-16
        • 1970-01-01
        • 2014-12-23
        • 2013-10-20
        • 1970-01-01
        相关资源
        最近更新 更多