如何在 Haskell 中将大数据块解析到内存中?

2023-12-28

经过反思,整个问题可以归结为更简洁的问题。我正在寻找一个 Haskell 数据结构

  • 看起来像一个列表
  • 查找时间复杂度为 O(1)
  • 具有 O(1) 元素替换orO(1) 元素附加(或前置...如果是这种情况,我可以反转我的索引查找)。我总是可以在编写我以后的算法时考虑其中之一。
  • 内存开销非常小

我正在尝试构建一个图像文件解析器。文件格式是基本的 8 位颜色 ppm 文件,但我打算支持 16 位颜色文件以及 PNG 和 JPEG 文件。现有的 Netpbm 库尽管有很多拆箱注释,但在尝试加载我使用的文件时实际上会消耗所有可用内存:

照片3-10张,最小45MB,最大110MB。

现在,我无法理解 Netpbm 代码中的优化,所以我决定自己尝试一下。这是一种简单的文件格式...

我首先决定,无论文件格式是什么,我都将以这种格式存储未压缩的最终图像:

import Data.Vector.Unboxed (Vector)
data PixelMap = RGB8 {
      width :: Int
    , height :: Int
    , redChannel :: Vector Word8
    , greenChannel :: Vector Word8
    , blueChannel :: Vector Word8
    }

然后我编写了一个解析器,它可以处理三个向量,如下所示:

import Data.Attoparsec.ByteString
data Progress = Progress {
      addr      :: Int
    , size      :: Int
    , redC      :: Vector Word8
    , greenC    :: Vector Word8
    , blueC     :: Vector Word8
    }

