yfaming on Nostr: 看完了 Crafting Interpreters 第 30 章,Optimization。 ...
看完了 Crafting Interpreters 第 30 章,Optimization。
这是本书最后一整,完结撒花!
本章用实际的例子介绍了如何进行性能优化。
现代计算机非常复杂,无法仅凭思考就确定性能瓶颈。优化前,我们需要对性能进行测量, 需要写 benchmark 基准测试,来测试程序的性能,并且进行 profiling 性能剖析,跟踪代码执行过程中硬件资源使用情况。
本章展示了两个具体的优化。
第一个是优化 hash table 的访问性能。profiling 发现,`tableGet` 函数占用了 72% 的运行时间。进一步细化,发现取模操作 `%` 占用了 70% 的运行时间。优化方案是,将取模操作改为位操作。优化后,性能提升一倍左右。
第二个优化是 NaN boxing。
clox 中值通过 Value 表示,它是一个 tagged union。在 64 位机器上,Value 占用 16 字节,`type` 和 union `as` 各 8 字节。`type` 占用的 8 字节里,有 4 字节是 padding。使用 NaN boxing 之后,我们可以彻底去掉 `type` 字段,让 Value 只占用 8 字节,节省一半内存空间。
具体原理如下。
64 位的 IEEE 754 浮点数,结构分为 3 部分:1-bit sign、11-bit exponent、52-bit mantissa。当所有的 exponent 位置 1 时,这个浮点数就是 NaN,Not a Number,不论其他部分如何。
这意味着有许多不同的 NaN,可分为两类,signaling NaN 和 quiet NaN。signaling NaN 的 mantissa 最高位为 0。quiet NaN 的 mantissa 最高位则为 1。
标准规定,芯片读到 signalling NaN 时,可以中止程序,甚至自毁。所以我们只使用 quiet NaN 进行优化。但还要避开 Intel 的 QNaN Floating-Point Indefinite。最终,我们有 51 位可供使用。
对于指针,在 64 位架构上它是 64 位的,但是,广泛使用的芯片目前只使用低 48 位。剩余的 16 位要么未指定,要么始终为 0。
假如我们把指针塞进 quiet NaN 浮点数里面,还剩余 3 位空间。这 3 位刚好够用来存储一些小的类型标签,以区分 nil、bool 和指针等。这样的操作,就是 NaN boxing。
64 位的浮点数,除了表示正常的数字,我们还利用 quiet NaN 来表示指针、bool、nil。也就是说,只要 8 个字节,就可以表示 clox 所有种类的值了。
接下来,开始 Crafting Interpreters 第二部分,用 C 实现的 Lox 字节码解释器 clox。
在第一部分,用 Java 实现的 tree-walk interpreter jlox 中,我们完整实现了 Lox 解释器,包括 scanner、parser、resolver、interpreter 等等。
但是,第一部分的解释器利用了 Java 语言的许多特性。
比如,Lox 的值,全部用 Java 的 Object 类型表示。
Lox 里的 return,用 Java 的异常来实现。
而 Java 自带 GC,所以 Lox 中对象的生命周期,我们也完全没考虑过。
在第二部分,我们将使用 C 语言实现解释器。C 比 Java 更加 low-level,它不支持 OOP,不支持异常和 GC。
因此,这几个问题,我们就需要在 C 里面手动处理了。
而且,第二部分我们实现的是字节码解释器。我们需要定义字节码,并在 parsing 将 ast 编译为字节码。
用不同语言实现同一门语言,但采用略有不同的技术,可以加深我们的理解,并学习 compiler/interpreter 领域的不同主题。
Published at
2026-09-14 09:03:56 UTCEvent JSON
{
"id": "c961510670a3d3e1ef19d0133c4a7c20b3d264810c874120b58bdd663d592470",
"pubkey": "908fbc3babc322eea473a0ab1e6bf6b3adcf899288ac97b2fa79f34ad408d4e4",
"created_at": 1789376636,
"kind": 1,
"tags": [
[
"q",
"1aea59b1f714db6bebf6019f30c55e36795d9bbba666682c752163c90db1acff",
"wss://lang.relays.land/zh",
"908fbc3babc322eea473a0ab1e6bf6b3adcf899288ac97b2fa79f34ad408d4e4"
]
],
"content": "看完了 Crafting Interpreters 第 30 章,Optimization。\n\n这是本书最后一整,完结撒花!\n\n本章用实际的例子介绍了如何进行性能优化。\n\n现代计算机非常复杂,无法仅凭思考就确定性能瓶颈。优化前,我们需要对性能进行测量, 需要写 benchmark 基准测试,来测试程序的性能,并且进行 profiling 性能剖析,跟踪代码执行过程中硬件资源使用情况。\n\n本章展示了两个具体的优化。\n\n第一个是优化 hash table 的访问性能。profiling 发现,`tableGet` 函数占用了 72% 的运行时间。进一步细化,发现取模操作 `%` 占用了 70% 的运行时间。优化方案是,将取模操作改为位操作。优化后,性能提升一倍左右。\n\n第二个优化是 NaN boxing。\nclox 中值通过 Value 表示,它是一个 tagged union。在 64 位机器上,Value 占用 16 字节,`type` 和 union `as` 各 8 字节。`type` 占用的 8 字节里,有 4 字节是 padding。使用 NaN boxing 之后,我们可以彻底去掉 `type` 字段,让 Value 只占用 8 字节,节省一半内存空间。\n\n具体原理如下。\n\n64 位的 IEEE 754 浮点数,结构分为 3 部分:1-bit sign、11-bit exponent、52-bit mantissa。当所有的 exponent 位置 1 时,这个浮点数就是 NaN,Not a Number,不论其他部分如何。\n这意味着有许多不同的 NaN,可分为两类,signaling NaN 和 quiet NaN。signaling NaN 的 mantissa 最高位为 0。quiet NaN 的 mantissa 最高位则为 1。\n标准规定,芯片读到 signalling NaN 时,可以中止程序,甚至自毁。所以我们只使用 quiet NaN 进行优化。但还要避开 Intel 的 QNaN Floating-Point Indefinite。最终,我们有 51 位可供使用。\n\n对于指针,在 64 位架构上它是 64 位的,但是,广泛使用的芯片目前只使用低 48 位。剩余的 16 位要么未指定,要么始终为 0。\n\n假如我们把指针塞进 quiet NaN 浮点数里面,还剩余 3 位空间。这 3 位刚好够用来存储一些小的类型标签,以区分 nil、bool 和指针等。这样的操作,就是 NaN boxing。\n\n64 位的浮点数,除了表示正常的数字,我们还利用 quiet NaN 来表示指针、bool、nil。也就是说,只要 8 个字节,就可以表示 clox 所有种类的值了。\n\n\nnostr:nevent1qvzqqqqqqypzpyy0hsa6hseza6j88g9tre4ldvade7ye9z9vj7e0570nft2q348yqyvhwumn8ghj7mrpdenjuun9d3shjuewd3skuep00f5qzxrhwden5te0wfjkccte9ehx7um5wfaxstn0wfnj7qpqrt49nv0hzndkh6lkqx0np327xeu4mxam5enxstr4y93ujrd34nls7vs4y9",
"sig": "d7dc2abd4f267d47b479c084dc1c9f33cd49f8b180d601120a50625c620a9459afd4363bead1301e658ea6c6b73b24602574be6e5d7755c0c6e06cd17e29cb49"
}