Rubyの新機能: 埋め込みTypedDataオブジェクトが実装された(翻訳)
CRubyのオブジェクトは、内部的には強く型付けされていて、Array、Hash、Regexp、Objectなどさまざまな型があります。
また、Ruby内部にはTypedDataという型も使われており、任意のデータへのネイティブポインタを保存するのに使われます。TypedDataオブジェクトに含まれるRubyの型には、Time、Mutex、Enumeratorなどがあります。Nokogiriやpg、mysql2、liquid-cなどのC拡張でもTypedDataオブジェクトが広く用いられています。
Jean Boussierと私は、Ruby 3.3でTypedDataオブジェクトをVariable Width Allocationを用いて実装し、パフォーマンスとメモリ使用量を改善しました。
本記事では、TypedDataとは何か、埋め込みTypedDataオブジェクトによってメモリレイアウトがどう変わるか、そしてさまざまな型におけるTypedDataオブジェクトの実装の進捗について説明いたします。
🔗 TypedDataオブジェクトとは何か
RubyのTypedDataオブジェクトは、他のRubyオブジェクトと見かけ上何も変わりません。何らかのクラスのインスタンスであり、呼び出し可能なインスタンスメソッドを備えていて、インスタンス変数を保持しています。
しかし内部的には通常のRubyオブジェクトと大きく異なります。TypedDataオブジェクトは、任意のデータを指すポインタを保存するよう設計されています。データをインスタンス変数に保存する場合と比較すると、インスタンス変数の探索が発生しないため高速であり、しかもRubyオブジェクト以外のデータも保存できます。
TypedDataオブジェクトの利用法などの詳しいガイドについては、私の個人ブログにある以下の過去記事を参照してください。
参考: A Rubyist's Walk Along the C-side (Part 7): TypedData Objects - Peter Zhu
TypedDataオブジェクトは以下のような構成になっています。

TypedDataオブジェクト内のフィールドは以下の通りです。
headers- すべてのRubyオブジェクトに共通するヘッダー情報(ガベージコレクタの対象となるオブジェクトのメタデータも含む)。
typeTypedDataオブジェクト用の設定を指すポインタを保存する。設定には以下が含まれる:
-TypedDataオブジェクトの名前。
- mark関数(このTypedDataオブジェクトが参照しているRubyオブジェクトをマーキングする)。
- free関数(ガベージコレクタで回収されたときにTypedDataオブジェクトのリソースを解放する)
- フラグ(TypedDataオブジェクトでサポートされている機能で用いる)typed_flag- レガシーな理由で残されているだけなので、有用性はない。
data- メモリの任意の領域を指すポインタ。
🔗 「埋め込み」TypedDataオブジェクトとは
Jean Boussierと私は、埋め込みTypedDataオブジェクトという新しい種類のTypedDataオブジェクトを実装しました(#7440)。TypedDataオブジェクトのデータを外部にアロケーションするのではなく、そのオブジェクト自身の直後にアロケーションします。これによって以下のようなメリットを得られます。
- アロケーション回数が2回(Rubyオブジェクトで1回、システムレベルのメモリアロケーションで1回)だったのが、Rubyオブジェクトの1回だけで済むようになり、アロケーションのパフォーマンスが大きく向上する(システムからメモリをアロケーションしてから解放すると、パフォーマンスが著しく悪化する可能性がある)。
-
ポインタをたどる必要がなくなるため、
TypedDataオブジェクトのデータを読み込むときのメモリアクセスが削減され、実行時パフォーマンスも向上する。 -
メモリ領域を指す8バイトのポインタを保存する必要がなくなるので、システムでアロケーションされたメモリを管理するメモリが不要になり、メモリ使用量も削減される。
-
一部の
malloc実装は、外部メモリのフラグメンテーション(断片化)問題によってメモリ使用量が増加する。Rubyのガベージコレクタは、この問題を軽減するよう設計されている。

RUBY_TYPED_EMBEDDABLEフラグを使うと、この機能が個別のTypedDataオブジェクトで有効になります。ただし実装を若干変更する必要があるため、この機能をまだ有効にできないTypedDataオブジェクトも存在します。
また、以下の過去記事で説明しているガベージコレクションのコンパクションフェーズでオブジェクトが移動されるとデータのアドレスが変わってしまう可能性があるため、データを複数のTypedDataオブジェクトで共有しないという要件もあります。
参考: Garbage Collection in Ruby - Peter Zhu
🔗 埋め込みTypedDataオブジェクトの影響
現時点では、この機能をTime(#7440)、Enumerator(#8956)、Method(#8955)、TracePoint(#8999)を含む、よく使われる30個以上のTypedDataオブジェクトに実装しました。
埋め込みTypedDataオブジェクトではmallocでシステムからメモリをアロケーションする必要がなくなるので、アロケーションの速度が大きく向上します。たとえば、Time.nowでは80%、Object#to_enumでは68%、Object#methodではほぼ50%速度が向上しました。
🔗 まとめ
本記事では、TypedDataオブジェクトの概要、埋め込みTypedDataオブジェクトの実装方法、そして埋め込みTypedDataオブジェクトがもたらすパフォーマンス向上について見てきました。
このAPIをサードパーティのネイティブ拡張機能に公開することで、コミュニティでさらに多くの人々がこの機能によるパフォーマンス向上の恩恵を受けられることを心待ちにしています。
概要
CC BY-NC-SA 4.0 Deedに基づいて翻訳・公開いたします。
日本語タイトルは内容に即したものにしました。
参考: TypedDataについて - mirichiの日記