Haskell 中的内存高效字符串
Posted
技术标签:
【中文标题】Haskell 中的内存高效字符串【英文标题】:Memory efficient strings in Haskell 【发布时间】:2012-02-22 16:14:25 【问题描述】:通常推荐的 Haskell 字符串类型似乎是 ByteString 或 Text。我经常使用大量的短(英文单词大小)字符串,并且通常需要将它们存储在 Data.Map 等查找表中。在许多情况下,我发现在这种情况下,字符串表占用的内存比字节字符串表要少。 Word8 的 Unboxed Data.Vectors 也(很多)比 ByteStrings 紧凑。
当需要在 Haskell 中存储和比较大量小字符串时,最佳实践是什么?
下面我尝试将一个特定的有问题的案例浓缩成一个小例子:
import qualified Data.ByteString.Lazy.Char8 as S
import qualified Data.ByteString as Strict
import qualified Data.Map as Map
import qualified Data.Vector.Unboxed as U
import qualified Data.Serialize as Serialize
import Control.Monad.State
main = putStr
. unlines . map show . flip evalState (0,Map.empty)
. mapM toInt
. S.words
=<<
S.getContents
toInt x = do
let x' =
U.fromList . Strict.unpack . -- Comment this line to increase memory usage
Serialize.encode $ x
(i,t) <- get
case Map.lookup x' t of
Just j -> return j
Nothing -> do
let i' = i + (1::Int)
put (i', Map.insert x' i t)
return i
当我在一个包含大约 400.000 个英文文本的文件上运行此程序时,带有严格字节串键的版本使用大约 50MB 内存,带有 Word8 向量的版本使用 6MB。
【问题讨论】:
您能否举一些代码示例,其中 ByteStrings 比 Strings 占用更多内存或比 Word8 向量“多得多”的内存?我不明白为什么会这样,除非你在做一些奇怪的事情。 @shang:如果您错误地将充满严格 ByteStrings 的映射的大小与包含字符串 thunk 的映射进行比较,我可以想象会发生这种情况。虽然更多细节会有所帮助。演示该问题的简短测试程序会特别好。 @hammar:是的,这是一种选择。另一个可能是您正在从一个大的 ByteString 中分割单词并保留对它的引用。 你可能想看看Data.Trie。 另见短字节串hackage.haskell.org/package/bytestring-0.10.4.0/docs/… 【参考方案1】:在没有其他答案的情况下,我将在这里冒险。
当需要在 Haskell 中存储和比较大量小字符串时,最佳实践是什么?
如果小字符串是人类可读的(例如英文单词),则使用Text
。如果它们只能由计算机读取,请使用ByteString
。使用严格或惰性变体的决定取决于您如何构建和使用这些小字符串。
您不需要使用自己未装箱的Vector
s 或Word8
。如果您遇到常规String
比Text
或ByteString
快的特定情况,请在 *** 上提供详细信息,我们将尝试找出原因。如果您进行详细分析并可以证明Word8
的未装箱Vector
始终比Text
或ByteString
工作得更好,然后开始在邮件列表、irc、reddit 等上进行对话;标准库不是一成不变的,总是欢迎改进。
但我认为你很可能只是在做一些奇怪的事情,正如 hammar 和 shang 所暗示的那样。
附:对于您的特定用例,您应该考虑更合适的数据结构来满足您的需求,而不是存储大量小字符串,例如danr 建议的 Trie。
【讨论】:
对 short 字符串进行排序是常规String
性能优于ByteString
的一个地方(我不知道Text
,但我不会如果String
也能胜任这项任务,我会感到惊讶)。为什么这是显而易见的:ByteString
使用计数排序。【参考方案2】:
一个(严格的)ByteSting 是一个构造函数,将一个未装箱的 ForiegnPtr
转换为一个 Word8
和两个未装箱的 Ints。
ForeignPtr
是Addr#
(GHC prim)和ForeignPtrContents
之上的另一个构造函数:
data ForeignPtrContents
= PlainForeignPtr !(IORef (Finalizers, [IO ()]))
| MallocPtr (MutableByteArray# RealWorld) !(IORef (Finalizers, [IO ()]))
| PlainPtr (MutableByteArray# RealWorld)
...
对于短字符串,ByteStrings 只是打包了太多的管理,以使其对实际“字符串”数据的连续表示受益。
对于最初的问题 - 我会检查您的语料库的平均字长,但我看不出 ByteString 比 String aka [Char] 更有效,每个 Char 使用 12 个字节(来源原始 ByteString 论文)。
对 Haskellers 的一般请求(不是针对原始问题的发布者) - 请停止抨击 String aka [Char] - 同时拥有 String 和 Text(以及当你真正需要字节时的 ByteString)是有道理的。或者在连续字符串表示更适合短字符串的地方使用 Clean。
警告 - 我可能一直在查看旧版本的 ByteString 内部结构,了解它在内部使用的数据类型。
【讨论】:
【参考方案3】:我知道这是一篇已有 6 年历史的帖子,但我最近也有同样的疑惑,发现这篇有用的博文:https://markkarpov.com/post/short-bs-and-text.html。看来是的,这是一个公认的问题,Short(Text/ByteString) 是解决方案。
【讨论】:
以上是关于Haskell 中的内存高效字符串的主要内容,如果未能解决你的问题,请参考以下文章