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オブジェクトに共通するヘッダー情報(ガベージコレクタの対象となるオブジェクトのメタデータも含む)。
type
TypedData
オブジェクト用の設定を指すポインタを保存する。設定には以下が含まれる:
-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の日記