为什么这个简单的 haskell 算法这么慢?

2024-01-05

剧透警告:这与问题14 https://projecteuler.net/problem=14来自欧拉计划。

以下代码运行大约需要 15 秒。我有一个运行时间为 1 秒的非递归 Java 解决方案。我想我应该能够让这段代码更接近那个。

import Data.List

collatz a 1  = a
collatz a x
  | even x    = collatz (a + 1) (x `div` 2)
  | otherwise = collatz (a + 1) (3 * x + 1)

main = do
  print ((foldl1' max) . map (collatz 1) $ [1..1000000])

我已经与+RHS -p并注意到分配的内存很大,并且随着输入的增长而增长。为了n = 100,000分配了 1GB(!),用于n = 1,000,000分配了 13gb(!!)。

话又说回来,-sstderr显示虽然分配了很多字节,但总内存使用量为 1mb,生产率为 95% 以上,因此 13gb 可能是转移注意力的。

我能想到几种可能性:

  1. 有些事情并不像需要的那么严格。我已经发现了foldl1',但也许我需要做更多?是否可以标记collatz同样严格(这有意义吗?

  2. collatz不是尾调用优化。我认为应该是但不是 知道一个确认的方法。

  3. 编译器没有做一些我认为应该做的优化 - 例如 只有两个结果collatz任何时候都需要存储在内存中(最大值和当前)

有什么建议么?

这几乎是重复的为什么这个 Haskell 表达式这么慢? https://stackoverflow.com/questions/7134669/why-is-this-haskell-expression-so-slow,但我会注意到快速 Java 解决方案不必执行任何记忆化。有没有什么方法可以加快速度而不需要求助于它?

作为参考,这是我的分析输出:

  Wed Dec 28 09:33 2011 Time and Allocation Profiling Report  (Final)

     scratch +RTS -p -hc -RTS

  total time  =        5.12 secs   (256 ticks @ 20 ms)
  total alloc = 13,229,705,716 bytes  (excludes profiling overheads)

COST CENTRE                    MODULE               %time %alloc

collatz                        Main                  99.6   99.4


                                                                                               individual    inherited
COST CENTRE              MODULE                                               no.    entries  %time %alloc   %time %alloc

MAIN                     MAIN                                                   1           0   0.0    0.0   100.0  100.0
 CAF                     Main                                                 208          10   0.0    0.0   100.0  100.0
  collatz                Main                                                 215           1   0.0    0.0     0.0    0.0
  main                   Main                                                 214           1   0.4    0.6   100.0  100.0
   collatz               Main                                                 216           0  99.6   99.4    99.6   99.4
 CAF                     GHC.IO.Handle.FD                                     145           2   0.0    0.0     0.0    0.0
 CAF                     System.Posix.Internals                               144           1   0.0    0.0     0.0    0.0
 CAF                     GHC.Conc                                             128           1   0.0    0.0     0.0    0.0
 CAF                     GHC.IO.Handle.Internals                              119           1   0.0    0.0     0.0    0.0
 CAF                     GHC.IO.Encoding.Iconv                                113           5   0.0    0.0     0.0    0.0

和-stderr:

./scratch +RTS -sstderr 
525
  21,085,474,908 bytes allocated in the heap
      87,799,504 bytes copied during GC
           9,420 bytes maximum residency (1 sample(s))          
          12,824 bytes maximum slop               
               1 MB total memory in use (0 MB lost due to fragmentation)  

  Generation 0: 40219 collections,     0 parallel,  0.40s,  0.51s elapsed
  Generation 1:     1 collections,     0 parallel,  0.00s,  0.00s elapsed

  INIT  time    0.00s  (  0.00s elapsed)
  MUT   time   35.38s  ( 36.37s elapsed)
  GC    time    0.40s  (  0.51s elapsed)
  RP    time    0.00s  (  0.00s elapsed)  PROF  time    0.00s  (  0.00s elapsed)
  EXIT  time    0.00s  (  0.00s elapsed)
  Total time   35.79s  ( 36.88s elapsed)  %GC time       1.1%  (1.4% elapsed)  Alloc rate    595,897,095 bytes per MUT second

  Productivity  98.9% of total user, 95.9% of total elapsed

和 Java 解决方案(不是我的,取自 Project Euler 论坛,删除了记忆功能):

public class Collatz {
  public int getChainLength( int n )
  {
    long num = n;
    int count = 1;
    while( num > 1 )
    {
      num = ( num%2 == 0 ) ? num >> 1 : 3*num+1;
      count++;
    }
    return count;
  }

  public static void main(String[] args) {
    Collatz obj = new Collatz();
    long tic = System.currentTimeMillis();
    int max = 0, len = 0, index = 0;
    for( int i = 3; i < 1000000; i++ )
    {
      len = obj.getChainLength(i);
      if( len > max )
      {
        max = len;
        index = i;
      }
    }
    long toc = System.currentTimeMillis();
    System.out.println(toc-tic);
    System.out.println( "Index: " + index + ", length = " + max );
  }
}

起初,我认为你应该尝试在前面加一个感叹号a in collatz:

collatz !a 1  = a
collatz !a x
  | even x    = collatz (a + 1) (x `div` 2)
  | otherwise = collatz (a + 1) (3 * x + 1)

(你需要把{-# LANGUAGE BangPatterns #-}位于源文件的顶部才能正常工作。)

我的推理如下:问题是你正在建立一个巨大的thunk在 collat​​z 的第一个参数中:它开始于1,然后变成1 + 1,然后变成(1 + 1) + 1,...这一切都没有被强迫。这刘海图案 http://www.haskell.org/ghc/docs/latest/html/users_guide/bang-patterns.html强制第一个参数collatz每当进行调用时都会被强制,因此它从 1 开始,然后变成 2,依此类推,而不会建立一个大的未评估的 thunk:它只是保留为整数。

请注意,刘海模式只是使用的简写seq http://hackage.haskell.org/packages/archive/base/latest/doc/html/Prelude.html#v:seq;在这种情况下,我们可以重写collatz如下:

collatz a _ | seq a False = undefined
collatz a 1  = a
collatz a x
  | even x    = collatz (a + 1) (x `div` 2)
  | otherwise = collatz (a + 1) (3 * x + 1)

这里的技巧是强制a在守卫中,它总是评估为 False (因此主体是不相关的)。然后继续评估下一个案例,a已经被评估过。然而,刘海图案更清晰。

不幸的是,当编译时-O2,这并不比原来的运行速度快!我们还能尝试什么?好吧,我们可以做的一件事是假设这两个数字永远不会溢出机器大小的整数,并给出collatz该类型注释:

collatz :: Int -> Int -> Int

我们将把 bang 模式保留在那里,因为我们仍然应该避免建立 thunk,即使它们不是性能问题的根源。这使得我的(慢速)计算机上的时间缩短至 8.5 秒。

下一步是尝试使其更接近 Java 解决方案。首先要意识到的是,在 Haskell 中,div对于负整数,其行为在数学上更正确,但比“正常”C 除法慢,在 Haskell 中称为quot。更换div with quot将运行时间缩短至 5.2 秒,并替换x `quot` 2 with x `shiftR` 1(导入 Data.Bits)以匹配 Java 解决方案,将其缩短至 4.9 秒。

这大约是我目前能得到的最低值,但我认为这是一个相当不错的结果;因为你的计算机比我的更快,所以它应该更接近 Java 解决方案。

这是最终的代码(我在途中做了一些清理):

{-# LANGUAGE BangPatterns #-}

import Data.Bits
import Data.List

collatz :: Int -> Int
collatz = collatz' 1
  where collatz' :: Int -> Int -> Int
        collatz' !a 1 = a
        collatz' !a x
          | even x    = collatz' (a + 1) (x `shiftR` 1)
          | otherwise = collatz' (a + 1) (3 * x + 1)

main :: IO ()
main = print . foldl1' max . map collatz $ [1..1000000]

查看该程序的 GHC 核心(与ghc-core http://hackage.haskell.org/package/ghc-core),我认为这可能已经是最好的了;这collatz循环使用未装箱的整数,程序的其余部分看起来不错。我能想到的唯一改进就是消除拳击map collatz [1..1000000]迭代。

顺便说一句,不要担心“总分配”数字;这是分配的总内存在程序的生命周期内,即使 GC 回收该内存,它也不会减少。数 TB 的数字很常见。

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

为什么这个简单的 haskell 算法这么慢? 的相关文章

  • 模式匹配中的 Monoid mempty

    我尝试写一个通用的maximum功能类似于Prelude 我的第一个天真的方法如下所示 maximum F Foldable a Ord b gt a b gt Maybe b maximum mempty Nothing maximum
  • Haskell GHC:具有 N 个构造函数的模式匹配的时间复杂度是多少?

    假设我们有以下 Haskell data T T0 T1 T2 TN toInt T gt Int toInt t case t of T0 gt 0 T1 gt 1 T2 gt 2 TN gt N 这里使用什么算法来执行模式匹配 我看到两
  • 测试列表是否已排序

    在 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
  • 我是否应该使用 GHC Haskell 扩展?

    当我学习 Haskell 时 我发现有很多语言扩展 http haskell org ghc docs latest html users guide ghc language features html在现实生活中使用的代码 作为初学者
  • 在 Haskell 中对单位的组成(例如英寸、美元等)进行建模

    跟进自我之前的一个问题 https stackoverflow com q 73375273 222529 我问如何创建一个可以对单元进行建模的类型 例如Inch 作为 Haskell 中的一种类型 我现在面临的问题是如何对该单元和其他单元
  • 在 Haskell 中为自定义数据类型创建 Read 类型类的实例

    我有一个自定义数据类型Foo Foo a Int b Int 我正在尝试使 Foo 成为 read 的自定义实例 我已经有一个功能了bar String gt Foo我尝试这样做 instance Read Foo a b where re
  • Haskell:处理死锁的自引用列表

    GHC 允许永久阻止以下内容是否有任何有用的理由 list 1 tail list 看起来列表迭代器 生成器有点复杂 我们应该能够做一些更有用的事情 Return error Infinitely blocking list Return
  • Haskell,范围缩小到无步骤[重复]

    这个问题在这里已经有答案了 为什么在 Haskell 中工作范围不能降低到没有步骤 7 1 gt 但只工作这个 7 6 1 gt 7 6 5 4 3 2 1 Haskell 无法知道您想要执行 1 除非您给出提示 在某些情况下 您可能需要一
  • Haskell 真的是纯粹的吗(有任何语言可以处理系统外的输入和输出)吗?

    在谈到函数式编程中的 Monad 后 该功能是否真的使语言变得纯粹 或者它只是黑板数学之外的现实世界中计算机系统推理的另一张 免狱卡 EDIT 这不是有人在这篇文章中所说的火焰诱饵 而是一个真正的问题 我希望有人能用它来击倒我并说 证明 它
  • 表达式“ap zip tail”如何工作

    我想知道怎么写f x zip x tail x 点免费 所以我使用了pointfree程序 结果是f ap zip tail ap作为 Control Monad 的函数 我不明白点自由定义是如何工作的 如果我能从类型的角度去理解的话 希望
  • 不同 hs 文件中的函数分离时堆栈空间溢出

    我有一个巨大的 haskell 文件 它编译和运行没有任何问题 我想将一些函数和类型定义放在通用 hs 文件中的单独模块中 然后将其导入我的主模块中 虽然主程序编译时没有任何错误 它还编译导入的模块 但当我尝试运行它时 出现堆栈空间溢出 I
  • 如何在 Haskell 中枚举递归数据类型?

    这篇博文 http lukepalmer wordpress com 2008 05 02 enumerating a context free language 对于如何使用 Omega monad 对角枚举任意语法有一个有趣的解释 他提
  • “反向”使用 Maybe Monad

    假设我有很多功能 f a gt Maybe a g a gt Maybe a h a gt Maybe a 我想按以下方式组合它们 如果 f 返回 Nothing 则计算 g 如果 g 返回 Nothing 则计算 h 如果其中任何一个计算
  • Haskell 错误处理方法

    毫无疑问 Haskell 中有多种机制来处理错误并正确处理它们 错误单子 要么 也许 异常等 那么为什么用其他语言编写容易出现异常的代码比用 Haskell 感觉更简单呢 假设我想编写一个命令行工具来处理命令行上传递的文件 我想 验证提供的
  • 带有查询参数的渲染 url

    无法找到简单问题的解决方案 答案应该是显而易见的 如何在 hamlet 模板中使用查询参数渲染 url I e ItemsR 将生成http localhost 3000 items我如何生成类似的东西http localhost 3000
  • 我应该使用什么递归方案来重复有效的操作,直到其结果符合某些标准?

    也就是说 我要问的是一个循环 effectful Int gt IO Int effectful n do putStrLn Effect show n return n condition 3 final Int gt IO final
  • 为什么 exceptT 没有 MonadMask 实例?

    爱德华 克梅特例外情况图书馆不提供单子掩码 https www stackage org haddock lts 7 18 exceptions 0 8 3 Control Monad Catch html t MonadMask实例为Ex
  • 嵌套在其他 monad 中的 IO 操作未执行

    我有一个 foobar IO ParseResult String String ParseResult 是一个在这里定义的 monad https hackage haskell org package haskell src exts
  • 有什么方法可以在 do / while / let 块中打印出变量的类型吗?

    有没有办法打印出嵌套变量的推断类型ghci 考虑代码 let f g where g x Int x 那么 最好查询一下类型g e g t f g会打印出Int gt Int 您可以通过给出适当的错误类型注释并检查错误消息来诱骗此信息 Ma
  • 类型级编程有哪些示例? [关闭]

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

随机推荐

  • Silverlight图像编辑器控件[关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 您是否知道任何 Silverlight 图像编辑器 控件 商业或开源 主要功能要求 裁剪 调整大小 旋转图像 设置背景颜色 插入文字 插入
  • 如何在 Java 中查找 2D 数组中的子数组是否具有特定的和?

    我试图通过比较源图像和图案图像中存在的像素的平均颜色来解决图像匹配问题 我已将这个问题简化为子数组求和问题 但无法找到解决方法 假设我有一个包含所有正整数的二维数组 ARR 我有一个数字 x 它是小图案图像中存在的像素颜色的平均值 我只需要
  • iPhone - 从另一个视图控制器调用函数

    我有一个名为 sendDataToMotor 的函数 它在我的第一个视图控制器类中 我有另一个名为 SecondViewController 的视图控制器 我需要从 Second View Controller m 类调用此函数 我尝试申报
  • haproxy 全局 maxconn 和服务器 maxconn 的区别

    我对我的 haproxy 配置有疑问 Global settings global log 127 0 0 1 syslog emerg maxconn 4000 quiet user haproxy group haproxy daemo
  • 如何测量并显示单个测试的运行时间?

    我有一个可能需要长时间运行的测试分级测试 test a long running test failAfter Span 60 Seconds 即使测试在超时限制内完成 其运行时间对于运行测试的人来说也是有价值的 我如何测量并显示这个的运行
  • 将 WriteableBitmap 保存为 PNG

    如何将 WriteableBitmap 保存为具有透明背景的 PNG PNG 和带有透明度的 PNG 有区别吗 感谢你的帮助 请给我看示例代码 谢谢 只需浏览以下链接即可 希望这可以帮助你 使用 WPF 将 WriteableBitmap
  • 我如何获得有关谁调用了某个方法的信息?

    我想获得一些有关谁调用了特定方法的信息 也就是说 如果可能的话 获取进行调用的方法的行号和文件名 类似于FILE and LINE 除了堆栈中的下一层 这在高级语言中是可能的 但是在 Objective C 中有什么方法可以做到这一点吗 v
  • 如何调试 Grunt Mocha 任务?

    我正在使用 WebStorm 来运行 grunt 任务 调试器成功停止在 Gruntfile js 文件中的断点处 但不在我的任务文件中 在 Gruntfile js 中 我注册了一个如下任务 grunt initConfig config
  • 错误消息“ENOENT,没有这样的文件或目录”

    我从 Node js 应用程序中收到此错误 ENOENT 没有这样的文件或目录 Desktop MyApp newversion partials navigation jade 我知道该文件在那里 因为当我尝试使用精确复制和粘贴的路径打开
  • jQuery UI 选项卡 - 深度链接到选项卡内容

    我不确定目前这是否可能 而且我所做的测试似乎提供了奇怪的结果 我在一页上有 4 个选项卡 这些选项卡内有几个文本部分 每个部分都有一个唯一的锚点名称 我想做的是从另一个页面链接到选项卡 3 中的第四个内容块 这些选项卡都工作得很好 如果我链
  • 将参数传递给 Go IIFE(以下 javascript 示例)

    我习惯于在 javascript 中进行编程 我可以执行以下操作将参数传递到立即调用的函数表达式中 function twoSeconds do something with twoSeconds here 2 1000 所以我希望能够在
  • PostgreSQL - 从函数返回 n 大小的 varchar

    正如我在文档中发现的 带括号的类型修饰符 例如 类型的精度字段 numeric 被 CREATE FUNCTION 丢弃 是否有其他方法可以从 plpgsql 函数返回 varchar N 类型 问题更新 在图片上 您可以看到 Name 列
  • 为什么不能使用 cat 逐行读取文件,其中每行都有分隔符

    我有一个文本文件 其中包含如下内容 abc 123 comma the quick brown fox jumped over the lazy dog comma comma 我写了一个脚本 for i in cat file do ec
  • 通过 Android 应用程序连接到 WiFi

    我希望创建一个应用程序 检测附近可用的 wifi 连接 然后连接到它们 到目前为止 我所做的是创建一个 ListView 列出可用的 wifi 连接 然后创建一个 LongItemClick 对话框 显示网络的 SSID 和 BSSID 并
  • 无法在 C# 中反序列化 JSON 结果。输入字符串格式错误

    我正在尝试将 json 输出反序列化为 C 对象 JSON 结果 order commission 3 490000 cost 4 490000 duration day extended hours false fees 0 000000
  • 如何使用winsock的send()函数发送宽字符?

    It says here http msdn microsoft com en us library ms740149 28VS 85 29 aspx发送函数需要 const char 如何发送宽字符 我尝试了以下方法 void MyCla
  • Installshield msi 无法注册 flash.ocx

    我正在使用 Installshield 安装项目创建 msi 安装程序 该应用程序当前工作正常 并且正在用作 Click Once 应用程序 但现在有创建安装包 即 msi 安装程序 的业务要求 安装程序 在我的机器上运行良好 但在用户设置
  • 了解 ASP.NET 中的负载平衡

    我正在编写一个将开始使用负载均衡器的网站 并且我正在尝试解决它 IIS 会为您做所有的平衡吗 您是否有一个位于分布式服务器上的单独的 Web 层 该层在发送到子服务器之前执行一些工作 例如身份验证或其他工作 似乎我一直在阅读的很多文章并没有
  • 为什么pipenv需要Pipfile和Pipfile.lock?

    我认为 我理解背后的原理pipenv 以及其他 venv 并经常使用它们 然而我一直不明白为什么pipenv需要两个Pipfile and a Pipfile lock file 这个答案 https stackoverflow com a
  • 为什么这个简单的 haskell 算法这么慢?

    剧透警告 这与问题14 https projecteuler net problem 14来自欧拉计划 以下代码运行大约需要 15 秒 我有一个运行时间为 1 秒的非递归 Java 解决方案 我想我应该能够让这段代码更接近那个 import