在许多实际的程序中,您可以为此使用并行策略。这是因为,即使没有明确的机制可以取消不需要的计算,但当垃圾收集器运行时,这将隐式发生。作为一个具体的示例,请考虑以下程序:
import Control.Concurrent
import Control.Parallel.Strategies
import Data.Int
import System.Mem
lcgs :: Int32 -> [Int32]
lcgs = iterate lcg
where lcg x = 1664525 * x + 1013904223
hasWaldo :: Int32 -> Bool
hasWaldo x = waldo `elem` take 40000000 (lcgs x)
waldo :: Int32
waldo = 0
main :: IO ()
main = do
print $ or (map hasWaldo [1..100] `using` parList rseq)
这使用并行列表策略waldo = 0
在100个PRNG流的输出中搜索(永远不会被发现),每个PRNG流具有4000万个数字。编译并运行它:
ghc -threaded -O2 ParallelAny.hs
./ParallelAny +RTS -s -N4
它钉住四个内核约16秒钟,最终打印出来False
。在统计数据中请注意,所有100个火花都已“转换”,因此可以完成操作:
SPARKS: 100(100 converted, 0 overflowed, 0 dud, 0 GC'd, 0 fizzled)
现在,更改waldo
为一个可以尽早找到的值:
waldo = 531186389 -- lcgs 5 !! 50000
并进行修改main
以使线程保持活动状态10秒钟:
main :: IO ()
main = do
print $ or (map hasWaldo [1..100] `using` parList rseq)
threadDelay 10000000
您会观察到它True
几乎可以立即打印,但是4个内核仍保持100%CPU占用率(至少有一小段时间),这说明不必要的计算一直在运行,并且不会短路,就像您可能担心的那样。
但是,如果您在得到答案后强制进行垃圾收集,事情就会改变:
main :: IO ()
main = do
print $ or (map hasWaldo [1..100] `using` parList rseq)
performGC
threadDelay 10000000
现在,您将看到CPU在打印后不久就进入空闲状态True
,并且统计数据表明大多数计算是在运行之前被垃圾回收的:
SPARKS: 100(9 converted, 0 overflowed, 0 dud, 91 GC'd, 0 fizzled)
在现实的程序中,performGC
将不需要显式的,因为理所当然会定期执行GC。找到答案后,一些不必要的计算将继续运行,但是在许多实际情况下,不必要的计算所占比例并不是特别重要。
特别是,如果列表很大,并且列表元素的每个单独测试都很快,那么并行策略将具有出色的实际性能,并且很容易实现讨价还价。