为什么未执行的编译时代码会增加 Raku 的字节码大小?它会降低运行时性能吗?

Why does non-executed compile-time code increase Raku's bytecode size? Does it slow runtime performance?

考虑以下两个程序:

unit module Comp;
say 'Hello, world!'

unit module Comp;
CHECK { if $*DISTRO.is-win { say 'compiling on Windows' }}
say 'Hello, world!'

天真地,我希望两个程序都能编译成完全相同的字节码:CHECK 块在编译结束时将代码指定为 运行;检查变量然后什么都不做对程序的 运行 时间行为没有影响,因此(我认为)不需要包含在编译的字节码中。

但是,编译这两个程序不会产生相同的字节码。具体来说,编译没有 CHECK 块的版本会创建 24K 的字节码,而带有它的版本则为 60K。为什么这两个版本的字节码不同?字节码的这种差异是否有(或可能有)运行时间成本? (好像是必须的,但我想确定)。

还有一个相关问题:DOC CHECK 块如何与上述内容相符?我的理解是,即使 编译器 在不使用 --doc 标志的 运行 时也会跳过 DOC CHECK 块。与此一致,当给定一个 DOC CHECK 块时,hello-world 程序的字节码确实 而不是 增加大小。但是,如果该块包含 use 语句,它 增加大小。由此,我得出结论 use 在某种程度上是特殊情况,甚至在 DOC CHECK 块中也会执行。那是对的吗?如果是这样,我应该了解其他类似的特殊形式吗?

CHECKBEGIN 块(或其他 BEGIN 时间构造)可能包含转义代码。例如:

BEGIN SomeClass.^add_method('foo', anon method foo() { 42 })

向 class 添加方法,该方法存在于 BEGIN 块的边界之外。因此,编译输出中需要该方法的字节码。目前,Rakudo 保守地将所有内容的字节码包含在 BEGINCHECK 块中。以后有可能在一些简单的情况下避免这种情况。

就 运行 时间成本而言,实现会竭尽全力将永远不会 运行 的字节码成本降至最低(对于这种情况来说不是很多,但是因为标准图书馆很大,但许多程序只使用其中的一小部分)。例如:

  • 字节码是 mmap 的,所以它的一些未使用的部分实际上可能不会被分页到内存中
  • 字节码仅在第一次调用该帧时验证
  • 框架meta-data(它有什么词法)只在第一次调用框架时反序列化
  • 除非有东西引用它,否则代码对象不会被反序列化

use而言,它的动作在解析后立即执行。在 DOC CHECK 块内并不会抑制这一点 - 通常不能,因为 use 可能会带来需要知道的东西才能完成对该块内容的解析。