【问题标题】:Efficient String Implementation in HaskellHaskell 中的高效字符串实现
【发布时间】:2009-02-23 00:50:53
【问题描述】:

我目前正在自学 Haskell,我想知道在 Haskell 中使用字符串时的最佳实践是什么。

Haskell 中的默认字符串实现是一个 Char 列表。根据Real World Haskell,这对于文件输入输出效率很低,因为每个字符都是单独分配的(我假设这意味着字符串基本上是 Haskell 中的链表,但我不确定。)

但是,如果默认的字符串实现对于文件 i/o 来说效率低下,那么在内存中处理字符串是否也效率低下?为什么或者为什么不? C 使用一个 char 数组来表示一个字符串,我认为这将是大多数语言的默认处理方式。

正如我所见,String 的列表实现将占用更多内存,因为每个字符都需要开销,并且需要更多时间来迭代,因为需要指针解引用才能到达下一个字符。但到目前为止我一直喜欢使用 Haskell,所以我想相信默认实现是高效的。

【问题讨论】:

  • 默认实现是最方便使用的,对于小字符串和想要对其执行的常见操作。对于您想基本上将其视为一个字节块的大字符串,它效率不高;使用 Data.ByteString 或 Data.ByteString.Lazy

标签: string haskell functional-programming


【解决方案1】:

除了 String/ByteString 之外,现在还有 Text 库,它结合了两全其美 - 它与 Unicode 兼容,同时内部基于 ByteString,因此您可以获得快速、正确的字符串。

【讨论】:

    【解决方案2】:

    在 Haskell 中高效处理字符串的最佳实践基本上是:使用 Data.ByteString/Data.ByteString.Lazy。

    http://hackage.haskell.org/packages/archive/bytestring/latest/doc/html/


    就 Haskell 中默认字符串实现的效率而言,它不是。每个Char 代表一个Unicode 代码点,这意味着每个Char 至少需要21 位。

    由于String只是[Char],即Char的链表,这意味着Strings的引用局部性较差,再次意味着Strings在内存中相当大,至少它是N * (21bits + Mbits),其中 N 是字符串的长度,M 是指针的大小(32、64,你有什么),与 Haskell 使用列表的许多其他地方不同,其他语言可能使用不同的结构(我'我在这里专门考虑控制流),Strings 不太可能被编译器优化为循环等。

    虽然Char 对应于一个代码点,但 Haskell 98 报告没有指定任何有关执行文件 IO 时使用的编码的信息,甚至没有指定默认值,更不用说更改它的方法。在实践中,GHC 提供了一个扩展,例如二进制 IO,但无论如何您都将取消保留。

    即使使用诸如在字符串前面添加的操作,String 在实践中也不太可能击败ByteString

    【讨论】:

    • +1 正是我要回答的包裹。 ByteString 将字符串作为偏移量存储到字节数组中。 Data.ByteString.Char8 允许您直接在 ByteStrings 中使用 Chars,假设只有低 8 位是重要的(即 ASCII)。 ByteString 也提供了自己的高效 IO 函数。
    • 由于堆对象的指针和标题,一个字符需要 2 个单词和一个列表节点 3,每个字符有 5 个单词 (wiki.haskell.org/GHC/Memory_Footprint)。在 64 位系统上,5 个单词每个字符 40 个字节。
    【解决方案3】:

    答案比“使用惰性字节串”要复杂一些。

    • 字节字符串每个值仅存储 8 位,而字符串保存真正的 Unicode 字符。因此,如果您想使用 Unicode,那么您必须始终在 UTF-8 或 UTF-16 之间进行转换,这比仅使用字符串更昂贵。不要错误地假设您的程序只需要 ASCII。除非它只是一次性代码,否则有一天有人需要输入一个欧元符号 (U+20AC) 或重音字符,那么您的快速字节串实现将无法挽回地被破坏。
    • 字节字符串使某些事情变得更加昂贵,例如添加到字符串的开头。

    也就是说,如果您需要性能并且可以纯粹用字节串表示数据,那么就这样做吧。

    【讨论】:

    • "不要误以为你的程序只需要 ASCII。" --wiki.c2.com/?YouArentGonnaNeedIt。即便如此,你也可以使用 Data.Text 来存储 unicode 打包字符串,而不是字符的链接列表。此外,在性能方面,unicode 往往比 ascii 更快,因为操作系统往往只处理 unicode 文本,如果在处理它们时使用 ascii,则需要进行转换。
    【解决方案4】:

    给出的基本答案,使用 ByteString,是正确的。也就是说,我之前的三个答案都有不准确之处。

    关于 UTF-8:这是否会成为问题完全取决于您对字符串进行何种处理。如果您只是将它们视为单个数据块(包括连接等操作,但不拆分),或者执行某些有限的基于字节的操作(例如,以字节为单位查找字符串的长度,而不是在字符),你不会有任何问题。如果您使用的是 I18N,还有很多其他问题,只需使用 String 而不是 ByteString 将开始解决您将遇到的极少数问题。

    将单个字节添加到 ByteString 的前面可能比对 String 执行相同操作更昂贵。但是,如果您经常这样做,则可能会找到更便宜的方法来解决您的特定问题。

    但最终结果将是,对于原始问题的发布者:是的,字符串在 Haskell 中效率低下,尽管相当方便。如果您担心效率,请使用 ByteStrings,并将它们视为 Char8 或 Word8 的数组,具体取决于您的目的(ASCII/ISO-8859-1 与某种类型的 Unicode,或只是任意二进制数据)。通常,使用惰性字节字符串(在字符串开头添加实际上是一个非常快速的操作),除非您知道为什么要使用非惰性字节字符串(这通常包含在对惰性求值的性能方面的欣赏中)。

    不管怎样,我正在完全用 Haskell 构建一个自动交易系统,我们需要做的一件事是非常快速地解析我们通过网络连接接收到的市场数据馈送。我每秒可以处理读取和解析 300 条消息,而 CPU 数量可以忽略不计;就处理这些数据而言,GHC 编译的 Haskell 的性能与 C 非常接近,因此远未进入我的显着问题列表。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-02-07
      • 1970-01-01
      • 2015-02-13
      • 2013-08-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-05-05
      相关资源
      最近更新 更多