为什么这样的事情在Haskell中运行得非常慢?
test = [x|a<-[1..100],b<-[1..100],c<-[1..100],d<-[1..100],let x = a]
print $ length test
只有大约10^8
个数字才能运行,它应该在眨眼之间完成,但它似乎永远都在运行并且几乎崩溃。
答案 0 :(得分:5)
你在ghci或编译程序中运行它吗?它有很大的不同。
如果在ghci中,则ghci将保持false
的计算值,以防您以后想要使用它。通常这是一个好主意,但在这种情况下,test
是一个巨大的价值,无论如何重新计算都很便宜。多大?对于初学者来说,它是10 ^ 8个元素的列表,并且(在64位系统上)列表每个元素需要24个字节,因此已经是2.4G。然后是值本身的空间使用。有人可能会认为这些值都来自test
,所以它们应该被共享并且总共使用的空间可以忽略不计。但是列表中的值实际上是[1..100]
形式,可能取决于x
,a
,b
和c
以及{{1}永远不会检查列表中的值,因为它遍历它。因此,每个元素将被表示为一个引用d
,length
,a
和b
的闭包,它至少需要8 *(4 + 1)=多40个字节,使我们总共达到6.4G。
这是相当多的,当你分配6.4G的数据时,垃圾收集器必须进行大量的复制,所有数据都是永久存在的。这需要很长时间,而不是实际计算列表或其长度。
如果你编译程序
c
然后d
不必保持活动,因为它的长度正在被计算,显然它永远不会被再次使用。所以现在GC几乎没有工作要做,程序运行几秒钟(合理的〜{10 ^ 8列表节点分配和test = [x|a<-[1..100],b<-[1..100],c<-[1..100],d<-[1..100],let x = a]
main = print $ length test
上的计算)。
答案 1 :(得分:4)
你不只是运行循环10 ^ 8次,你正在创建一个包含10 ^ 8个元素的列表。由于您使用的是length
,因此Haskell必须实际评估整个列表以返回其长度。列表中的每个元素都有一个字,可能是32位,也可能是64位。在乐观的假设它是32位(4字节),你刚刚分配了400 MB(约381.5 MiB)的内存。如果它是64位,那么你刚刚分配的内存为800 MB(约763 MiB)。根据系统上发生的其他情况,您可能只是通过在一个块上分配那么多RAM来点击交换文件/交换分区。
如果还有其他细微之处,我不知道它们,但是内存使用是我第一次怀疑为什么这么慢。