Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

Rubyの新機能: 埋め込みTypedDataオブジェクトが実装された(翻訳)

概要

CC BY-NC-SA 4.0 Deedに基づいて翻訳・公開いたします。

CC BY-NC-SA 4.0 Deed | 表示 - 非営利 - 継承 4.0 国際 | Creative Commons

日本語タイトルは内容に即したものにしました。

参考: TypedDataについて - mirichiの日記

Rubyの新機能: 埋め込みTypedDataオブジェクトが実装された(翻訳)

CRubyのオブジェクトは、内部的には強く型付けされていて、ArrayHashRegexpObjectなどさまざまな型があります。
また、Ruby内部にはTypedDataという型も使われており、任意のデータへのネイティブポインタを保存するのに使われます。TypedDataオブジェクトに含まれるRubyの型には、TimeMutexEnumeratorなどがあります。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オブジェクトのデータを外部にアロケーションするのではなく、そのオブジェクト自身の直後にアロケーションします。これによって以下のようなメリットを得られます。

  1. アロケーション回数が2回(Rubyオブジェクトで1回、システムレベルのメモリアロケーションで1回)だったのが、Rubyオブジェクトの1回だけで済むようになり、アロケーションのパフォーマンスが大きく向上する(システムからメモリをアロケーションしてから解放すると、パフォーマンスが著しく悪化する可能性がある)。

  2. ポインタをたどる必要がなくなるため、TypedDataオブジェクトのデータを読み込むときのメモリアクセスが削減され、実行時パフォーマンスも向上する。

  3. メモリ領域を指す8バイトのポインタを保存する必要がなくなるので、システムでアロケーションされたメモリを管理するメモリが不要になり、メモリ使用量も削減される。

  4. 一部の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をサードパーティのネイティブ拡張機能に公開することで、コミュニティでさらに多くの人々がこの機能によるパフォーマンス向上の恩恵を受けられることを心待ちにしています。

関連記事

Rubyのメモリ管理方法2: Ruby 3.1の文字列の可変幅アロケーション(翻訳)

Ruby: mallocでマルチスレッドプログラムのメモリが倍増する理由(翻訳)


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。