【发布时间】:2017-09-27 14:32:17
【问题描述】:
我正在尝试学习/评估 Haskell,并且正在努力为一个简单的案例获得高效的可执行文件。 我正在使用的测试是 PRNG 序列(复制 PCG32 RNG)。我把它写成一个基本状态转换函数的迭代(我现在只看状态)。
{-# LANGUAGE BangPatterns #-}
import System.Environment (getArgs)
import Data.Bits
import Data.Word
iterate' f !x = x : iterate' f (f x)
main = print $ pcg32_rng 100000000
pcg32_random_r :: Word64 -> Word64 -> Word64
pcg32_random_r !i !state = state * (6364136223846793005 :: Word64) + (i .|. 1)
{-# INLINE pcg32_random_r #-}
pcg32_rng_s = iterate' (pcg32_random_r 1) 0
pcg32_rng n = pcg32_rng_s !! (n - 1)
我可以编译和运行该代码。它仍然使用比它应该使用的更多的内存,并且运行速度比 C 等价物慢 10 倍。主要问题似乎是迭代没有变成一个简单的循环。
让 GHC 在这里生成更快/更高效的代码我缺少什么?
编辑
这是我比较的 C 版本,它本质上捕获了我想要实现的目标。我尝试进行公平比较,但如果我遗漏了什么,请告诉我。
#include <stdio.h>
#include <stdint.h>
int main() {
uint64_t oldstate,state;
int i;
for(i=0;i<100000000;i++) {
oldstate = state;
// Advance internal state
state = oldstate * 6364136223846793005ULL + (1|1);
}
printf("%ld\n",state);
}
我最初尝试使用 Prelude iterate 函数,但这会导致延迟评估和堆栈溢出。 “terate”旨在解决这个问题。
我的下一步是尝试让 GHC 内联pcg32_random_r,这就是我对其添加严格性的地方,但这似乎还不够。当我查看 GHC 核心时,它不是内联的。
@WillemVanOnsem 我用perform确认结果与C相当,实际上pcg32_random_r函数是内联的。在这个阶段,我已经达到了对 Haskell 和 GHC 的掌握的极限。您能否详细说明为什么perform 表现更好以及如何决定何时使用什么?
这种转换是否可以由编译器自动执行,还是需要设计决策?
问最后一个问题的原因是,我希望将功能和实现选择(速度/空间权衡,...)分开,以最大限度地重用,我希望 Haskell 能在这方面帮助我。
【问题讨论】:
-
你能展示 C 的等价物吗?它是否也将列表元素一一分配,还是将它们全部预先分配为数组?
-
您是否分析并确定
Data.List.iterate比您在这里写的要慢?我认为pcg32_random_r的严格性足以避免累积 thunk 并且iterate的 Prelude 版本使用 foldr/build fusion 可能会通过 GHC 更好地优化 -
@cdk
iterate的问题在于传递给它的函数的严格性无关紧要。如果您丢弃未评估的结果元素,它总是会建立 thunk。这限制了它在您评估您考虑的部分结果的大部分元素的情况下的适用性。 -
@Carl 用户 luqui 曾经在 SO 上展示过如何通过将地图编写为折叠来克服这个问题,使用 seq 将映射与 (:) 节点创建联系起来,以便访问本身自动强制逐步评价。剩下的就是把 iterate 写成一个 map,
iter f x = ys where ys = {- x : map f ys = -} x : foldr ((:) . (f $!)) undefined ys。用xs = iter (1:) [0 :: Int]然后head (xs !! 3)然后:sprint xs进行测试时,似乎可以工作。 -
@WillNess 当然,您可以编写一个比
iterate更严格的函数。但是,如果您丢弃它产生的一些序列值,则没有任何东西可以传递给iterate以防止它建立 thunk。