在 Haskell 中解析大型日志文件

2024-01-01

假设我有几个 200mb+ 的文件想要 grep 遍历。我该如何在 Haskell 中做到这一点?

这是我的初始程序:

import Data.List
import Control.Monad
import System.IO
import System.Environment

main = do
  filename <- liftM head getArgs
  contents <- liftM lines $ readFile filename
  putStrLn . unlines . filter (isPrefixOf "import") $ contents

这会在解析之前将整个文件读入内存。 然后我就这样做了:

import Data.List
import Control.Monad
import System.IO
import System.Environment

main = do
  filename <- liftM head getArgs
  file <- (openFile filename ReadMode)
  contents <- liftM lines $ hGetContents file
  putStrLn . unlines . filter (isPrefixOf "import") $ contents

我以为自从hGetContents是懒惰,它将避免将整个文件读入内存 http://book.realworldhaskell.org/read/io.html#io.lazy。但是在下面运行这两个脚本valgrind两者的内存使用情况相似。所以要么我的脚本是错误的,要么valgrind是错的。我使用编译脚本

ghc --make test.hs -prof

我缺少什么?额外问题:我看到很多人提到 Haskell 中的 Lazy IO 实际上是一件坏事。我如何/为什么要使用严格 IO?

Update:

所以看起来我对 valgrind 的理解是错误的。使用+RTS -s,这就是我得到的:

 7,807,461,968 bytes allocated in the heap
 1,563,351,416 bytes copied during GC
       101,888 bytes maximum residency (1150 sample(s))
        45,576 bytes maximum slop
             2 MB total memory in use (0 MB lost due to fragmentation)

Generation 0: 13739 collections,     0 parallel,  2.91s,  2.95s elapsed
Generation 1:  1150 collections,     0 parallel,  0.18s,  0.18s elapsed

INIT  time    0.00s  (  0.00s elapsed)
MUT   time    2.07s  (  2.28s elapsed)
GC    time    3.09s  (  3.13s elapsed)
EXIT  time    0.00s  (  0.00s elapsed)
Total time    5.16s  (  5.41s elapsed)

重要的一行是101,888 bytes maximum residency,它表示在任何给定点我的脚本最多使用 101 kb 内存。我正在查找的文件有 44 mb。所以我认为判决是:readFile and hGetContents两人都很懒。

后续问题:

为什么我看到堆上分配了 7GB 内存?对于读取 44 MB 文件的脚本来说,这似乎非常高。

更新后续问题

看起来在堆上分配几 GB 的内存对于 Haskell 来说并不罕见,所以没有理由担心。使用ByteStrings 而不是Strings 使内存使用量下降很多:

  81,617,024 bytes allocated in the heap
      35,072 bytes copied during GC
      78,832 bytes maximum residency (1 sample(s))
      26,960 bytes maximum slop
           2 MB total memory in use (0 MB lost due to fragmentation)

请不要使用Strings(尤其是在处理 >100 Mb 文件时)。 只需将它们替换为ByteStrings (or Data.Text):