parseColorBinary :: Progress -> Parser Progress
parseColorBinary progress@Progress{..}
    | addr == size = return progress
    | addr < size = do
        !redV <- anyWord8
        !greenV <- anyWord8
        !blueV <- anyWord8
        parseColorBinary progress { addr    = addr + 1
                                  , redC    = redC V.// [(addr, redV)]
                                  , greenC  = greenC V.// [(addr, greenV)]
                                  , blueC   = blueC V.// [(addr, blueV)] }

在解析器的末尾,我像这样构造 RGB8:

Progress{..} <- parseColorBinary $ ...
return $ RGB8 width height redC greenC blueC

像这样编写的程序,加载一张 45MB 的图像,将消耗 5GB 或更多的内存。如果我改变定义Progress以便redC, greenC, and blueC都是!(Vector Word8),那么程序仍保持在合理的内存范围内,但加载单个文件需要很长时间,以至于我不允许它实际完成。最后,如果我用标准列表替换这里的向量,每个文件的内存使用量会回升到 5GB(我假设......在我点击那个之前我实际上已经用完了空间),and加载时间约为 6 秒。 Ubuntu 的预览应用程序一旦启动,几乎立即加载并呈现文件。

根据每次调用 V.// 实际上每次都完全复制向量的理论,我尝试切换到Data.Vector.Unboxed.Mutable,但是...我什至无法对其进行打字检查。该文档不存在,并且了解数据类型的用途将需要与多个other图书馆也是如此。而且我什至不知道它是否能解决问题,所以我什至不愿意尝试。

根本问题实际上非常简单:

如何在不使用大量内存的情况下快速读取、保留并可能操作非常大的数据结构?我发现的所有例子都是关于暂时生成大量数据然后尽快删除它的。

原则上,我希望最终表示是不可变的,但我不太关心是否必须使用可变结构才能到达那里。


为了完整起见,完整的代码(BSD 3-许可证)位于 bitbucket 中https://bitbucket.org/savannidgerinel/photo-tools https://bitbucket.org/savannidgerinel/photo-tools . The performance分支包含解析器的严格版本,可以通过快速更改来使其变得不严格Progress的数据结构Codec.Image.Netpbm.

运行性能测试

ulimit -Sv 6000000 -- set a ulimit of 6GB, or change to whatever makes sense for you
cabal build
dist/build/perf-test/perf-test +RTS -p -sstderr

我首先认为,只需读取整个字节串,然后将内容解压缩到未装箱的向量中就足够了。事实上,即使没有神秘的空间泄漏,您发布的解析代码也会相当糟糕:您在输入的每个字节上复制所有三个向量的全部!谈论二次复杂度。

所以我写了以下内容:

chunksOf3 :: [a] -> [(a, a, a)]
chunksOf3 (a:b:c:xs) = (a, b, c) : chunksOf3 xs
chunksOf3 _          = []

parseRGB :: Int -> Atto.Parser (Vector Word8, Vector Word8, Vector Word8)
parseRGB size = do
    input <- Atto.take (size * 3)
    let (rs, gs, bs) = unzip3 $ chunksOf3 $ B.unpack input
    return (V.fromList rs, V.fromList gs, V.fromList bs)

然后使用 45 Mb 的随机字节文件对其进行测试。我承认我很惊讶这段代码导致了千兆字节的 RAM 使用。我很好奇空间泄漏到底在哪里。

不过,可变向量效果很好。以下代码使用 133 Mb RAM,Criterion 将其基准测试为 60 毫秒文件读取。我在评论中添加了一些解释。 SO 和其他地方也有大量关于 ST monad 和可变向量的材料(我同意图书馆文档对初学者不友好)。

import Data.Vector.Unboxed (Vector)
import Data.ByteString (ByteString)

import qualified Data.Vector.Unboxed as V
import qualified Data.ByteString as B
import qualified Data.Vector.Unboxed.Mutable as MV

import Control.Monad.ST.Strict 
import Data.Word
import Control.Monad
import Control.DeepSeq

-- benchmarking stuff
import Criterion.Main (defaultMainWith, bench, whnfIO)
import Criterion.Config (defaultConfig, Config(..), ljust)

-- This is just the part that parses the three vectors for the colors.
-- Of course, you can embed this into an Attoparsec computation by taking 
-- the current input, feeding it to parseRGB, or you can just take the right 
-- sized chunk in the parser and omit the "Maybe" test from the code below. 
parseRGB :: Int -> ByteString -> Maybe (Vector Word8, Vector Word8, Vector Word8)
parseRGB size input 
    | 3* size > B.length input = Nothing
    | otherwise = Just $ runST $ do

        -- We are allocating three mutable vectors of size "size"
        -- This is usually a bit of pain for new users, because we have to
        -- specify the correct type somewhere, and it's not an exactly simple type.
        -- In the ST monad there is always an "s" type parameter that labels the
        -- state of the action. A type of "ST s something" is a bit similar to
        -- "IO something", except that the inner type often also contains "s" as
        -- parameter. The purpose of that "s" is to statically disallow mutable
        -- variables from escaping the ST action. 
        [r, g, b] <- replicateM 3 $ MV.new size :: ST s [MV.MVector s Word8]

        -- forM_ = flip mapM_
        -- In ST code forM_ is a nicer looking approximation of the usual
        -- imperative loop. 
        forM_ [0..size - 1] $ \i -> do
            let i' = 3 * i
            MV.unsafeWrite r i (B.index input $ i'    )
            MV.unsafeWrite g i (B.index input $ i' + 1)
            MV.unsafeWrite b i (B.index input $ i' + 2)

        -- freeze converts a mutable vector living in the ST monad into 
        -- a regular vector, which can be then returned from the action
        -- since its type no longer depends on that pesky "s".
        -- unsafeFreeze does the conversion in place without copying.
        -- This implies that the original mutable vector should not be used after
        -- unsafeFreezing. 
        [r, g, b] <- mapM V.unsafeFreeze [r, g, b]
        return (r, g, b)

-- I prepared a file with 3 * 15 million random bytes.
inputSize = 15000000
benchConf = defaultConfig {cfgSamples = ljust 10}

main = do
    defaultMainWith benchConf (return ()) $ [
        bench "parseRGB test" $ whnfIO $ do 
            input <- B.readFile "randomInp.dat" 
            force (parseRGB inputSize input) `seq` putStrLn "done"
        ]
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何在 Haskell 中将大数据块解析到内存中? 的相关文章

  • 如何利用磁盘 IO 队列

    我需要从 3 7 GB 文件中读取小数据序列 我需要阅读的职位是不相邻 但我可以命令 IO 以便从头到尾读取文件 该文件存储在 iSCSI SAN 上 该 SAN 应该能够处理 优化排队 IO 问题是 如何一次性请求我需要的所有数据 位置
  • jQuery UI .buttonset() 太慢

    我的 HTML 页面上有几千个按钮 运行需要10多秒 buttonset buttonset 文件准备好 有没有更快的方法来做到这一点 或者是我以某种方式限制按钮数量的唯一解决方案 创建buttonset在第一次显示之前按需进行 我刚刚测试
  • 高性能 C# 服务器套接字的提示/技术

    我有一个 NET 2 0 服务器似乎遇到了扩展问题 可能是由于套接字处理代码的设计不佳 我正在寻找有关如何重新设计它以提高性能的指导 使用场景 50 150 个客户端 每个客户端以高速率 高达 100 秒 秒 发送小消息 每条 10 字节
  • Haskell 中美元符号 ($) 和 id 函数之间有关系吗?

    这几天我正在读一篇评论莫纳德挑战 http mightybyte github io monad challenges 我强烈推荐给像我这样的 Haskell 初学者 我最终得到了这个线程 https news ycombinator co
  • Haskell 中函数和函子有什么区别?只有定义吗?

    在 Haskell 中 当编写函数时 这意味着我们将某个东西 输入 映射到另一个东西 输出 我尝试 LYAH 来理解 Functor 的定义 看起来和普通 Functor 一样 函数被称为函子有什么限制吗 Functor 是否允许有 I O
  • 在未排序的整数列表中最优搜索 k 个最小值

    我刚刚接受采访时提出了一个问题 我很好奇答案应该是什么 问题本质上是 假设您有一个包含 n 个整数的未排序列表 您如何找到此列表中的 k 个最小值 也就是说 如果您有一个 10 11 24 12 13 列表并且正在寻找 2 个最小值 您将得
  • 通过 Emacs 评估 ghci 或 Hugs 中的缓冲区

    在 Emacs 中使用 sml mode 我已经能够使用以下命令将缓冲区内容直接发送到较差的 SML 进程C c C b 现在我只想用 Haskell 做同样的事情 Haskell 模式似乎不支持这一点 所以我想知道 使用 Emacs 和
  • 在 R 中替换数据帧中最低列表值的最有效方法

    我有一个数据框 df 其中包含为每个受试者记录的数字列表 向量 用于测试项目的两次重复 subj item rep vec s1 1 1 2 1 4 5 8 4 7 s1 1 2 1 1 3 4 7 5 3 s1 2 1 6 5 4 1 2
  • JMeter:tearDown Thread Group的目的是什么

    我想了解JMeter中tearDown Thread Group的实际用法 在什么场景下可以使用tearDown Thread Group 根据提供的帮助JMeter 拆解线程组 http jmeter apache org userman
  • 整数转浮点数

    这段代码的工作原理 posToXY Float gt Float gt Integer posToXY a b do let y a b round y 但这不起作用 posToXY Integer gt Integer gt Intege
  • Haskell:找不到模块“Data.List.Split”

    我正在尝试在 Haskell 中拆分列表 据我所知 最简单的方法是splitOn 但是这个函数需要Data List Split 所以我尝试运行import Data List Split在前奏曲中 但是 我收到以下错误 Could not
  • Haskell 中的前提条件检查有哪些选项

    这是一个简单的问题 我认为答案很复杂 一个非常常见的编程问题是函数返回某些内容 或者前置条件检查失败 在Java中 我会使用一些抛出异常的断言函数IllegalArgumentException在方法的开头 如下所示 method body
  • LINQ 函数的顺序重要吗?

    基本上 正如问题所述 LINQ 函数的顺序是否重要 表现 显然 结果仍然必须相同 Example myCollection OrderBy item gt item CreatedDate Where item gt item Code g
  • 如何缓存 ASP.NET 网站以获得更好的性能

    我是一名网页设计师 通常设计不需要更新的企业网站 所以我想将输出缓存一天 我怎样才能做到这一点 此外 任何有关在慢速服务器上提高 ASP NET 性能的建议都被接受 请注意 ASP NET 缓存有一个bug http connect mic
  • 在 Haskell 中增长数组

    我想在 Haskell 中实现以下 命令式 算法 给定一个序列对 e0 s0 e1 s1 e2 s2 en sn 其中 e 和 s 部分不一定是自然数不同的是 在每个时间步都会随机选择该序列的一个元素 例如 ei si 并根据 ei si
  • Haskell 类型系统的细微差别

    我一直在深入了解 haskell 类型系统的本质 并试图了解类型类的要点 我已经学到了很多东西 但我在下面的代码片段上遇到了困难 使用这些类和实例定义 class Show a gt C a where f Int gt a instanc
  • WPF 应用程序在第一次交互(例如单击按钮)后停止/冻结

    我目前在 WPF 中遇到问题 UI 加载正常 但每当进行第一次用户交互时 例如单击按钮 应用程序似乎会停止 或者例如 如果我有两个显示 MessageBox 的按钮 则第一次单击将等待几秒钟 然后显示MessageBox 但任何后续交互都是
  • 为什么我的原生 C++ 代码在 Android 上运行速度比 Java 慢很多?

    我将 Java 代码的某些部分移植到 C 以加快 Android 上的计算速度 这是一个物理子例程 我发现本机代码的运行速度比 Java 代码慢几倍 我认为我的项目配置可能有问题 或者可能是数组处理有问题 所以我在 HelloAndroid
  • 为什么 Orchard 在执行内容项查询时如此慢?

    假设我想查询所有 Orchard 用户 ID 并且还想包括那些已被删除 也称为软删除 的用户 该数据库包含大约 1000 个用户 Option A 大约需要 2 分钟 Orchard ContentManagement IContentMa
  • Haskell 下划线与显式变量

    我已经学习 Haskell 几个星期了 我有一个关于下划线的使用的问题 作为函数参数 我认为用一个具体的例子来问我的问题会更好 假设我想定义一个函数 根据提供的索引提取列表的元素 是的 我意识到 已经是预先定义的 我可以定义该函数的两种方法

随机推荐

  • Firebase 托管 - 功能重写定价

    如果你使用Firebase 托管将请求定向到云功能通过重写 通过 托管的请求流量是否会计入 Firebase 托管GB 已转移 忽略云功能的计费 换句话说 Do Firebase 托管当请求到来时 函数重写本身要花钱吗 需要明确的是 明显地
  • HTTP 标头中缺少 Spring WebServiceTemplate SOAPAction

    我在通过 Spring ws WebServiceTemplate 调用 SOAP 1 2 WebService 时遇到困难 发出的请求在 Http 标头中缺少 SOAPAction 并且服务器抛出错误 无法处理没有有效操作参数的请求 请提
  • 添加 2 个时间值的混乱

    基本上这一切都让我感到沮丧 我是编程新手 所以如果我问了一个愚蠢的问题 我深表歉意 我的数据库中存储了一个 MySQL time 我想将此时间添加到当前时间以建立目标时间 持续时间为 06 00 00 MySQL时间 length strt
  • laravel中的双冒号是什么意思

    例子 Auth guard guard gt guest 我不明白双冒号 表示法在 Laravel 框架中的含义 从http php net manual en language oop5 paamayim nekudotayim php
  • EditText setError 在 PopupWindow 中不起作用

    I had popup window具有自定义布局edittext 我试图在以下位置显示错误消息edittext with setError方法 但它给出了以下异常 android view WindowManager BadTokenEx
  • 在 Postgresql 中归档旧数据

    目前 我期待有人就我将要进行的数据库归档过程提供建议 我的数据库 DB 1 有 2 个非常大的表 一个表有 25 GB 的数据 另一个表有 20 GB 的数据 即使我有索引 这也会导致主要的性能问题 因此 我们考虑通过以下过程归档旧数据 从
  • Jquery一次循环绑定10条记录

    我有一个场景 我从服务器获取数千条 JSON 记录并将所有记录绑定到页面 对于每条记录 我都在 jquery 中进行一些计算并将数据绑定到 UI 由于记录数为 1000 条 计算和绑定数据所需的时间更长 当所有记录计算完成后 页面上的数据将
  • MongoDB:在具有多个条件的数组中查找值

    我正在尝试根据价格范围过滤文档 我有以下文档结构示例 name test 1 priceObject price value 1000 price value 500 price value 333 我使用聚合来匹配
  • 访问未配置的 YouTube API

    我正在尝试将观看 YouTube 上的某些视频的功能添加到我正在创建的 iOS 应用程序中 我有来自 Google 的 API 密钥 并从开发者控制台启用了 YouTube API 正如评论所建议的那样 这不是问题 我有一个非常简单的方法
  • JBoss AS 7 中的集群 EJB 不平衡

    我已成功设置由 2 个 JBoss AS 7 实例组成的集群 并部署了以下 SLSB Stateless Remote TestEJBRemote class Clustered public class TestEJB implement
  • 如何在 Perforce 提交上触发 Jenkins 构建

    我将 Jenkins 与 Perforce 结合使用 我已经下载了P4插件 https wiki jenkins ci org display JENKINS P4 Plugin 我已经阅读了文档 但我仍然有点困惑 在我的 Jenkins
  • 刷新库存时出错。应用内结算

    我正在设置和测试应用内结算 我设法购买了 android test purchased 它做了它应该做的事情 但现在我需要消耗它来继续我的测试 问题是我无法到达库存 当调用它时 我得到 result isFaliure 被调用 但我无法获取
  • 如何从 Visual Studio 2013 中的项目生成类图?

    在 Visual Studio 2010 中 我只需单击两次即可从项目中生成类图 但现在在 Visual Studio 2013 中 我在项目菜单中看不到 查看类图 选项 这个物品在哪里消失了 现在如何从项目生成类图 右键单击解决方案资源管
  • 如何在Java中不使用正则表达式仅替换字符串一次?

    我需要替换较大字符串中的动态子字符串 但仅替换一次 即第一次匹配 String类只提供replace 它替换子字符串的所有实例 有一个replaceFirst 方法 但它只需要正则表达式而不是常规字符串 我对使用正则表达式有两个顾虑 1 我
  • 我的预购遍历出了什么问题?

    我正在尝试解决这个问题https oj leetcode com problems binary tree preorder traversal https oj leetcode com problems binary tree preo
  • 如何将 16 位灰度图像写入 jpeg?

    我有每像素 16 位灰度BufferedImage由一系列短裤创建 private BufferedImage get16bitImage short pixels ColorModel colorModel new ComponentCo
  • 求子集的个数,剩余数异或等于0

    给定n个数 找到最小子集数 其中剩余数等于0 例如 1 1 3 4 5 结果等于 3 因为我们可以删除子集 1 3 有两种方式 或 3 4 5 我正在寻找比 O 2 n 蛮力更快的东西 让我们考虑一个大小为 n m 的动态规划表 其中 m
  • 销毁ARM模板部署创建的资源

    需要使用什么特定语法才能成功销毁由devenvironment下面代码中的部署组 上下文 运行以下命令时 将在 Azure 中创建资源集合 az deployment group create name devenvironment res
  • Django:如何在 Q() 语句中使用字符串作为关键字?

    我正在为某个型号编写一个简单的搜索表单 我们来调用模型Orchard并赋予它属性apples oranges and pears 只是为了演示 因此 该表格不需要填写所有字段 所以你可以搜索apples and oranges但不是梨 我需
  • 如何在 Haskell 中将大数据块解析到内存中?

    经过反思 整个问题可以归结为更简洁的问题 我正在寻找一个 Haskell 数据结构 看起来像一个列表 查找时间复杂度为 O 1 具有 O 1 元素替换orO 1 元素附加 或前置 如果是这种情况 我可以反转我的索引查找 我总是可以在编写我以