这篇文章主要介绍了 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_id
,ObjectSpace._id2ref
也很简单,将object_id
乘以 2 可得到对象指针。但这种实现存在对象被回收后可能返回不同对象的问题,2018 年就有提议弃用#object_id
和_id2ref
,但_id2ref
未被正式弃用。 - Aaron Patterson 在 Ruby 2.7 中实现 GC 压缩时,由于对象可能被移动,
#object_id
不能再从对象地址推导,于是添加了两个内部哈希表来存储对象和 ID 的关系,这导致#object_id
的获取成本更高。
- 直到 Ruby 2.6,
改进措施与思考:
- 可以优化
ObjectSpace._id2ref
,直到需要时才创建或更新id -> object
表,以减少锁的持有时间和内存使用。 - 考虑将
object_id
存储在对象的内联空间中,类似于实例变量,但存在一些限制,如形状的大部分是不可变的,创建子形状仍需要同步 VM,对于可能在ractor
之间共享的对象,存储object_id
时仍需要获取锁,以及不同类型的对象存储实例变量的方式不同等。
- 可以优化
- 结论:作者的补丁尚未完成,还需要解决如何处理“通用”对象等问题,但分享出来有助于思考问题,并且展示了使
ractor
更并行所需做的工作类型。类似的工作还需要在其他内部表(如符号表和各种方法表)上进行。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。