避免"分享"的好方法?

yai*_*chu 19 haskell sharing

假设有人将这个简单的Python代码翻译成Haskell:

def important_astrological_calculation(digits):
  # Get the first 1000000 digits of Pi!
  lucky_numbers = calculate_first_digits_of_pi(1000000)
  return digits in lucky_numbers
Run Code Online (Sandbox Code Playgroud)

Haskell版本:

importantAstrologicalCalculation digits =
  isInfixOf digits luckyNumbers
  where
    luckyNumbers = calculateFirstDigitsOfPi 1000000
Run Code Online (Sandbox Code Playgroud)

在使用Haskell版本之后,程序员惊讶地发现他的Haskell版本"泄漏"内存 - 在第一次调用他的函数之后,luckyNumbers永远不会被释放.这令人不安,因为该程序包含一些更类似的功能,并且所有这些功能所消耗的内存都非常重要.

是否有一种简单而优雅的方式使程序"忘记" luckyNumbers

Don*_*art 21

在这种情况下,您的pidigits列表是一个常量(或"常量应用形式"),GHC可能会将其浮出,计算一次,并在使用中共享.如果没有对CAF的引用,它将被垃圾收集.

现在,通常,如果您想要重新计算某些内容,请将其转换为函数(例如,通过添加虚拟()参数).关于CAF的链接问题中的示例:如何在Haskell中使CAF不是CAF?


yai*_*chu 0

解决这个问题的三种方法(基于这篇博客文章

\n

使用INLINE编译指示

\n

添加{-# INLINE luckyNumbers #-}另一个importantAstrologicalCalculation

\n

这将使单独的调用彼此独立,每个调用都使用自己的副本,该副本luckyNumbers会迭代一次并立即由 GC 收集。

\n

优点:

\n
    \n
  • 需要对我们的代码进行最少的更改
  • \n
\n

缺点:

\n
    \n
  • 脆弱的?kuribas写道“INLINE doen\xe2\x80\x99t 保证内联,这取决于优化标志”
  • \n
  • 机器代码重复。可能会创建更大且效率可能较低的可执行文件
  • \n
\n

使用-fno-full-lazinessGHC 标志

\n

luckyNumbers用虚拟 lambda 包裹并使用-fno-full-laziness

\n
{-# OPTIONS -fno-full-laziness #-}\n\nluckyNumbers _ = calculateFirstDigitsOfPi 1000000\n
Run Code Online (Sandbox Code Playgroud)\n

如果没有该标志,GHC 可能会注意到 in 中的表达式luckyNumbers不使用其参数,因此它可能会将其浮出并共享它。

\n

优点:

\n
    \n
  • 无机器代码重复:共享 fibs 的实现,但不共享结果列表!
  • \n
\n

缺点:

\n
    \n
  • 脆弱的?我担心如果另一个模块使用 fibs 并且 GHC 决定内联它,并且第二个模块没有启用,这个解决方案可能会崩溃-fno-full-laziness
  • \n
  • 依赖 GHC 标志。这些可能比语言标准更容易改变
  • \n
  • 需要修改我们的代码,包括所有 fibs 的调用站点
  • \n
\n

功能化

\n

阿朗佐·丘奇 (Alonzo Church) 著名地发现数据可以在函数中编码,我们可以使用它来避免创建可以共享的数据结构。

\n

luckyNumbers可以将其制作为折叠 pi 数字的函数,而不是数据结构。

\n

优点:

\n
    \n
  • 坚硬的。毫无疑问,面对各种编译器优化,这将恢复工作
  • \n
\n

缺点:

\n
    \n
  • 更详细
  • \n
  • 非标准。我们不再使用标准列表,这些列表有大量支持它们的标准库函数,我们可能需要重新实现
  • \n
\n