示例工程【免费下载链接】malmal - Make a Lisp项目地址https://gitcode.com/gh_mirrors/ma/mal点击查看免费下载本篇技术指南围绕当前仓库malMake a Lisp中的java-truffle实现展开它用 Oracle 的 Truffle 框架在 Java 上重写了一套 Mal 解释器并在 GraalVM 编译器上通过部分求值partial evaluation获得数倍乃至数十倍的性能提升。文章以该实现的官方文档 impls/java-truffle/README.md 为主体结合源码逐步讲解从朴素解释器Step A到函数调用特化、静态符号查找、查找缓存、宏内联Step B–E的完整优化路径以及基准测试方法与 AOT 原生镜像构建。读完本文你将理解 Truffle/GraalVM 的核心加速机制掌握一套可直接复用的解释器渐进式特化工程方法。1. Truffle 的论点部分求值如何让解释器变快Truffle 是一个用于实现解释器的 Java 库。当用 Truffle 写出的解释器运行在 GraalVM 上时GraalVM 编译器能够使用一种叫部分求值的技术把解释器 被解释程序整体 JIT 编译成机器码而不是逐条解释执行。部分求值存在一个微妙的平衡问题如果把解释器的每一行代码包括支撑库等都纳入求值范围编译产物会膨胀到不可接受的程度必须划定边界但边界划得太多编译带来的加速又可能不值得编译本身的代价。Truffle 的核心主张是只需一小撮原始操作primitives就能让经部分求值实现 JIT 编译变得切实可行。这些原始操作把解释器运行时收集到的动态数据反馈给编译器让编译器在编译期对解释器代码做特化specialize即乐观地简化代码同时编译器会插入轻量级运行时检查来守护这些简化所依赖的假设。一旦检查失败已编译代码就会被去优化de-optimized控制权交还给解释器继续执行。对这套思想更深入的阐述可参考 PLDI 2017 论文Practical Partial Evaluation for High-Performance Dynamic Language Runtimes。2. 实验动机与第一个惊人结果作者给自己出的题目是一个没有编译器设计背景的工程师能否仅用 Truffle 实现一个动态语言Mal解释器且性能大幅超越仓库中已有的纯 Java 实现impls/java答案在文档中很直接Yup。以递归斐波那契为基准tests/fib.mal在 OpenJDK 11 上运行 Java 版、在 GraalVM 上运行 java-truffle 版# Recursive Fibonacci on OpenJDK 11 with java mal $ ./run ../tests/fib.mal 30 10 Times (in ms) for (fib 30) on java: [2062 1809 1814 1777 1772 1791 1725 1723 1786 1745] # Recursive Fibonacci on GraalVM with java-truffle mal $ ./run ../tests/fib.mal 30 10 Times (in ms) for (fib 30) on java-truffle: [280 142 21 26 22 75 21 26 21 24]这大约是82 倍的加速。作为参照文档还给出了 OpenJDK 11 上带类型提示^long的 Clojure 1.10.0 同一计算耗时约 31 ms——注意此时 GraalVM 上的 Truffle Mal 已经能与之掰手腕甚至更快。当然作者也提醒递归 Fibonacci 是接近最理想情况的基准远不足以全面刻画实现性能。在展开细节之前有必要先交代两个免责声明原文 Disclaimers实验成功功劳属于 Truffle/GraalVM 团队实验失败责任归于作者本人——读者应默认本实现的任何缺陷都源于作者对工具的理解与运用不当而非 Truffle 或 GraalVM 的固有限制。本实现不是惯用 Java也不是 Truffle 的惯用用法。它把大量包私有类塞进单个文件如Types.java各 step 文件间有大量重复这是为了贴合 Mal 项目的教学组织方式与既有 Java 实现的结构。每个 Mal step 注册一个独立的 Truffle 语言实现语言 id 形如mal_step${n}不同 step 拥有各自的 AST 节点子类但共享Core.java中的内建 AST 节点与Types.java中的运行时类型这种共享也给Core.java带来了一些别扭之处。3. 环境准备GraalVM、Gradle 与 Docker运行 java-truffle 实现的前置要求文档 PrerequisitesGraalVM Community Edition版本20.1.0 或更高应位于 PATH 中且由JAVA_HOME指向若不用仓库提供的 Docker 镜像还需自行安装 Gradle。仓库中impls/java-truffle/Dockerfile给出了一个现成环境基础镜像为ghcr.io/graalvm/graalvm-ce:21.1.0通过microdnf安装python3与unzippython 用于 Mal 的某些测试脚本再下载 Gradle 7.0.2 并加入 PATH工作目录设为/mal。启动脚本 impls/java-truffle/run 揭示了实际的 JVM 运行参数CP$(gradle -q --console plain printClasspath) java \ -Dgraalvm.locatorDisabledtrue \ -Xss8m \ --add-opens org.graalvm.truffle/com.oracle.truffle.apiALL-UNNAMED \ --add-opens org.graalvm.truffle/com.oracle.truffle.api.interopALL-UNNAMED \ --add-opens org.graalvm.truffle/com.oracle.truffle.api.nodesALL-UNNAMED \ -classpath $CP \ truffle.mal.${STEP:-stepE_macros} $要点通过环境变量STEP选择运行哪个 step默认stepE_macros即完整优化后的最终实现-Xss8m把线程栈扩到 8 MB这是自举self-hosted场景所必需的——自托管 Mal 不做尾调用优化会占用更多栈空间见第 5 节通过--add-opens打开org.graalvm.truffle各内部包的访问权限这是 Truffle API 在较新 JDK 模块化体系下的常规要求gradle -q printClasspath是项目 Gradle 配置提供的 classpath 打印任务。构建则非常简单impls/java-truffle/Makefile 只有gradle build、gradle clean两个目标。4. 总体设计Step A 是朴素基线Step B–E 逐级特化文档 Outline of Approach 给出了清晰的实验架构Step 0 到 Step A刻意不做任何 Truffle 特有优化。Step A 是一个完全朴素的 Truffle 应用——用 Truffle AST 节点实现纯粹的解释器但不借助任何 Truffle 原始操作来特化编译代码。通过对比Truffle Step A 跑在 OpenJDK 上与纯 Java Step A 跑在 OpenJDK 上可以度量 Truffle 框架本身给解释器带来的开销通过对比Truffle Step A 跑在 OpenJDK 上与Truffle Step A 跑在 GraalVM 上可以度量 GraalVM 编译器免费送给语言实现者的性能。Step A 之后的每一步都启用 Truffle 原始操作以支持编译期特化Step特化内容核心机制B函数调用特化假设调用点是**单态monomorphic**的即同一位置总是调用同一个函数直到被证伪DirectCallNode 编译期常量折叠 内联C环境查找特化为词法作用域内的符号分配槽位slot用Object[]数组替代 HashMapLexicalScope 槽位数组 AssumptionD环境查找进一步特化对闭包外的符号查找做缓存假设其未被重新绑定直接跳过查找Assumption 缓存失效机制E宏展开特化让宏展开结果替换整个 apply 节点以向后兼容的方式扩展了 Mal 的宏语义AST 自修改replace5. 性能评估方法双环境、三基准Truffle Mal 的性能相对 Java Mal 评估每个基准都在 OpenJDK 与 GraalVM 两个环境下各跑一遍 Java 版与 Truffle 版。文档给出的两套java -version输出# OpenJDK openjdk version 11.0.7 2020-04-14 OpenJDK Runtime Environment (build 11.0.710-post-Ubuntu-2ubuntu218.04) # GraalVM openjdk version 11.0.7 2020-04-14 OpenJDK Runtime Environment GraalVM CE 20.1.0 (build 11.0.710-jvmci-20.1-b02)需要特别说明的公平性因素Truffle Mal 复用了 Clojure 的持久化向量与映射实现。这大概率不影响 perf4 与 fib 基准它们不操作向量/映射但自托管 Mal依赖宿主 Mal 的 map 实现来构建环境。Java Mal 的 map 基于java.util.HashMap、没有结构共享其assoc/dissoc复杂度严格劣于 Truffle MalO(n) 对 O(lg n)不过实际环境规模很小作者声明未在结果中对此做任何校正。三个基准的规程如下Fib聚焦符号查找、算术与函数应用。用朴素递归计算第 30 个斐波那契数跑 10 轮取最快。测试脚本见 tests/fib.mal。Busywork是perf3.mal基准的重构主要测试宏与 atom 性能脚本见 tests/busywork.mal。测量执行 10,000 次busywork函数迭代的耗时同样跑 10 轮取最快。Fib on Mal用自托管 Mal 运行fib.mal给每个实现更全面的锻炼。计算第 15 个斐波那契数 10 次取最快。注意自托管 Mal 不支持尾调用优化运行越久栈消耗越大因此 Truffle Mal 需要把栈从默认 1 MB 提到8 MB正是 run 脚本中-Xss8m的由来以避免栈溢出。以下各节表格中Truffle 性能以绝对耗时给出并相对 Java 实现OpenJDK 与 GraalVM 两轮中较快者计算倍数。6. Step A朴素解释器的真实开销Step A 是完全用 Truffle AST 节点写的、不做任何特化的解释器其结果回答了Truffle 框架的基座成本是多少BenchmarkJava (OpenJDK)Truffle (OpenJDK)Truffle (GraalVM)Fib1700 ms1293 ms (1.3x)675 ms (2.5x)Busywork781 ms914 ms888 msFib on Mal686 ms2101 ms1664 msFib 基准上OpenJDK 下 Java 与 Truffle 同处一个量级Truffle 快 1.3x而 Truffle 版在 GraalVM 上相对 OpenJDK 又近乎免费地快了约 2 倍达到纯 Java 的 2.5 倍。Busywork 却是另一番景象Truffle 版在两个环境下都略慢GraalVM 也没带来多少提升。Fib on Mal 更奇怪OpenJDK 下 Truffle 慢了 3 倍GraalVM 帮助也不大。稍微做点 profiling 答案就出来了宏macros。文档引用了stepA_mal.java中ApplyNode的处理逻辑对应源码 stepA_mal.javaif (fn.isMacro) { // Mals macro semantics are... interesting. To preserve them in the // general case, we must re-expand a macro each time its applied. // Executing the result means turning it into a Truffle AST, creating // a CallTarget, calling it, and then throwing it away. // This is TERRIBLE for performance! Truffle should not be used like this! var result applyMacro(env, fn); var newRoot new MalRootNode(language, result, env, invokeNode.tailPosition); var target Truffle.getRuntime().createCallTarget(newRoot); return invokeNode.invoke(target, new Object[] {}, false); } else {Truffle 的CallTarget代表一个可被外部调用的 AST其构造是重量级操作会遍历整个 AST 做各种初始化这个成本本应摊薄在后续大量调用中并被调用足够多次才被 JIT 编译的收益抵消。Truffle AST 支持自修改理想做法是宏只展开一次、然后用展开结果替换宏应用节点。但 Mal 的宏语义恰恰不允许宏可以根据作用域内任意环境的当前值、甚至用户输入来决定如何展开代码更糟的是Mal 增量式宏展开允许写出尾递归宏如果急于展开会占用随输入指数增长的空间——例如文档中的sumdown-via-macro*示例在任何一个符合规范的 Mal 实现里都能无问题执行。因此 Step A 中每次应用宏都要重新展开并重建 AST对性能是毁灭性的。7. Step B函数调用特化单态调用点Step A 中所有函数调用点都用 Truffle 的IndirectCallNode表示Truffle 还提供了DirectCallNode用于同一位置总是调用同一函数的调用点而直接调用可以被 GraalVM 编译器内联。Mal 的语义让静态证明某调用点总调同一函数变得困难甚至有时不可能但解释器可以乐观假设调用点是直接的直到事实推翻它。Step B 版InvokeNode的核心逻辑文档引用的源码见 stepB_calls.javastatic class InvokeNode extends AbstractInvokeNode { final boolean tailPosition; CompilationFinal private boolean initialized false; CompilationFinal private boolean usingCachedTarget; CompilationFinal private CallTarget cachedTarget; CompilationFinal Child private DirectCallNode directCallNode; CompilationFinal Child private IndirectCallNode indirectCallNode; Object invoke(CallTarget target, Object[] args, boolean allowTailCall) { if (tailPosition allowTailCall) { throw new TailCallException(target, args); } else { if (!initialized) { CompilerDirectives.transferToInterpreterAndInvalidate(); initialized true; usingCachedTarget true; cachedTarget target; directCallNode Truffle.getRuntime().createDirectCallNode(target); } while (true) { try { if (usingCachedTarget) { if (cachedTarget target) { return directCallNode.call(args); } CompilerDirectives.transferToInterpreterAndInvalidate(); usingCachedTarget false; indirectCallNode Truffle.getRuntime().createIndirectCallNode(); } return indirectCallNode.call(target, args); } catch (TailCallException ex) { target ex.callTarget; args ex.args; } } } } }表面看分支变多了为什么反而更快关键在两处新成员变量全部标注CompilationFinal告诉编译器这些变量在已编译代码中不会再变可以当作final处理用CompilerDirectives.transferToInterpreterAndInvalidate()确保它们确实不变在解释执行时这是空操作在已编译代码中它会被替换成一条触发去优化、交还解释器继续执行的指令。于是一个非尾调用位置、且每次调用都是同一个函数、且已热到触发编译的调用点其invoke在编译后被简化成Object invoke(CallTarget target, Object[] args, boolean allowTailCall) { while (true) { try { if (cachedTarget target) { return directCallNode.call(args); } CompilerDirectives.transferToInterpreterAndInvalidate(); } catch (TailCallException ex) { target ex.callTarget; args ex.args; } } }因为用的是DirectCallNode编译器还可能把被调函数一并内联让部分求值算法跨越函数边界延伸。Step B 实测BenchmarkJava (OpenJDK)Truffle (OpenJDK)Truffle (GraalVM)Fib1700 ms991 ms (1.7x)430 ms (3.9x)Busywork781 ms671 ms (1.2x)409 ms (1.9x)Fib on Mal686 ms1912 ms (0.35x)1407 ms (0.48x)相比 Step A 全线小幅改善Busywork 在 GraalVM 上较 Step A 提升了 2 倍。8. Step C静态符号查找LexicalScope 与槽位profiling 显示执行 Mal 程序的大量工作其实是环境维护构造 HashMap、put 符号/值对、再查回来。对于函数调用密集的代码如 Fib 基准这累积成巨大开销。文档提出的问题很尖锐为什么一定要用 HashMap在从 Mal 表单构造 AST 时完全可以跟踪每个词法作用域里的变量给每个变量分配一个槽位环境对应Object[]数组的索引执行时用Object[]构造环境按槽位读写彻底告别 HashMap。麻烦在于def!可以在运行时突变环境为从未经let*/fn*声明的符号新增绑定。文档给出的例子(def! f (fn* [x b] (do (who-knows? b) y)))y不在词法作用域内不会分到槽位只能在执行期到全局环境查找。但如果运行时who-knows?解析成一个宏例如(fn* [b] (if b(def! y 42)))当b为真时y 终究被绑定到了函数体环境里——可环境的对象数组里并没有它的槽位。Truffle 的力量就在于不需要静态证明槽位分配有效这不是写编译器只需假设槽位分配有效直到发现无效再回退到较慢但通用的路径。Step C 的高层做法与文档对应具体实现见 MalEnv.java 与其中的LexicalScope类引入LexicalScope类把符号映射到数组索引并把LexicalScope对象贯穿 AST 构造过程给MalEnv在常规bindingsHashMap 之外增加staticBindingsObject 数组数组大小由关联LexicalScope的符号数决定bindingsHashMap 改为惰性创建——只有当某个不在LexicalScope里的符号被def!绑定时才真正构造给MalEnv增加基于槽位的get/set方法与既有基于符号的方法并存扩展let*与fn*的 AST 节点创建携带正确符号的LexicalScope、分配槽位并用槽位版get/set绑定符号修改符号查找的 AST 节点当符号在词法作用域内时投机地走槽位查找前提是假设它未被def!重新定义。最后这一点是整个方案的关键用 Truffle 的Assumption抽象把槽位查找依赖的假设告知编译器。LexicalScope分配槽位时创建该符号未被def!绑定的假设源码中notDynamicallyBound假设见 MalEnv.java槽位查找代码由该假设守护MalEnv的动态set方法def!使用的那个会使假设失效scope.wasDynamicallyBound(symbol)见 MalEnv.java触发可能因此错误的符号查找去优化。Step C 实测BenchmarkJava (OpenJDK)Truffle (OpenJDK)Truffle (GraalVM)Fib1700 ms829 ms (2.1x)219 ms (7.8x)Busywork781 ms686 ms (1.1x)394 ms (2.0x)Fib on Mal686 ms1932 ms (0.35x)1507 ms (0.46x)在 GraalVM 上 Fib 基准比 Step B 又快了 2 倍多逼近纯 Java 的 8 倍展示了 Truffle 框架配合 GraalVM 的真实威力OpenJDK 下只有约 1.2x 的小幅提升来自省掉部分 HashMap 开销。但另外两个基准毫无起色Fib on Mal 甚至变慢了——还是宏在作祟每次遇到宏都新建 AST编译器永远没有机会编译它我们付了全部簿记开销却得不到任何收益。9. Step D缓存符号查找有了 Step C 的地基可以更进一步词法作用域内的符号走Object[]快路径编译器甚至能把沿环境链上溯的循环展开——MalEnv.get(EnvSlot)标注了ExplodeLoop见 MalEnv.java但不在词法作用域内的符号呢行为良好的 Mal 程序里这类符号要么产生运行时环境要么解析到全局环境实践中它们几乎总在查找核心函数其值在程序生命周期内不太可能但并非不可能变化。Step D 的做法对非词法作用域的符号查找直接缓存查到的值并跳过后续查找除非该符号被重新绑定。同样为每次缓存查找创建一个Assumption表示假设它未被重新定义并让def!在重绑定时使假设失效。源码中的cachedGet与CachedResult携带notRedefined假设正是这一机制见 MalEnv.java 与 MalEnv.java。Step D 实测BenchmarkJava (OpenJDK)Truffle (OpenJDK)Truffle (GraalVM)Fib1700 ms733 ms (2.3x)18 ms (94x !!)Busywork781 ms657 ms (1.2x)311 ms (2.5x)Fib on Mal686 ms1971 ms (0.35x)1474 ms (0.47x)Fib 基准上缓存符号查找带来了巨大差异。看fib的代码(def! fib (fn* [n] (if ( n 0) 1 (if ( n 1) 1 ( (fib (- n 1)) (fib (- n 2)))))))其中有 7 处非词法作用域符号查找、、、fib、-、fib、-现在全部被消除剩下的只有n的快路径槽位查找、两次比较、三次算术运算且全部被编译器内联编译器甚至通过把fib内联进自身展开了若干层递归。最终结果不仅快而且在 OpenJDK 上甚至超过了带类型提示的 Clojure。但宏依然在其他基准上击败了我们是时候正面解决它了。10. Step E宏内联以向后兼容的方式扩展 Mal 语义严格在 Mal 语义内宏是性能杀手语义过于动态。但在实践中宏经常只是引入语法糖例如cond、or、and、-、-——它们的展开行为不依赖运行时值每次应用展开结果相同产出代码规模与输入线性相关。为什么要每次应用都重新展开何不展开一次、直接替换结果Clojure 的宏就是这样工作的。Step E 的作弊方案见 stepE_macros.java扩展 Mal 语义——凡是元数据 map 中含有:inline? true条目的宏只展开一次其展开结果永久内联替换宏应用位置然后把上述宏都标记为可内联。文档强调这不是 Truffle 特有优化任何支持该语义的 Mal 解释器都能获得可观收益只不过 Mal 数据结构的不可变特性可能让其他解释器的重构更棘手而对 Truffle 来说这只是顺水推舟——Truffle AST 明确支持自修改。isInlinableMacro判断宏元数据见 stepE_macros.java核心内联逻辑极其简短见 stepE_macros.javaif (fn.isMacro) { var expanded applyMacro(env, fn); if (isInlinableMacro(fn)) { CompilerDirectives.transferToInterpreterAndInvalidate(); var newNode expanded.body; this.replace(newNode); return newNode.executeGeneric(frame, env); } else { return invokeMacro(expanded); } }仅仅几行代码。宏的定义处如何打标以 stepE_macros.java 中的cond为例(defmacro! cond ^{:inline? true} (fn* ...))基准脚本 tests/busywork.mal 中也用^{:inline? true}显式标记了and、or、-、-宏。有意思的是仓库lib目录下的共享 Mal 库也把:inline? true作为一种通用约定见 lib/README.md 相关说明。Step E 实测BenchmarkJava (OpenJDK)Truffle (OpenJDK)Truffle (GraalVM)Fib1700 ms718 ms (2.3x)21 ms (81x)Busywork781 ms19 ms (41x)12 ms (65x)Fib on Mal686 ms104 ms (6.6x)25 ms (27x)Fib 基本没变化它不用宏符合预期Busywork 与 Fib on Mal 则是巨幅提升因为两者都重度依赖宏。不过 OpenJDK 与 GraalVM 两轮差距不大令人起疑——会不会是测试跑得太快、预热不足把迭代次数从 10k 提到 100kBenchmarkJava (OpenJDK)Truffle (OpenJDK)Truffle (GraalVM)Busywork 10x7264 ms223 ms (32x)37 ms (196x)宏内联之前 Java 与 Java-Truffle 性能接近即便在 Java Mal 中也实现宏内联做公平对比Truffle Mal 仍可能领先约 6–7 倍。至于 Fib on Mal 在 OpenJDK 与 GraalVM 间差距不大这次不是预热问题——profiling 显示大量时间花在未被部分求值覆盖的代码上自托管 Mal 的环境实现会把符号转成字符串再作 map 键而非直接用符号本身Printer中对象转字符串的代码重度依赖未按部分求值设计的 JDK 类为避免编译产物爆炸必须把这些代码排除在部分求值之外。11. 结论性能从何而来代价几何Truffle 兑现了经部分求值获得高性能 JIT 代码的承诺吗作者的结论是肯定的它不是凭空 100 倍的魔法粉末但它确实以远小于一个数量级的额外工作量带来了相比朴素解释器一个数量级以上的速度提升。回到开头的三个问题更复杂的 Mal 程序也有类似加速吗没有。GraalVM JIT 不会让任意 Mal 程序都获得 Fib 基准那样的巨大收益这完全在预期之内。加速有多少归功于 Truffle/GraalVM 组合又有多少来自任何解释器都可做的优化取决于程序特征。看 Truffle Mal 在 GraalVM 上相对 OpenJDK 的提升后者没有 Truffle 部分求值加持BenchmarkTruffleMal (GraalVM relative to OpenJDK)Fib34xBusywork 10x6xFib On Mal4x极端情况下算术与函数调用密集的程序在扣除全部优化之后 Truffle/GraalVM 组合仍贡献约 30 倍现实点看常见预期是 3–6 倍的直接收益——依然可观。为了性能牺牲了多少简洁性文档给出了各实现的行数LOC对比FileLOC (Java)LOC (Truffle Step A)LOC (Truffle Step E)stepA_mal.java310757886env.java58145370printer.java53100100reader.java151166166types.java381532545core.java63315061511Total15863206 (2x)3578 (2.25x)未优化前的 Truffle 版约为 Java 版的 2 倍体量大头来自 Truffle 框架的样板代码作者认为这部分对概念复杂度几乎没有贡献core 函数行数变多则是因为使用了本文未展开的Truffle DSL它按参数类型对核心函数做特化虽然增加代码规模却以类似模式匹配的方式降低了代码复杂度。对解释器节点本身的特化只增加了约 15%120 行环境实现则膨胀到 2.5 倍复杂度确实显著上升。值得吗粗略估计基线 Java 解释器整体复杂度约增加 1.5 倍其中环境部分约 3 倍换来的是不同 Mal 程序 25 倍到 80 倍的性能提升。即使不借助 Truffle 也能在 Java 解释器上做大部分优化但会得到相似的复杂度与明显更小的性能收益。作者明确表态如果要写生产级 Mal 实现他会选 Truffle GraalVM——性能收益本身就足以证明此外 Truffle/GraalVM 还提供了与性能无关的其他好处其中最有趣的是与其他 Truffle 语言互操作的潜力。12. 彩蛋AOT 编译为原生镜像GraalVM 可以把 Java 提前编译AOT成独立可执行文件有若干前提称为native image——这对 Truffle 解释器同样适用。AOT 编译的 Mal 同时保留 Truffle 的全部 JIT 优势、摆脱 Java 运行时依赖、并跳过漫长的 JVM 启动时间非常适合脚本与命令行应用。仓库提供了脚本 impls/java-truffle/make-native.sh#!/usr/bin/env bash STEP${1:-stepE_macros} CP$(gradle -q --console plain printClasspath) native-image --macro:truffle --no-fallback --initialize-at-build-time \ -H:TruffleCheckBlackListedMethods \ -cp $CP truffle.mal.$STEP build/$STEP用法要点文档与脚本一致前提是已运行gradle build编译全部 Java 类唯一参数是 step 名例如step3_env不带参数时默认编译stepE_macros产物为build/${STEP}原生镜像构建原生镜像还需要 GraalVM 官方文档所列的额外前置条件如 native-image 工具链与系统依赖。如果你想亲自复现本文的全部实验用 run 脚本STEP环境变量切换 step默认stepE_macros即可运行任意 step 的解释器配合 tests/fib.mal、tests/busywork.mal 等基准脚本./run ../tests/fib.mal 30 10即可验证各优化阶段的性能数据源码入口与关键实现位于 impls/java-truffle/src/main/java/truffle/mal从stepB_calls.java到stepE_macros.java完整记录了每一步特化手法是学习 Truffle 解释器优化的理想教材。赞分享示例工程【免费下载链接】malmal - Make a Lisp项目地址https://gitcode.com/gh_mirrors/ma/mal点击查看免费下载相关推荐GraalVM EspressoJava on Truffle实战指南基于 Truffle 框架的 Java 虚拟机实现GraalVM EspressoJava on Truffle实战指南基于 Truffle 框架的 Java 虚拟机实现 导读 Espresso又称编译器JIT编译语言运行时高性能计算内存管理GraalVM Truffle 解释器性能优化实战从 Profiling 到去优化循环检测的完整工具链GraalVM Truffle 解释器性能优化实战从 Profiling 到去优化循环检测的完整工具链 本文基于 GraalVM 仓库的 Truffle 官方编译器JIT编译语言运行时高性能计算内存管理Espresso 深度解析GraalVM 中用 Truffle 框架实现的 Java on Truffle JVM架构原理与构建实战指南Espresso 深度解析GraalVM 中用 Truffle 框架实现的 Java on Truffle JVM架构原理与构建实战指南 Espresso编译器JIT编译语言运行时高性能计算内存管理上一篇TV Bro 电视遥控器浏览器使用指南从安装到进阶的完整流程下一篇m4s 如何一键转换成 MP4B 站缓存视频无损转换与本地备份指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
企业数字化 ERP 产品动态
相关推荐
网盘文件丢给 IDM 或 Aria2?先装这个浏览器脚本把直链取出来 网盘文件丢给 IDM 或 Aria2?先装这个浏览器脚本把直链取出来 【免费下载链接】Online-disk-direct-link-download-assistant 一个基于 JavaScript 的网盘文件下载地址获取工具。基于【网盘直链下载助手】修改 ,支持 百度网盘 / 阿里云盘 / 中国移动云盘 … · 2026/9/24 17:09:05
定时短信报警 文章目录 引言 I 需求 短信模版 用户配置 II 实现 调度任务配置 接口 接口实现 III 工具方法 短信发送 船舶权限过滤 引言
首次上报遇险报警后,若未处理,系统每小时推送一次短信提醒。通过定时任务调用/smsAlarm接口,查询开启通知的用户及未处理报警数据,按用户配置的报警… · 2026/9/24 17:08:59
开工才发现缺料,BOM、库存、采购怎么串起来?物料齐套系统选型 制造业里最让人头疼的场面之一,就是工单已经下发了、工人已经到位了,开工才发现——缺料。A料库存不够,B料采购还没到,C料在仓库里但没人知道在哪。生产计划被迫中断,交期一拖再拖。 这个问题的根源,不是库… · 2026/9/24 17:52:58
设备不会说话:一天里的七层证据 设备不会说话:一天里的七层证据标签:有限状态机、互斥租约、生产者-消费者、内存池、构建指纹、软限位、图像显示链路、统计基线、git 语义
引言:设备不会说话。它不会告诉你"我现在很忙",也不会解释"刚才那次为什… · 2026/9/24 17:52:58
毕业设计说明书和毕业论文:图纸说明先落在谁那里,改一次要回头动几处 说明书与论文这两份文本的分工,不看你把哪段写进哪一本,而看这一处内容改一次要连带动几份。图纸说明大多两条船都踩:动一处就得回头核另一处。文末两项能力可以免费用起来:一项先把章节骨架整段搭起来,一项把图表公式… · 2026/9/24 17:52:58
什么?我就想写个Markdown,还要收费?推荐一款免费好用的md笔记软件 不知道大家有没有和我一样的感受:
本来只是想安安静静写点笔记、记录技术文档、整理学习心得,结果打开常用的Markdown编辑器,弹窗提示激活、付费、解锁完整功能。
瞬间写作的好心情直接归零。
一直以来,大家最常用的 Typora 凭… · 2026/9/24 17:52:58
STM32F103的流水灯点亮版本1(寄存器地址操作) 一、STM32F103C8T6 最小系统核心板介绍
STM32F103C8T6 是 ST 意法半导体推出的Cortex‑M3 内核 32 位高性能单片机,LQFP‑48 封装,是学生嵌入式学习最常用的最小系统核心板。 (一)核心参数
(1)内核&#… · 2026/9/24 17:52:58
OpenVLA论文阅读 首先是VLA模型的架构,:1、视觉编码器Dinov2、SigLIP,通过MLP(视觉投影层,多层全连接层组成的基础神经网络)与LLM进行连接,将视觉特征直接转换为token embedding 向量输入大语言模型中。为什么要… · 2026/9/24 17:52:51
基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程 简介:这是一套面向计算机、人工智能、自动化等专业学生与教师的毕业设计级项目资源,围绕YOLOv8实现渔船作业监控系统,可用于毕设、课程设计、大作业或项目立项演示。压缩包共97个文件,约24.21MB,以70个Python源码文件为… · 2026/9/24 0:00:13
1D-CNN时间序列建模实战:从Conv1d原理到工业落地 简介:面向时间序列数据建模的一维卷积神经网络完整实现,适合深度学习入门者及需要快速验证时序模型的研究者,能够从音频、文本、传感器或股价等序列中挖掘局部特征与时间依赖。压缩包体积很小,只有3KB,内含3个Python脚… · 2026/9/24 0:00:26
柔软的L:汉语语流中被忽视的舌肌张力控制 1. 这个“L”不是字母表里的L,而是舌尖上的L最近在几个方言群和语音教学社群里,反复看到有人发一句:“也说字母L:柔软的长舌”。初看以为是英语发音课笔记,点开才发现全是方言爱好者、播音系学生、语言康复师甚至戏曲演… · 2026/9/24 0:00:44