解锁反应堆:对象 ID

这篇文章主要介绍了 Ruby 中ractor的相关内容,包括其现状、存在的问题及改进方向等,具体如下:

  • ractor的现状与问题:在之前关于ractor的帖子中,作者认为不太可能在ractor内部运行整个应用,但ractor在将 CPU 密集型工作移出主线程和解锁一些并行算法方面很有用。然而,目前ractor还不可行,因为存在许多已知的实现错误会导致解释器崩溃,且 Ruby VM 仍有一个全局锁,ractor在执行某些操作时需要获取该锁,导致其性能往往不如等效的单线程代码。
  • 具体示例与改进

    • fstring_table为例,之前 Ruby 在访问该表时需要获取剩余的 VM 锁,导致可能崩溃。最近 John Hawthorn 用无锁的 Hash-Set 替换了它,消除了竞争点,使ractor版本的 JSON 基准测试速度提升为单线程版本的两倍。
    • #object_id方法原本用于返回对象的实际指针,后来由于 Ruby 堆的回收机制,其实现发生了变化,导致#object_id的获取成本增加,并且在ractor中成为了竞争点,因为多个ractor可能同时访问相关的哈希表。
  • 历史与设计变化

    • 直到 Ruby 2.6,#object_id的实现很简单,通过对象地址除以 2 得到object_idObjectSpace._id2ref也很简单,将object_id乘以 2 可得到对象指针。但这种实现存在对象被回收后可能返回不同对象的问题,2018 年就有提议弃用#object_id_id2ref,但_id2ref未被正式弃用。
    • Aaron Patterson 在 Ruby 2.7 中实现 GC 压缩时,由于对象可能被移动,#object_id不能再从对象地址推导,于是添加了两个内部哈希表来存储对象和 ID 的关系,这导致#object_id的获取成本更高。
  • 改进措施与思考

    • 可以优化ObjectSpace._id2ref,直到需要时才创建或更新id -> object表,以减少锁的持有时间和内存使用。
    • 考虑将object_id存储在对象的内联空间中,类似于实例变量,但存在一些限制,如形状的大部分是不可变的,创建子形状仍需要同步 VM,对于可能在ractor之间共享的对象,存储object_id时仍需要获取锁,以及不同类型的对象存储实例变量的方式不同等。
  • 结论:作者的补丁尚未完成,还需要解决如何处理“通用”对象等问题,但分享出来有助于思考问题,并且展示了使ractor更并行所需做的工作类型。类似的工作还需要在其他内部表(如符号表和各种方法表)上进行。
阅读 24
0 条评论