Haskell 中的高效比特流

2023-12-22

在不断努力有效地摆弄位的过程中(例如,请参阅此所以问题 https://stackoverflow.com/questions/43601927/efficient-bit-fiddling-in-a-lfsr-implementation)最新的挑战是高效的流传输和比特消耗。

作为第一个简单任务,我选择在由以下命令生成的比特流中找到最长的相同比特序列/dev/urandom。一个典型的咒语是head -c 1000000 </dev/urandom | my-exe。实际目标是传输比特并解码埃利亚斯伽玛码 https://en.wikipedia.org/wiki/Elias_gamma_coding,例如,即不是字节块或其倍数的代码。

对于这种可变长度的代码,最好有take, takeWhile, group等列表操作语言。自从一个BitStream.take实际上会消耗一些 monad 可能会发挥作用的比特流的一部分。

明显的起点是来自的惰性字节串Data.ByteString.Lazy http://hackage.haskell.org/package/bytestring-0.10.8.2/docs/Data-ByteString-Lazy.html.

A. 计算字节数

正如预期的那样,这个非常简单的 Haskell 程序的性能与 C 程序相当。

import qualified Data.ByteString.Lazy as BSL

main :: IO ()
main = do
    bs <- BSL.getContents
    print $ BSL.length bs

B. 添加字节

一旦我开始使用unpack事情应该变得更糟。

main = do
    bs <- BSL.getContents
    print $ sum $ BSL.unpack bs

令人惊讶的是,Haskell 和 C 表现出几乎相同的性能。

C. 最长的相同位序列

作为第一个重要任务,可以像这样找到最长的相同位序列:

module Main where

import           Data.Bits            (shiftR, (.&.))
import qualified Data.ByteString.Lazy as BSL
import           Data.List            (group)
import           Data.Word8           (Word8)

splitByte :: Word8 -> [Bool]
splitByte w = Prelude.map (\i-> (w `shiftR` i) .&. 1 == 1) [0..7]

bitStream :: BSL.ByteString -> [Bool]
bitStream bs = concat $ map splitByte (BSL.unpack bs)

main :: IO ()
main = do
    bs <- BSL.getContents
    print $ maximum $ length <$> (group $ bitStream bs)

惰性字节串转换为列表[Word8]然后,利用轮班,每个Word被分成几位,产生一个列表[Bool]。然后将这个列表列表展平为concat。获得了(惰性)列表Bool, use group将列表拆分为相同位的序列,然后映射length超过它。最后maximum给出了所需的结果。很简单,但不是很快:

# C
real    0m0.606s

# Haskell
real    0m6.062s

这种幼稚的实现正好慢了一个数量级。

分析显示分配了相当多的内存(解析 1MB 输入大约需要 3GB)。不过,没有观察到大规模的空间泄漏。

从这里我开始四处探索:

  • 有一个bitstream package https://hackage.haskell.org/package/bitstream-0.2.0.4/docs/Data-Bitstream.html承诺“具有半自动流融合的快速、打包、严格的比特流(即布尔列表)。”。不幸的是,它与当前的情况不同步。vector包,参见here https://github.com/depressed-pho/bitstream/issues/6了解详情。
  • 接下来我调查一下streaming https://hackage.haskell.org/package/streaming。我不太明白为什么我应该需要“有效”的流媒体来发挥一些单子的作用——至少在我开始与所提出的任务相反的任务之前,即编码并将比特流写入文件。
  • 就这样怎么样fold- 超过ByteString?我必须引入状态来跟踪消耗的位。这不太好take, takeWhile, group等所需的语言。

现在我不太确定该去哪里。

Update:

我想出了如何做到这一点streaming https://hackage.haskell.org/package/streaming-0.2.1.0 and streaming-bytestring https://hackage.haskell.org/package/streaming-bytestring-0.1.6/docs/Data-ByteString-Streaming.html。我可能做得不对,因为结果是灾难性的糟糕。

import           Data.Bits                 (shiftR, (.&.))
import qualified Data.ByteString.Streaming as BSS
import           Data.Word8                (Word8)
import qualified Streaming                 as S
import           Streaming.Prelude         (Of, Stream)
import qualified Streaming.Prelude         as S

splitByte :: Word8 -> [Bool]
splitByte w = (\i-> (w `shiftR` i) .&. 1 == 1) <$> [0..7]

bitStream :: Monad m => Stream (Of Word8) m () -> Stream (Of Bool) m ()
bitStream s = S.concat $ S.map splitByte s

main :: IO ()
main = do
    let bs = BSS.unpack BSS.getContents :: Stream (Of Word8) IO ()
        gs = S.group $ bitStream bs ::  Stream (Stream (Of Bool) IO) IO ()
    maxLen <- S.maximum $ S.mapped S.length gs
    print $ S.fst' maxLen

这将考验您对来自标准输入的超过几千字节输入的耐心。分析器表示它花费了大量的时间(输入大小的二次方)Streaming.Internal.>>=.loop and Data.Functor.Of.fmap。我不太确定第一个是什么,但是fmap表明(?)这些杂耍Of a b对我们没有任何好处,而且因为我们处于 IO monad 中,所以无法对其进行优化。

我还有字节加法器的流式传输等效项here: SumBytesStream.hs https://github.com/mcmayer/haskell-efficient-bitstreams/blob/master/SumBytesStream.hs,比简单的lazy稍微慢一点ByteString实施,但仍然不错。自从streaming-bytestring is 宣布 https://hackage.haskell.org/package/streaming-bytestring to be "字节串 io 正确完成“我的预期更好。那么我可能做得不对。

无论如何,所有这些位计算都不应该发生在 IO monad 中。但BSS.getContents迫使我进入 IO monad 因为getContents :: MonadIO m => ByteString m ()并且没有出路。

Update 2

按照@dfeuer的建议,我使用了streaming https://github.com/haskell-streaming/streaming包位于 master@HEAD。这是结果。

longest-seq-c       0m0.747s    (C)
longest-seq         0m8.190s    (Haskell ByteString)
longest-seq-stream  0m13.946s   (Haskell streaming-bytestring)

O(n^2) 问题Streaming.concat已经解决了,但是我们仍然没有接近 C 基准。

Update 3

Cirdec 的解决方案产生与 C 相当的性能。所使用的构造称为“Church 编码列表”,请参阅此所以答案 https://stackoverflow.com/questions/15589556/why-are-difference-lists-not-an-instance-of-foldable/15593349#15593349或 Haskell Wiki 上N 级类型 https://wiki.haskell.org/Rank-N_types.

源文件:

所有源文件都可以在github https://github.com/mcmayer/haskell-efficient-bitstreams. The Makefile具有运行实验和分析的所有各种目标。默认make只会构建一切(创建一个bin/首先是目录!)然后make time将进行计时longest-seq可执行文件。 C 可执行文件得到一个-c附加以区分它们。


当流上的操作融合在一起时,可以消除中间分配及其相应的开销。 GHC prelude 以以下形式为惰性流提供折叠/构建融合:重写规则 https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/glasgow_exts.html#list-fusion。一般的想法是,如果一个函数产生一个看起来像foldr的结果(它的类型是(a -> b -> b) -> b -> b应用于(:) and []),另一个函数使用一个看起来像foldr的列表,构造中间列表可以被删除。

对于你的问题,我将构建类似的东西,但使用严格的左折叠(foldl') 而不是文件夹。而不是使用尝试检测某些内容何时看起来像的重写规则foldl,我将使用强制列表看起来像左折叠的数据类型。

-- A list encoded as a strict left fold.
newtype ListS a = ListS {build :: forall b. (b -> a -> b) -> b -> b}

由于我一开始就放弃了列表,因此我们将重新实现列表前奏的一部分。

可以从以下位置创建严格的左折叠foldl'列表和字节串的函数。

{-# INLINE fromList #-}
fromList :: [a] -> ListS a
fromList l = ListS (\c z -> foldl' c z l)

{-# INLINE fromBS #-}
fromBS :: BSL.ByteString -> ListS Word8
fromBS l = ListS (\c z -> BSL.foldl' c z l)

最简单的使用示例是查找列表的长度。

{-# INLINE length' #-}
length' :: ListS a -> Int
length' l = build l (\z a -> z+1) 0

我们还可以映射和连接左侧折叠。

{-# INLINE map' #-}
-- fmap renamed so it can be inlined
map' f l = ListS (\c z -> build l (\z a -> c z (f a)) z)

{-# INLINE concat' #-}
concat' :: ListS (ListS a) -> ListS a
concat' ll = ListS (\c z -> build ll (\z l -> build l c z) z)

对于您的问题,我们需要能够将单词拆分为位。

{-# INLINE splitByte #-}
splitByte :: Word8 -> [Bool]
splitByte w = Prelude.map (\i-> (w `shiftR` i) .&. 1 == 1) [0..7]

{-# INLINE splitByte' #-}
splitByte' :: Word8 -> ListS Bool
splitByte' = fromList . splitByte

And a ByteString变成比特

{-# INLINE bitStream' #-}
bitStream' :: BSL.ByteString -> ListS Bool
bitStream' = concat' . map' splitByte' . fromBS

为了找到最长的运行,我们将跟踪先前的值、当前运行的长度以及最长运行的长度。我们使字段变得严格,以便折叠的严格性可以防止重击链在内存中累积。为状态创建严格的数据类型是控制其内存表示及其字段评估时间的简单方法。

data LongestRun = LongestRun !Bool !Int !Int

{-# INLINE extendRun #-}
extendRun (LongestRun previous run longest) x = LongestRun x current (max current longest)
  where
    current = if x == previous then run + 1 else 1

{-# INLINE longestRun #-}
longestRun :: ListS Bool -> Int
longestRun l = longest
 where
   (LongestRun _ _ longest) = build l extendRun (LongestRun False 0 0)

我们就完成了

main :: IO ()
main = do
    bs <- BSL.getContents
    print $ longestRun $ bitStream' bs

这要快得多,但不完全是 c 的性能。

longest-seq-c       0m00.12s    (C)
longest-seq         0m08.65s    (Haskell ByteString)
longest-seq-fuse    0m00.81s    (Haskell ByteString fused)

该程序分配大约 1 Mb 来从输入中读取 1000000 字节。

total alloc =   1,173,104 bytes  (excludes profiling overheads)

Updated github代码 https://github.com/Cedev/haskell-efficient-bitstreams

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

Haskell 中的高效比特流 的相关文章

  • 为什么我不能声明推断类型?

    我有以下内容 runcount Eq a Num b gt a gt b runcount runcountacc 0 runcountacc Eq a Num b gt b gt a gt b runcountacc n runcount
  • 优化 Haskell 内循环

    仍在 Haskell 中进行 SHA1 实现 我现在已经有了一个有效的实现 这是内部循环 iterateBlock Int gt Word32 gt Word32 gt Word32 gt Word32 gt Word32 gt Word3
  • Haskell Cabal:“包间接依赖于同一包的多个版本”

    清除我的所有后cabal installed 包 我运行了以下会话 cabal update Downloading the latest package list from hackage haskell org james bast c
  • 如何与更高级别的类型合作

    玩弄教堂的数字 我遇到了无法指导 GHC 类型检查器处理高阶类型的情况 首先我写了一个版本 没有任何类型签名 module ChurchStripped where zero z z inc n z s s n z s natInteger
  • Cabal 无法安装依赖项,但如果直接询问可以安装它们

    我发现 Cabal 反复出现一个非常奇怪的问题 它影响了我获得可重复的 Haskell 构建的能力 我有一个带有沙箱的阴谋集团项目 如果我做cabal install 我收到以下形式的错误 Y failed during the build
  • 为什么 GHC 在这里推断出单态类型,即使禁用了单态限制?

    这是由解析 f f pure 的类型 https stackoverflow com questions 55388119 resolving the type of f f pure 55388309 noredirect 1 comme
  • 将“Functor”类泛化为“MultiFunctor”?

    我正在学习 自由应用函子 https arxiv org pdf 1403 0749 pdf 当然 我要问的问题有点偏离论文的主要思想 但仍然 第 6 页试图概括Functor to MultiFunctor class Functor f
  • monadicIO 的工作原理

    我有以下代码 fastShuffle a gt IO a fastShuffle a
  • Haskell 有 takeUntil 函数吗?

    目前我正在使用 takeWhile x gt x 1 x 89 l 从列表中获取最多为 1 或 89 的元素 但是 结果不包括这些标记值 Haskell 是否有一个标准函数可以提供这种变化takeWhile结果中包含哨兵 到目前为止 我对胡
  • parList 和 parBuffer 如何选择?

    我从 haskell 并行开始 我已经成功学习了如何使用一些策略 例如 r0 rseq rdeepseq parList parMap 现在我正在进一步寻求更高的效率 所以这是我的问题 有什么区别parList and parBuffer
  • 如何获得具有超载字段名称的经典镜头?

    我正在尝试为具有相同字段名称的记录构建镜头 除此之外 我试图 包装 扩展 这些基本记录 并希望相同的字段名称适用于包装 扩展的记录 我相信 优雅的镜头就是这样做的 我如何让以下内容发挥作用 Data types for context of
  • 我可以在线性时间内检查有界列表是否包含重复项吗?

    假设我有一个Int列表 其中元素已知是有界的 并且列表已知不长于它们的范围 因此它完全有可能不包含重复项 如何才能最快地测试是否是这种情况 我知道nubOrd https hackage haskell org package contai
  • 在 Haskell 中证明“没有腐败”

    我所在的行业对安全要求很高 我们的软件项目一般都会有安全要求 我们必须证明该软件具有高度确定性 通常这些都是负面的 例如 腐败的频率不得超过 1 我要补充的是 这些要求来自统计系统安全要求 损坏的根源之一显然是编码错误 我想使用 Haske
  • 如何给Servant中的所有端点添加前缀?

    我在 Haskell 仆人中有一个 hello world 应用程序 这是其中的一部分 type API my items gt Get JSON MyItem lt gt my items gt Capture id Int gt Get
  • 双共体的方法是什么?

    在思考建议哪些更有用的标准课程时到这个 https stackoverflow com a 40833245 745903 class Coordinate c where createCoordinate x gt y gt c x y
  • 一个目录中的多个 Haskell cabal-packages

    在一个目录中包含多个 cabal 软件包的推荐方法是什么 Why 我有一个包含许多可分离模块的旧项目 由于最初它们只形成一个程序 因此将它们放在同一目录中以便于编译非常方便 而且现在仍然如此 Options 只是忍受并将所有内容 包括保存内
  • 在没有互联网连接的情况下使用 cabal 安装 Haskell 软件包

    我有一台根本无法访问互联网的机器 我使用通过随身碟从另一台机器获得的安装程序在其上安装了 Haskell 平台 现在我想安装这个包repa在我的家用机器上 无法访问互联网 我该怎么做呢 我的家用计算机运行的是 Linux Debian 我的
  • 如何在递归方案中派生实例

    我正在测试其中的一些想法本文 http blog sumtypeofway com an introduction to recursion schemes 我想派生 Term 类型的 Eq 实例 LANGUAGE DeriveFuncto
  • 为什么Haskell没有split函数? [关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 在许多语言中 都有一个函数可以使用指定的分隔符将字符串分成几部分 它经常被称为split 您可以在 Python C Java JavaScri
  • 绑定变量时 Haskell 中的无限循环

    下面的 Haskell 代码不会终止 有人可以解释一下为什么吗 谢谢 f let x 10 in let x x x in x 我认为解释器首先绑定 x 10 然后将 x x 计算为 100 并绑定 x 100 环境变为 x 100 那么整

随机推荐

  • 编译器在转换整型常量时做什么?

    使用以下宏 define MIN SWORD signed int 0x8000 例如以下表达式 signed long s32 if s32 lt signed long MIN SWORD 预计要做以下检查 if s32 lt 3276
  • 数据表中的字典

    说我有3个Dictionary
  • 应用程序缓存清单+本地存储大小限制

    我正在构建一个可能有大量离线存储需求的应用程序 我想知道是否可以同时使用离线缓存清单 5MB 和本地存储 5MB 来使用 10MB 存储 请注意 这是一个 Intranet 应用程序 因此我们可以控制设备 我已经在 Chrome Firef
  • 没有模糊视图效果的 UIAlertController 操作表

    我在用着UIAlertController对于一些行动 但我不太喜欢模糊视图效果在操作组视图中 参见下面的屏幕截图 我正在尝试消除这种模糊效果 我在网上查了一下 没有找到任何APIUIAlertController这样就可以消除这种模糊效果
  • php + mysql,按名称排序+从特定id开始

    MySQL id name 1 Joe 2 Craig 3 Shawn 4 Ryan 5 Seth PHP a mysql query SELECT FROM table name ORDER BY name DESC 但我想做的是 我想从
  • Firebird 交易计数超出

    我们有一个运行 Firebird 数据库的实现 但出现以下错误 超出实施限制 超出交易计数 执行备份和恢复以使数据库再次可操作 我们知道如何通过使数据库只读 执行备份和恢复以及再次读写来解决此问题 但是我们不太确定导致此问题的原因 我感觉交
  • > 和线程' aria-label='Rust 生命周期与 mpsc::Sender> 和线程'> Rust 生命周期与 mpsc::Sender> 和线程

    我正在创建一个多线程应用程序 在其中创建一个接收通道和一个用于保存发送通道的结构 稍后由实现使用 但是 我通过通道发送的类型具有生命周期规范 这种类型是websocket message Message来自 rust websocket 库
  • 如何在 zenframework 2 上配置学说命令行工具

    我在 zendframework 2 上使用原则 2 我已经正确配置了两者 并且它们都正常工作 不过我想用学说的命令行工具生成实体等 我遵循了学说的说明 并在应用程序的根目录中创建了一个 cli config php 页面 http doc
  • 如何防止 DIV 扩展并占据所有可用宽度?

    在下面的 HTML 中 我希望图像周围的框架能够紧贴 不要拉伸并占据父容器中的所有可用宽度 我知道有几种方法可以做到这一点 包括可怕的事情 例如手动将其宽度设置为特定数量的像素 但是什么是right way Edit 一个答案建议我关闭 d
  • ARRAY_CONTAINS hive 中的多个值

    有没有一种方便的方法来使用 hive 中的 ARRAY CONTAINS 函数来搜索数组列中的多个条目 而不仅仅是一个 所以而不是 WHERE ARRAY CONTAINS array val1 OR ARRAY CONTAINS arra
  • 如何在ios中调整uilabel的角度[重复]

    这个问题在这里已经有答案了 i m creating an iphone app in that application i want to angle the label according to the attached screen
  • iOS 8 CoreData 问题:recordChangeSnapshot:forObjectID:: 录制时全局 ID 可能不是临时的

    我正在将我的应用程序从 iOS 7 迁移到 iOS 8 当我尝试保存核心数据上下文时 我在 Xcode 中收到以下错误 iOS 7 和 Xcode 5 中不存在此错误 它会在下面的行中抛出异常 NSError saveError nil i
  • Django:“sys.path”应该是什么?

    开发 Django 应用程序时 什么是sys path应该包含 包含项目的目录 或项目的目录 或两者 sys path应该并且将会有项目的目录 根据您的设置 它还可能包含包含项目的目录 但是 如果这个问题背后的动机是确保可以找到某些文件 那
  • 如何在Windows批处理文件中循环连接字符串?

    我熟悉 Unix shell 脚本编写 但对 Windows 脚本编写不熟悉 我有一个包含 str1 str2 str3 str10 的字符串列表 我想这样做 for string in string list do var string
  • 调用未定义的方法 Maatwebsite\Excel\Excel::load()

    我正在尝试使用 maatwebsite 3 0 导入 Excel 文件 xlsx 如何修复此错误 调用未定义的方法 Maatwebsite Excel Excel load 我的控制器 public function importsave
  • CGMutablePathRef 的自动释放?

    我正在为 iPhone 开发 我想通过 CGPathCreateMutable 创建一个可变路径 并且我想从创建它的函数中返回它 我应该在完成后调用 CGPathRelease 但既然我要归还它 我希望自动释放它 由于 Quartz 路径是
  • 如何使用MockBloc实现widget测试?

    我正在尝试实现小部件测试以测试登录表单 该测试取决于我使用 MockBloc 嘲笑的块 但是 它会引发以下错误 EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK The following StateEr
  • 使用 odeint 求解 ODE 时如何传递源项

    强迫谐振子的微分方程为Mx Lx w 2 x F t 这里 F t 是源项 为了解决这个问题 我编写了一段代码 在函数 diff 中定义微分方程 我编写了另一个函数 generate pulse 来给出 F t 然后我使用 odeint 它
  • AngularJS 中的控制器功能?

    我是角度js新手 控制器在我的代码中无法正常工作 我正在尝试运行以下代码 name br div div
  • Haskell 中的高效比特流

    在不断努力有效地摆弄位的过程中 例如 请参阅此所以问题 https stackoverflow com questions 43601927 efficient bit fiddling in a lfsr implementation 最