恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

Nix 语言惰性特性揭秘:竟能让包管理器玩《超级马里奥兄弟 3》!

  • 首页
  • 资讯中心
  • /
  • Nix 语言惰性特性揭秘:竟能让包管理器玩《超级马里奥兄弟 3》!

相关资讯

Kimi LeetCode 3911. 移除子数组元素后第 K 小偶数 Python3实现 2026/8/15 10:57:27
Figma 汉化插件 FigmaCN 实测:3 分钟让设计界面全中文 2026/8/15 10:57:27
如何用Mapshaper把上百MB的Shapefile变成Web地图能用的轻量GeoJSON 2026/8/15 10:57:27

最新资讯

以系统论视角看待优化
AJAX服务器推送技术原理与实战应用
麒麟系统密码重置:GRUB引导与救援模式实战指南
AI Agent浏览器交互实战:Pickle策略门控与Token效率优化
OpenClaw AI Agent框架部署与配置实战:从Docker到避坑指南
抖音下载工具免费实战:5 分钟批量备份无水印视频

今日推荐

内景 空间站内部 中国空间站 太空 内仓
重新定义数据接口:3个突破性场景让通达信数据读取更智能
5大网络安全实操平台,免费练手入门,轻松掌握攻防技能

本周热门

5分钟告别提取码焦虑:baidupankey如何智能破解百度网盘资源锁
如何快速生成中国车牌图片:Python开源工具完整指南
当 LLM 遇见大文档:主流开源项目如何处理上下文超限

本月精选

如何用DamaiHelper实现演唱会门票的智能自动化抢购:完整技术解决方案指南
第4篇:59 倍性能差距的索引瓶颈定位——一次教科书级的全表扫描调优
终极歌词批量下载神器:5分钟解决离线音乐库歌词同步难题

Nix 语言惰性特性揭秘:竟能让包管理器玩《超级马里奥兄弟 3》!