{-# LANGUAGE OverloadedStrings #-}

import Control.Monad
import System.Environment
import qualified Data.ByteString.Lazy.Char8 as B

main = do
  filename <- liftM getArgs
  contents <- liftM B.lines $ B.readFile filename
  B.putStrLn . B.unlines . filter (B.isPrefixOf "import") $ contents

我敢打赌,这会快几倍。

UPD:关于你的后续问题。
分配的内存量与切换到字节串时的神奇加速密切相关。
As String只是一个通用列表,每个列表都需要额外的内存Char:指向下一个元素、对象头等的指针。所有这些内存都需要分配然后回收。这需要大量的计算能力。
另一方面,ByteString是一个列表chunks,即连续的内存块(我认为每个内存块不少于 64 字节)。这大大减少了分配和收集的数量,并提高了缓存局部性。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在 Haskell 中解析大型日志文件 的相关文章

  • 测试列表是否已排序

    在 haskell 中找到最小列表确实很容易 foldl1 min 9 5 7 3 7 4 6 10 给我3 我更换了min with lt 测试列表是否已排序 foldl1 lt 9 5 7 3 7 4 6 10 我收到此错误消息 No
  • 结构上强制的自由替代,没有左派分配性

    有一个不错的免费替代品 http hackage haskell org package free 4 12 4 docs Control Alternative Free html在伟大的free包 它将函子提升到左分配替代方案 也就是说
  • 仪器化状态单子

    我正在努力给予Monad and MonadState的实例State 计算的数量 gt gt return get and put运营 data Counts Counts binds Int returns Int gets Int p
  • 绑定变量时 Haskell 中的无限循环

    下面的 Haskell 代码不会终止 有人可以解释一下为什么吗 谢谢 f let x 10 in let x x x in x 我认为解释器首先绑定 x 10 然后将 x x 计算为 100 并绑定 x 100 环境变为 x 100 那么整
  • Haskell:如何创建将函数应用于元组项的最通用函数

    这是一个个人练习 旨在更好地理解 Haskell 类型系统的局限性 我想创建最通用的函数 将某些函数应用于 2 条目元组中的每个条目 例如 applyToTuple fn a b fn a fn b 我试图让这个函数在以下每种情况下都起作用
  • yesod——密码保护临时站点

    我正在尝试设置 yesod 网络服务器的临时实例 我想知道是否有一些简单的方法可以使整个站点受到密码保护 具体来说 我希望能够提示那些导航到我的网站的人提供凭据 经过身份验证后 它应该像典型站点一样运行 但如果他们无法验证自己的身份 他们就
  • 为什么这会导致 Haskell Conduit 库内存泄漏?

    我有一个conduit https hackage haskell org package conduit管道处理长文件 我想每 1000 条记录为用户打印一份进度报告 所以我这样写 Every n records perform the
  • 在 Haskell 中为自定义数据类型创建 Read 类型类的实例

    我有一个自定义数据类型Foo Foo a Int b Int 我正在尝试使 Foo 成为 read 的自定义实例 我已经有一个功能了bar String gt Foo我尝试这样做 instance Read Foo a b where re
  • 使用 Haskell 的欧拉项目 #1

    import Data Set euler Int euler sum x x lt nums where nums Data Set toList Data Set union Data Set fromList 3 6 999 Data
  • 如何向 Scotty 中间件添加基本身份验证?

    我目前正在制作 Scotty API 但找不到任何 basicAuth 实现的示例 Wai Middleware HttpAuth 具体来说 我想将基本身份验证标头 用户 通行证 添加到我的某些端点 即以 admin 开头的端点 我已经设置
  • 谁能解释一下 GHC 对 IO 的定义吗?

    标题非常自我描述 但有一个部分引起了我的注意 newtype IO a IO State RealWorld gt State RealWorld a 剥离newtype 我们得到 State RealWorld gt State Real
  • 列表理解:制作列表列表

    你好 我正在尝试在 haskell 中创建一个函数 该函数接受一个数字 a 使用列表 即数字 将其一部分4它会创造 1 1 1 1 1 1 2 1 3 2 2 4 我正在考虑使用列表理解来创建列表 x 然后使用 1 n 中的数字创建更多列表
  • 如何将只缓存某些内容的字段添加到ADT?

    我经常需要向 ADT 添加字段 仅记住一些冗余信息 但我还没有完全弄清楚如何又好又高效地做到这一点 说明问题的最好方法是举个例子 假设我们正在使用无类型 lambda 项 type VSym String data Lambda Var V
  • 我应该使用什么递归方案来重复有效的操作,直到其结果符合某些标准?

    也就是说 我要问的是一个循环 effectful Int gt IO Int effectful n do putStrLn Effect show n return n condition 3 final Int gt IO final
  • 有没有办法在 Emacs 中使用 Djinn 自动生成 Haskell 代码?

    标题几乎说明了一切 我正在寻找这样的东西 f Int gt Bool gt Int f body Djinn 可以使用定理证明来通过证明该类型存在来生成此类函数的代码 我想知道 是否有现有的方法可以从 Emacs 中获取此功能 因此 我不需
  • 什么是阴谋地狱?

    在阅读有关 阴谋地狱 的内容时 我有点困惑 因为这个词的含义太多了 我猜最初 Cabal Hell 指的是钻石依赖问题 该问题是通过限制构建计划在每个构建计划中只有任何包的单个版本来解决的 一个包的两个不同版本不能存在于单个构建计划中 正如
  • 如何处理在组合下发生变化的类型?

    我最近读了一篇非常有趣的论文单调性类型 https infoscience epfl ch record 231867 files monotonicity types pdf其中描述了一种新的 HM 语言 该语言可以跟踪操作之间的单调性
  • 类型级编程有哪些示例? [关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 我不明白 类型级编程 是什么意思 也无法使用Google找到合适的解释 有人可以提供一个演示类型级编程的示例吗 范式的解释和 或定义将
  • Parsec 函数“parse”和类“Stream”的类型签名

    约束条件是什么 Stream s Identity t 下面的类型声明是什么意思 parse Stream s Identity t gt Parsec s a gt SourceName gt s gt Either ParseError
  • Haskell 类型定义,=> 等

    我正在使用 Learn You a Haskell 来学习 Haskell 第 54 页上有一个 像这样执行 take Num i Ord i gt i gt a gt a take n n lt 0 take take n x xs x

随机推荐