我怎样才能停止 GHCi 中的无限评估?
How can I stop infinite evaluation in GHCi?
当我 运行 这样的事情时:
Prelude> cycle "ab"
我可以看到 "ab" 的无限打印。要停止它,我只需使用 Ctrl+c。并且有效。
当我运行:
Prelude Data.List> nub $ cycle "ab"
我无法阻止它。
问题:
- 为什么会这样?
- 如何停止此操作?
更新:
Ubuntu: version 18.10
GHCi: version 8.2.2
好问题!但是,由于 已经将重点放在您的第二部分,因此我们不再重复。相反,让我们关注第一个。
Why it so?
GHC 积极优化循环。如果没有分配,它会进一步优化它们 that it's even a known bug:
19.2.1. Bugs in GHC
GHC’s runtime system implements cooperative multitasking, with context switching potentially occurring only when a program allocates. This means that programs that do not allocate may never context switch. This is especially true of programs using STM, which may deadlock after observing inconsistent state. See Trac #367 for further discussion. [emphasis mine]
If you are hit by this, you may want to compile the affected module with -fno-omit-yields
(see -f*: platform-independent flags). This flag ensures that yield points are inserted at every function entrypoint (at the expense of a bit of performance).
如果我们检查 -fomit-yields
,我们发现:
-fomit-yields
Default: yield points enabled
Tells GHC to omit heap checks when no allocation is being performed. While this improves binary sizes by about 5%, it also means
that threads run in tight non-allocating loops will not get preempted
in a timely fashion. If it is important to always be able to interrupt
such threads, you should turn this optimization off. Consider also
recompiling all libraries with this optimization turned off, if you
need to guarantee interruptibility. [emphasis mine]
nub $ cycle "ab"
是一个紧凑的非分配循环,尽管 last $ repeat 1
是一个更明显的非分配示例。
"yield points enabled" 具有误导性:-fomit-yields
默认启用。由于标准库是使用 -fomit-yields
编译的, 标准库中所有导致紧密、非分配循环的函数 可能会显示 GHCi 中的行为,因为您永远不会重新编译它们.
我们可以用下面的程序来验证:
-- Test.hs
myLast :: [a] -> Maybe a
myLast [x] = Just x
myLast (_:xs) = myLast xs
myLast _ = Nothing
main = print $ myLast $ repeat 1
如果我们在 GHCi 中 运行 它 无需预先编译,我们可以使用 C-c 退出它 :
$ ghci Test.hs
[1 of 1] Compiling Main ( Test.hs, interpreted )
Ok, one module loaded.
*Main> :main <pressing C-c after a while>
Interrupted.
如果我们编译它然后在 GHCi 中重新运行它,它会挂起:
$ ghc Test.hs
[1 of 1] Compiling Main ( Test.hs, Test.o )
Linking Test.exe ...
$ ghci Test.hs
Ok, one module loaded.
*Main> :main
<hangs indefinitely>
请注意,如果您不使用 Windows,则需要 -dynamic
,否则 GHCi 将重新编译源文件。但是,如果我们使用-fno-omit-yield
,我们突然又可以退出了(在Windows)。
我们可以用另一个小片段再次验证:
Prelude> last xs = case xs of [x] -> x ; (_:ys) -> last ys
Prelude> last $ repeat 1
^CInterrupted
由于 ghci
不使用任何优化,它也不使用 -fomit-yield
(因此启用了 -fno-omit-yield
)。 last
的新变体不会产生与 Prelude.last
相同的行为,因为它不是用 fomit-yield
.
编译的
现在我们知道了为什么会发生这种情况,我们知道我们将在整个标准库中遇到这种行为,因为标准库是使用 -fomit-yield
编译的。
当我 运行 这样的事情时:
Prelude> cycle "ab"
我可以看到 "ab" 的无限打印。要停止它,我只需使用 Ctrl+c。并且有效。
当我运行:
Prelude Data.List> nub $ cycle "ab"
我无法阻止它。
问题:
- 为什么会这样?
- 如何停止此操作?
更新:
Ubuntu: version 18.10
GHCi: version 8.2.2
好问题!但是,由于
Why it so?
GHC 积极优化循环。如果没有分配,它会进一步优化它们 that it's even a known bug:
19.2.1. Bugs in GHC
GHC’s runtime system implements cooperative multitasking, with context switching potentially occurring only when a program allocates. This means that programs that do not allocate may never context switch. This is especially true of programs using STM, which may deadlock after observing inconsistent state. See Trac #367 for further discussion. [emphasis mine]
If you are hit by this, you may want to compile the affected module with
-fno-omit-yields
(see -f*: platform-independent flags). This flag ensures that yield points are inserted at every function entrypoint (at the expense of a bit of performance).
如果我们检查 -fomit-yields
,我们发现:
-fomit-yields
Default: yield points enabled
Tells GHC to omit heap checks when no allocation is being performed. While this improves binary sizes by about 5%, it also means that threads run in tight non-allocating loops will not get preempted in a timely fashion. If it is important to always be able to interrupt such threads, you should turn this optimization off. Consider also recompiling all libraries with this optimization turned off, if you need to guarantee interruptibility. [emphasis mine]
nub $ cycle "ab"
是一个紧凑的非分配循环,尽管 last $ repeat 1
是一个更明显的非分配示例。
"yield points enabled" 具有误导性:-fomit-yields
默认启用。由于标准库是使用 -fomit-yields
编译的, 标准库中所有导致紧密、非分配循环的函数 可能会显示 GHCi 中的行为,因为您永远不会重新编译它们.
我们可以用下面的程序来验证:
-- Test.hs
myLast :: [a] -> Maybe a
myLast [x] = Just x
myLast (_:xs) = myLast xs
myLast _ = Nothing
main = print $ myLast $ repeat 1
如果我们在 GHCi 中 运行 它 无需预先编译,我们可以使用 C-c 退出它 :
$ ghci Test.hs
[1 of 1] Compiling Main ( Test.hs, interpreted )
Ok, one module loaded.
*Main> :main <pressing C-c after a while>
Interrupted.
如果我们编译它然后在 GHCi 中重新运行它,它会挂起:
$ ghc Test.hs
[1 of 1] Compiling Main ( Test.hs, Test.o )
Linking Test.exe ...
$ ghci Test.hs
Ok, one module loaded.
*Main> :main
<hangs indefinitely>
请注意,如果您不使用 Windows,则需要 -dynamic
,否则 GHCi 将重新编译源文件。但是,如果我们使用-fno-omit-yield
,我们突然又可以退出了(在Windows)。
我们可以用另一个小片段再次验证:
Prelude> last xs = case xs of [x] -> x ; (_:ys) -> last ys
Prelude> last $ repeat 1
^CInterrupted
由于 ghci
不使用任何优化,它也不使用 -fomit-yield
(因此启用了 -fno-omit-yield
)。 last
的新变体不会产生与 Prelude.last
相同的行为,因为它不是用 fomit-yield
.
现在我们知道了为什么会发生这种情况,我们知道我们将在整个标准库中遇到这种行为,因为标准库是使用 -fomit-yield
编译的。