发布时间:2026/8/15 10:57:27
Nix 语言惰性特性揭秘:竟能让包管理器玩《超级马里奥兄弟 3》! Nix 语言惰性特性大揭秘竟能让包管理器玩《超级马里奥兄弟 3》2026 年 8 月 5 日 * 阅读时长6 分钟Nix 语言有个令人惊讶的特性——“惰性”这对没用过惰性语言的人来说尤为新奇。正是这种惰性让 Nixpkgs 成为可能不过也带来了一定复杂性。要观察这种惰性最简单的办法就是明白只有被访问的属性才会被求值。就像下面这个例子$ nix eval --expr let pkgs { hello hi; broken throw never forced; }; in pkgs.hello hi更有意思的是属性集中可以存在“无限”递归。Nixpkgs 里就全是这种像无底洞一样的属性集看下面这些代码$ nix eval -f nixpkgs pkgs.hello --raw /nix/store/18bbdvag5v2f3d4y37pdbkzvh7s71cw4-hello-2.12.2 $ nix eval -f nixpkgs pkgs.pkgs.pkgs.hello --raw /nix/store/18bbdvag5v2f3d4y37pdbkzvh7s71cw4-hello-2.12.2 $ nix eval -f nixpkgs pkgs.python3Packages.pkgs.hello --raw /nix/store/18bbdvag5v2f3d4y37pdbkzvh7s71cw4-hello-2.12.2每次得到的存储路径都一样。pkgs 包含自身其内部的每个包集也是如此是不是很让人头疼要是惰性让递归属性集能够终止那递归根本不需要有“尽头”看这个例子$ nix eval --expr let countdown n: { value n; next countdown (n 1); }; in (countdown 0).next.next.next.value 3这个属性集深度无限。对其进行三层索引就只需要进行三层求值而无限树的其余部分不会被构建因为没人去访问它们。所以属性路径就像在一棵惰性生成的树中漫步。这让人不禁思考如果把属性路径作为“某种东西”的输入会怎样呢有人决定将这个想法付诸实践把属性路径变成 《超级马里奥兄弟 3》 中的一系列按键操作。树中的每个节点代表游戏的一帧每个子节点则是一次按键操作会生成新的一帧。游戏状态本质上就是递归的。$ nix build .#level1.rightb.rightb.rightab.rightb $ file -L result result: PNG image data, 256 x 240, 8-bit/color RGB, non-interlaced.rightb 表示右 B在《超级马里奥兄弟 3》中是“向右跑”.rightab 表示跑并跳跃。输出的就是在真实硬件上玩这款游戏时按照这些顺序按键后看到的画面。这里的前缀 .#level1 是一组预设的按键序列能让你进入第 1 - 1 关的起始界面。在路径的任何位置加上 .play就能把整个游戏过程拼接成一个录像最酷的是这些画面中的每一帧在存储中都是一个独立的衍生对象。相关代码可以在 fzakaria/nes-nix 找到。这个项目具有通用性你可以将 ROM 作为 Flake 输入用于其他游戏。Flake 会根据属性路径计算衍生对象每次按键操作都是一个独立的衍生对象并且它会将“上一次按键操作的游戏存档状态”作为输入。每个衍生对象永远不会重新模拟其“祖先”帧。同时还会生成该帧的截图用于拼接视频序列。这样做的实际效果是存储变成了模拟器的游戏存档历史# 3 个衍生对象冷启动 $ nix build .#game.start4.wait2.right # 1 个衍生对象复用前缀 $ nix build .#game.start4.wait2.left # 1 个衍生对象全部复用 $ nix build .#game.start4.wait2.right.right在一百次按键操作的中途分支出去或者在末尾追加操作成本都只相当于一次按键操作。换个角度看依赖图就是输入序列所以我们可以让 Nix 告诉我们是哪些按键操作生成了某一帧$ nix-store --query --tree $(nix eval --raw .#game.start.wait4.start.drvPath) /nix/store/32n4ni0zg01b9c9v64x67am37rdmmr9y-nes-start.drv └───/nix/store/j5vy3385pgs9dzw0y7sdrdmn7xnrxgji-nes-wait4.drv └───/nix/store/w4zz5aqj5zxqhnialabdc7p3sy80v6dc-nes-start.drv └───/nix/store/k9wfz8w5157d0xdwaw1vvhf019dvw5s0-nes-boot.drv那么 .play 到底是怎么工作的呢其实它几乎没做什么。路径上的每一帧都已经作为一次按键操作的输出存在于存储中了所以录像过程不会进行任何模拟。它只是一个包含指向这些帧的符号链接的目录供 ffmpeg 处理。$ nix build .#level1.rightb.rightb.rightab.play $ ls -l result/frames | head -4 0000.png - /nix/store/3p2fxwngh…-nes-boot 0001.png - /nix/store/4ha88l0dk…-nes-start 0002.png - /nix/store/nh4zfsq6x…-nes-wait4 0003.png - /nix/store/ghbgn28f1…-nes-start我们能把这个输入序列的游戏输入想法发挥到什么程度呢默认情况下Nix 在大约 2400 次按键操作时就会出错$ nix eval --raw .#game.right.right.right…drvPath error: stack overflow; max-call-depth exceededmax-call-depth 默认值为 10000每次按键求值大约需要四层嵌套调用。这是为了防止无限递归而不是结构上的限制。我们可以把它提高到 1000 万这样就能处理 20000 次按键操作$ ulimit -s unlimited $ nix eval --raw --option max-call-depth 10000000 .#game.$( python3 -c print(..join([right]*20000))) .drvPath /nix/store/p4nm0a4p4k9bdjqsag1jj0baah9mj6hb-nes-right.drv在笔记本上对 20000 次按键操作进行求值大约需要 14 秒。成本与按键次数呈线性关系大约每次按键 0.7 毫秒。不过下一个瓶颈是机器内核在 21845 次按键操作时就会达到极限。属性路径是 argv 中的一个元素而 Linux 对参数列表的总大小和单个参数的大小都有限制。单个参数的限制是 131072 字节MAX_ARG_STRLEN每次按键操作大约占 6 个字节right.所以 21845 次按键操作就是能作为单个参数传递给 nix eval 的最大数量。解决办法是不再将操作序列作为参数传递而是从文件中读取输入序列$ nix build --impure --expr (builtins.getFlake (toString ./.)) .packages.x86_64-linux.game.sequenceFile ./runs/world1-1.txt这样生成的衍生对象与通过属性路径生成的是完全一样的所以存储在文件中的操作序列仍然可以共享相同的存储路径。到目前为止我们只是对 Nix 表达式进行了求值。现在还需要构建它。虽然 Nix 擅长并行构建衍生对象但这里的递归是尾递归因此只能串行进行。有人对不断增加的按键操作列表的构建时间进行了基准测试正如预期的那样成本与按键次数呈线性关系。启用替代器时每次按键操作的成本约为 1.27 秒禁用替代器时约为 0.28 秒。检查衍生对象是否在缓存中的往返成本明显高于模拟帧的成本。如果想避免这个成本可以设置 preferLocalBuild 或 allowSubstitutes。我们通常认为属性路径只是一个“名称”是指向现有事物目录的一个坐标。但惰性意味着它实际上是一个“程序”是求值器执行的一系列步骤根据需要生成所需的内容。Nixpkgs 恰好利用了这个机制来描述软件但这并不意味着这个树必须是一个目录。再加上存储结果证明是一个不错的可重现状态机持久化层这让我们的“包管理器”用来玩马里奥游戏也变得合理起来。

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号