深入 Ruby 37:Ruby 惯用法与必要优化
系列导航 导读 · 上一篇:36:YJIT 与性能实验 · 下一篇:38:输入与安全边界 · 完整源码包 优化从业务需要多少结果开始 对一组任务先筛选、再转换、最后只取前二十条,写成枚举链很清楚。但普通 Array 上的 select 和 map 会先完成各自处理,再把结果交给下一步。如果输入很大、最终只需要很少结果,中间工作可能超过业务需要。 这并不意味着所有枚举链都应该改成循环。要先固定契约:输入顺序是否保留、需要全部结果还是前缀、后续非法项是否必须验证、块中是否有副作用。不同契约可能使看似等价的优化改变可见行为。短路带来的速度,不能以遗漏必要校验为代价。 本章比较普通枚举链、惰性枚举链和显式循环。三种写法处理同一冻结输入,返回完全相同的前二十个偶数平方,记录实际访问次数、重复耗时、分配量与 RSS。实验用确定的访问计数解释耗时差异,不以代码行数判断效率。 把三种实现放在同一合同下 从仓库根目录运行: 1ruby examples/ruby/labs/37/run.rb 输入是一到十万的整数。普通枚举链写成: 123input.select { |number| n...
深入 Ruby 36:YJIT 与性能实验
系列导航 导读 · 上一篇:35:Prism、YARV 与字节码 · 下一篇:37:Ruby 惯用法与必要优化 · 完整源码包 先定义值得测量的收益 启用 YJIT 之后,某段 Ruby 代码可能更快,也可能因为启动、预热或额外内存成本而不适合当前任务。对短命命令行程序,用户等待的是从进程启动到结果出现的总时间;对常驻服务,稳定负载下的吞吐与尾延迟更重要。两种场景不能用同一段已经预热的循环统一回答。 Taskbook 目前处理小型本地任务集合,没有因为 Ruby 支持 JIT 就自动需要开启它。本章用确定输入建立可复现性能实验,结果只用于解释测量方法。只有业务瓶颈确认落在受益路径上,才有理由调整运行参数。 YJIT 3.4 文档说明构建要求、平台限制与内存代价。安装了 Ruby 不一定意味着二进制包含可用 YJIT;命令行传入选项也不应作为启用成功的唯一证据。实验在每个子进程内检查 RubyVM::YJIT.enabled?。 相同输入与相同结果先于计时 从仓库根目录运行: 1ruby examples/ruby/labs/36/run.rb 实验使用一到两万的整数数组,每个元素...
深入 Ruby 35:Prism、YARV 与字节码
系列导航 导读 · 上一篇:34:对象分配与 GC · 下一篇:36:YJIT 与性能实验 · 完整源码包 解析器、字节码与执行对象各自负责什么 Ruby 源码先要被理解为语法结构,再变成当前实现可以执行的形式,执行时才读取变量、调用方法并产生结果。Prism、YARV 与 Ruby 对象处于这条路径的不同位置。Ruby 3.4 把 Prism 设为默认解析器,不意味着删除了字节码虚拟机,也不意味着解析源码已经运行了程序。 这种区分直接影响调试。语法错误发生在解析阶段;方法不存在通常要到执行阶段才能确定;运行结果错误还可能来自对象状态或方法重新定义。把所有问题称为“编译器错误”,会使排查入口变得模糊。 本章选择一个很小的表达式,同时观察 Prism AST、CRuby InstructionSequence 以及求值结果。源码阅读固定到 Ruby 3.4.11 的完整提交 592f1ffdb36153e8be83603ade3c2e9ab6138a77。不同补丁版本的节点、指令文本或优化可能变化,不能拿未来 master 的实现解释当前实验。 下图区分源码处理与运行时执行。第 3...
深入 Ruby 34:对象分配与 GC
系列导航 导读 · 上一篇:33:进程、超时与取消 · 下一篇:35:Prism、YARV 与字节码 · 完整源码包 内存增长需要先确定测量对象 一个 Ruby 进程的常驻内存上升,可能来自仍被引用的业务对象、频繁分配形成的堆容量、本地库缓冲区、线程栈或 JIT 代码。只看操作系统的 RSS 曲线,无法确定哪类资源增长,更无法直接证明垃圾回收失效。 Taskbook 每次导入都会产生任务 Hash、字符串和标签数组。若处理后不再引用它们,生命周期应当结束;若把所有历史结果保存在缓存里,对象继续存活是程序引用关系的结果。GC 不会猜测“业务已经不需要”,也不会主动删除仍可达的缓存项。 本章用可控缓存建立基线,分别记录累计分配、存活槽位、显式保留对象的内存估计和进程 RSS。目的在于建立诊断顺序,避免尚未找到保留路径就调整 GC 参数。参数调优无法消除一条仍然存在的强引用。 四种数字回答四个问题 GC.stat提供当前实现的统计量。其中累计分配是从进程开始到现在发生过多少对象分配,数字通常持续增加;存活槽位描述某个时刻仍占用的 Ruby 对象槽位。这两者不能相减后直接得到进程内存字节...
深入 Ruby 33:进程、超时与取消
系列导航 导读 · 上一篇:32:Ractor 与对象隔离 · 下一篇:34:对象分配与 GC · 完整源码包 截止时间与任务终止是两个状态 一次等待超过期限,调用方可以决定不再等;被调用任务是否停止,还需要单独确认。网络请求超时后服务端可能继续提交,子进程收到信号后也可能执行清理代码。把“抛出了超时异常”直接当成“所有工作已经取消”,会使重试产生重复副作用。 Taskbook 的导入通常是本地计算与文件操作。若将来调用外部转换器,应把输入、结果、截止时间和进程生命周期放到同一个调用边界管理。调用者需要知道返回的是成功结果、业务失败、超时后已回收,还是仍有任务存活。仅返回 nil 会丢失这些区别。 本章实验固定 Ruby 3.4.11,先观察 Timeout 的 ensure,再启动受控 Ruby 子进程,执行就绪握手、等待超时、TERM 和 wait 回收。所有数据均来自临时管道,没有外部服务依赖。 优先在等待操作上设置期限 从仓库根目录运行: 1ruby examples/ruby/labs/33/run.rb 一次 TCP 请求通常存在连接建立、写请求、读响应等不同阶段。库...
深入 Ruby 32:Ractor 与对象隔离
系列导航 导读 · 上一篇:31:Fiber 与 scheduler · 下一篇:33:进程、超时与取消 · 完整源码包 Ractor 先改变对象所有权 Ractor 为并行执行设置对象访问边界。把原来共享可变数组的 Thread 代码直接换成 Ractor,往往先遇到对象不可访问,而非立刻得到加速。这些限制决定了数据怎样进入任务、计算结果怎样离开,以及发送方何时还能使用原对象。 本章固定 CRuby 3.4.11。Ractor 在此版本仍会给出实验性提示,不能把后续版本接口或第三方库的兼容结论直接套用。实验覆盖普通复制、可共享对象和 move,保留正常结果与预期错误;没有把创建成功当成整个生态已经兼容的证据。 官方 API与设计说明给出共享和隔离规则。实践中应先分类对象,再选择传递方式,最后测量并行收益。忽略分类会使对象在消息边界被隐式复制,或让后续访问直接失败。 外层冻结没有冻结对象图 从仓库根目录执行: 1ruby examples/ruby/labs/32/run.rb 第一个反例只冻结数组: 12shallow = ['mutable'.dup].f...
深入 Ruby 31:Fiber 与 scheduler
Fiber 把暂停位置变成显式控制点 Fiber 保存一段执行过程的局部状态,允许在暂停后从原位置继续。它适合表达分段生产数据、请求等待和协作任务。一个线程内可以有多个 Fiber,但任意时刻仍只有其中一个执行;没有让出控制权的长计算,会阻止同线程其他 Fiber 前进。 这与上一章 Thread 的调度边界不同。Thread 的切换不由业务每一处显式决定;普通 Fiber 的 resume 与 yield 则把控制转移暴露为 API。理解这种最小形式,再观察 scheduler 怎样接管等待,才能分清“暂停当前任务”和“让系统自动并行计算”。 官方 Fiber 文档区分阻塞与非阻塞 Fiber;scheduler 协议描述 Ruby 在受支持操作上调用的钩子。协议定义回调形状,并不自带一个适用于所有业务的事件循环。 从两次恢复建立执行模型 从仓库根目录运行: 1ruby examples/ruby/labs/31/run.rb 实验先创建一个普通 Fiber: 1234567task = Fiber.new do |value| next_value = Fiber.yiel...
深入 Ruby 30:Thread、Mutex、Queue 与 GVL
系列导航 导读 · 上一篇:29:配置、日志、信号与有序关闭 · 下一篇:31:Fiber 与 scheduler · 完整源码包 共享状态的风险从一次读改写开始 两个线程都给计数器加一,业务期望总数增加二。只要一次加一被拆成读取旧值、计算新值、写回结果,另一个线程就可能在中间改变状态。CRuby 的全局虚拟机锁(Global VM Lock,GVL)约束同一 Ractor 中 Ruby 线程的执行,并不把任意业务代码块变成事务。判断线程安全,要找出需要同时成立的约束,以及哪些路径能够改变这些约束。 Taskbook 若增加并发导入,最容易共享的是任务数组、已见标识集合和计数器。线程之间能访问同一个对象,不代表一次“查重后追加”不可分割。即使数组追加本身没有破坏内部结构,两个线程仍可能同时确认标识不存在,随后各自追加一条记录。对象内部有效和业务数据正确,是两个不同的验收条件。 Thread 文档规定了创建、等待和异常传播行为;Mutex 文档提供显式同步手段。实验先构造确定的冲突,再验证修复。这样测试不依赖操作系统恰好在几纳秒内切换线程。 用队列固定错误交错 从仓库根目录执行: ...
深入 Ruby 29:配置、日志、信号与有序关闭
系列导航 导读 · 上一篇:28:Net::HTTP、Rack 与本地请求实验 · 下一篇:30:Thread、Mutex、Queue 与 GVL · 完整源码包 收到 TERM 时,正在处理的任务怎么办 进程已经接收任务 one,输入管道里还排着任务 two。此时收到 TERM,可以立即中断,也可以完成 one 后停止接收 two;两种策略都可能合理,但必须先定义,才能写出有意义的关闭测试。 本篇采用“当前任务完成,后续任务不再接收”的策略。父进程等待子进程报告 ready,提交两项任务,收到 accepted one 后才发送 TERM。随后要求事件严格为 finished、stop_accepting、closed,并验证子进程成功退出。 前置是资源管理、CLI 和 HTTP 边界。实验位于 examples/ruby/labs/29/,在 CRuby 3.4.11 的本地 POSIX 环境执行。它没有验证 Windows 信号行为、SIGKILL 清理或真正持久化队列的恢复。 配置来源先合并,再校验 Taskbook 的教学配置采用文件、环境、命令行逐级覆盖,后者优先: ...
深入 Ruby 28:Net::HTTP、Rack 与本地请求实验
系列导航 导读 · 上一篇:27:CLI、OptionParser 与退出码 · 下一篇:29:配置、日志、信号与有序关闭 · 完整源码包 一次 HTTP 调用经过哪些接口 客户端调用 Net::HTTP,服务器接收 socket 字节,Web server 解析协议并构造请求环境,Rack 应用根据环境返回响应。框架可以在 Rack 之上增加路由、控制器和会话,但 Rack 自身不等于一个已经监听端口的服务器。 把这些层混在一起,会产生两种错误验收:只调用 Ruby 对象就宣称网络通信成功,或仅验证 TCP 连接成功就宣称业务响应正确。本篇分别建立真实回环 HTTP 客户端实验和 Rack 调用合同实验,保留各自能够证明的范围。 前置是异常、依赖、测试和数据边界。版本为 CRuby 3.4.11、net-http 0.6.0、Rack 3.2.1。实现位于 lib/taskbook/http.rb,实验入口为 labs/28/run.rb,只访问 127.0.0.1。 客户端先限制目标,再建立连接 教学客户端接收已经解析的 URI,并明确限制为回环 HTTP: 12345678...

