Crystal 解释器 – 一份特别的节日礼物
期待已久的 Crystal 解释器已 合并。要使用它,您需要使用特殊的标志编译 Crystal,并且在撰写本文时,官方版本(.deb、.rpm、Docker 镜像等)尚未使用它进行编译。
这篇文章兼作此特殊功能的常见问题解答。让我们从一开始就了解一下。
为什么 Crystal 需要解释器
虽然许多人会发现这很明显,但指明解释器可能启用的两个具体功能还是很有用的。
- 原则上,解释器应该更快地开始执行代码,因为代码生成阶段被跳过(见下文),允许快速测试某些代码的结果,而无需重新编译。需要注意的是,解释器对于只需要运行一次的简短程序来说很快,但编译模式提供了更高的性能,应该优先用于大多数生产用例。此外,虽然解释程序和编译程序的行为在某些方面可能有所不同(由于系统的原因),但我们打算将差异降到最低,并且大多数程序在解释模式和编译模式下的行为应该完全相同。
- 解释器改善了调试体验,成为语言的即席工具(不像通用的
lldb
)。
目前状态如何?
解释器目前处于实验阶段,还有很多 缺失的部分。在如此早期的阶段合并它的原因是,为了能够对与解释器相关的 PR 进行适当的讨论,并加快其开发速度。对于那些愿意尝试它的人,我们欢迎与解释器相关的 issue,请考虑前面链接中已知的 issue。
它是如何工作的?
该 原始 PR 回答了这个问题。
在解释模式下运行时,语义分析按预期进行,但随后不是使用 LLVM 生成代码,而是将代码编译为字节码(在此 PR 中定义的自定义字节码,与 LLVM 完全无关)。然后有一个解释器来理解这个字节码。
我如何调用它?
⚠️ 这并非一成不变!
假设您已经编译了 Crystal,并将标志 interpreter=1
传递给 make
,您现在可以使用两种模式调用解释器。
crystal i file.cr
此命令在解释模式下运行文件,因此如果 write_hello.cr
包含以下内容。
File.write("/tmp/hello", "Hello from the interpreter!")
puts "done"
调用 crystal i write_hello.cr
将生成文件 /tmp/hello
并将 "done"
打印到 stdout
上。
crystal i
此命令将打开一个交互式 Crystal 会话(REPL),类似于 Ruby 的 irb
。在此会话中,我们可以编写命令并获取其结果。
icr:1:0> File.read_lines("/tmp/hello")
=> ["Hello from the interpreter!"]
(注意:突出显示尚未成为解释器的一部分。)
调试程序
在这两种模式中的任何一种模式下,您都可以在代码中使用 debugger
来在该点调试它。这类似于 Ruby 的 pry
。在那里,您可以使用以下命令(也类似于 pry
)。
step
:转到下一行/指令,可能进入方法。next
:转到下一行/指令,不会进入方法。finish
:退出当前方法。continue
:恢复执行。whereami
:显示调试器的位置。
例如,如果我们在 write_hello.cr
中的 write
和 puts
之间添加 debugger
,我们在解释该文件后会得到以下结果。
From: /tmp/write_hello.cr:3:6 <Program>#/tmp/write_hello.cr:
1: File.write("/tmp/hello", "Hello from the interpreter!")
2: debugger
=> 3: puts "done"
pry>
如果我们 step
,我们可以看到正在调用的标准库中的方法。
pry> step
From: /Users/beta/projects/crystal/crystal/src/kernel.cr:385:1 <Program>#puts:
380:
381: # Prints *objects* to `STDOUT`, each followed by a newline character unless
382: # the object is a `String` and already ends with a newline.
383: #
384: # See also: `IO#puts`.
=> 385: def puts(*objects) : Nil
386: STDOUT.puts *objects
387: end
388:
389: # Inspects *object* to `STDOUT` followed
390: # by a newline. Returns *object*.
pry>
在这一点上,我们可能很好奇:我们传递给此方法的 objects
是什么?我们再次 step
以使变量在作用域内,然后发出以下命令。
pry> objects
{"done"}
解释器加载程序的速度提高了多少?
我们肯定缺少基准测试,更何况并非所有 shard 都能成功解释。在标准库和 Crystal 模式 中的一些随机文件中进行测试,它加载它们(并执行它们,见下文)的速度比编译它们的速度快 50% 到 75%(比较 time crystal <file>
与 time crystal i <file>
)。这在很大程度上取决于 Crystal 在编译器和解释器(如解析和语义分析)的常见步骤上花费的时间。
它运行速度有多快(或多慢)?
正如其创建者 Ary 在 视频中 的 Crystal Conf 1.0 中所展示的,它的运行速度足够快——对于解释器来说。当然,它远没有像 Ruby 这样成熟的解释器实现那样高效,但在我们的初步测试中,它的运行速度足够快,可以满足预期的用例。例如,它可以在一秒钟内处理数百万次整数加法。也就是说,不建议使用解释器来处理密集型任务,因为那是编译程序的任务。
总之,合并 PR 是在 Crystal 中获得一个工作解释器的第一步,更重要的是,它证明了 Crystal 团队致力于改善开发人员体验的决心。