Ruby C 拡張機能での引数解析のマスター:ステップバイステップ ガイド
Ruby は、まず人間のために、次に機械のために作られた素晴らしい言語です。読み書きも簡単です。何かを記述する方法はたくさんあり、多くの場合、自分で選択したメソッドの名前を入力することで、その標準ライブラリを推測できます。
このため、Ruby の引数は非常に柔軟であり、API を非常に明確に表現できます。しかし、これには欠点があります。Cextension 開発者にとって Ruby は解析が非常に難しいのです。
この記事では、C で書かれた複雑な Ruby API をセットアップする 2 つの方法を説明します。
rb_define_method付きrb_scan_argsで解析します- Ruby インターフェースを使用する
始めましょう!
C と Ruby:概要
前述したように、C 拡張機能開発者にとって Ruby は解析が困難です。
例:
Ruby が書かれている言語である C の美しさは、関数パラメータを含めたその単純さから生まれています。
<data-type> <variable-identifier>...可変長引数の場合。
これらは、理解するのがそれほど難しくないコードベースを維持するのに役立ちます。
C 関数を定義する最も複雑な方法は次のとおりです。
Ruby コードベースの C 拡張機能をコーディングすると、どこから複雑さが始まるのかがわかり始めます。でも心配しないでください。Ruby MRI 開発者がサポートしてくれます。
Ruby C 拡張機能での簡単なメソッド定義
まずは、いつか使用する必要があるメソッド rb_define_method から始めます。 .
このリポジトリのコード例に従うこともできます。
ここは rb_define_method です の署名:
Ruby の extension.rdoc によると:
argc は引数の数です。 argc が -1 の場合、関数は 3 つの引数 (argc、argv、self) を受け取ります。 argc が -2 の場合、関数は self と args の 2 つの引数を受け取ります。ここで、args はメソッド引数の Ruby 配列です。
簡単に言うと:
したがって、API が固定パラメータ長のメソッドのみで構成されている場合、または 1 つの可変引数パラメータ (def foo(*bar) のみ) で構成されている場合 )、これ以上読む必要はありません - これで完了です。 API を呼び出すためのより充実した方法が必要な場合は、私のゲストになってください。
ところで、より複雑な例が必要な場合は、rb_define_method を参照してください。 、 Peter Zhu のメソッドの定義に関する記事を読むことをお勧めします。
Ruby C API 内部の使用
それでは、ユースケース、複雑な引数の解析に戻りましょう。幸いなことに、いくつかのツールが役に立ちます。
しかしその前に、rb_define_method のみを使用する場合の制限を見てみましょう。 .
rb_define_method の欠点
ブロック引数についての言及なし
1 つの制限は、rb_define_method であることです。 ブロック引数については決して言及しません。いずれにしても、ブロックを渡しても Ruby にとってはあまり重要ではないため、これらは考慮されません。 rb_block_given_p を使用すると、ブロックが確実に渡されるようにすることができます。 または rb_need_block 。このトピックについては、Peter Zhu の記事で詳しく説明されています。
引数は変化する可能性があります
もう 1 つの重要な制限は、引数は変化する可能性がありますが、メソッド呼び出し自体にはそれほど制約がないことです。したがって、API を def foo(bar, *baz) のようにしたい場合は、 、引数を解析する必要があります。その道を進むのに役立つ方法はほとんどありません。 rb_check_arity rb_define_method の -1 バージョンと一緒に使用できるものです .
関数のシグネチャは次のとおりです。
キーワード引数
最後の制限として、キーワード引数の使用について説明します。そして、それがこの記事の中核となるため、最良のものを最後に残しておきました。
キーワード引数を正しく解析するには、その前にキーワード引数を取得する必要があります。幸いなことに、Ruby C API には、このためのメソッド rb_scan_args が付属しています。 .
rb_scan_args は次のとおりです。 署名:
rb_scan_args を渡します argc と argv rb_define_method によって与えられます — 引数をどのように解析するかを示す文字列 (fmt) ) とそれらの引数の受信者。これで、Ruby の引数の複雑さはすべて 1 行で解析されました。そうですね、もうすぐです。
fmt の正式な表現については、extension.rdoc を参照してください。 と書く必要がありますが、以下の例で部分的に説明します。
関数の作成と解析:例
この記事の残りの部分では、次の関数を書きたいと考えてみましょう。
rb_scan_args を使用してこれを解析します 次のようになります:
"1*:" 意味不明の意味:
1:1 つの必須の位置引数*:位置引数は必要ありません::末尾のキーワード引数
キーワード引数を解析する
これでメソッドを制限しましたが、残念ながらまだ完了していません。現在の API は def voronoi_diagram(envelope, *polygons, **kwargs) です。 .
最後に、rb_get_kwargs を使用してこれらのキーワード引数を解析する必要がありました。 .
必須およびオプションの引数をいくつか選択する必要があります。それが完了したら、table を使用します。 Ruby に引数の名前を伝え、その結果を配列 (values) に格納します。 ).
ほら、ありますよ! C のみを使用して解析される複雑な Ruby メソッド。ただし、これが複雑すぎる場合は、別のオプションがあります。
Ruby インターフェイスの使用
この問題を処理するもう 1 つの方法は、実際に Ruby の構文を直接使用し、Ruby の段階で解析を行うことです。
これにより、rb_define_method の 3 番目の形式を直接使用できるようになります。 、C メソッドの場合は次のようになります。
これで、Ruby 実装の一部のメソッドに実際に使用されるエレガントなソリューション (Primitive を使用) の問題を完全に回避できます。 クラス)。
MRI で使用されるクラスは非常に複雑で、C 自体を生成しますが、そこからインスピレーションを受けることができます。
c_voronoi_diagram が表示されないように、オブジェクトを作成してメソッドを接続しましょう。 API のユーザー向け:
RGeo のコードベースでこのクラスの実際の使用例を確認してください。
引数の解析:どの方法を使用する必要がありますか?
このリポジトリでは、C で書かれた複雑な Ruby API をセットアップする 2 つの方法を紹介します。要約すると、次のとおりです。
rb_define_methodを使用rb_scan_argsで解析します- Ruby インターフェースを使用する
両方のソリューションをパフォーマンスの観点から比較すると、ほぼ同等です (私の M1 では、Ruby の解析は平均して 1.02 倍高速です)。それは大きな違いではありません。
RGeo ライブラリでは、最初に設計で選択したのは、可変長引数のみを使用する API を使用することでした。キーワードもブロックもありません。これは非常に制限的になる可能性があるため、現在は Primitive を使用しています。 より複雑な引数を許可する方法です。
Ruby インターフェイスを使用する利点
私のアドバイスは、次のような複数の理由から Ruby インターフェースを使用することです。
- コードサイズが小さくなります
- 変更が容易になる
全体として、これにより、コードベースを読み取るエクスペリエンスがより簡単になります。
Ruby ユーザーに、彼らが使用する gem のソース コードを読んでもらうことが私にとって重要です。 Ruby は読みやすいので 、宝石も同様であるべきです。
rb_define_method を使用する利点
ただし、C バージョンでは、非常に便利な内部メソッドをいくつか体験できます。たとえば、rb_check_arity です。 今でも本当に役に立ちます。ブロックを処理するメソッドも優れており、ブロックに Ruby ファサードを使用する必要がない場合もあります。
重要なのは、ユースケースに最適な選択肢を見つけることです。
まとめ
この投稿では、Ruby C 拡張機能の引数を解析するための 2 つの方法 (rb_define_method を使用) を検討しました。 (その制限についても簡単に説明します)、rb_scan_args で解析します。 Ruby インターフェースを使用します。
C 拡張機能について詳しく知りたい場合は、以下をお勧めします。
- Ruby C 拡張機能を最初から構築する
- Rubyist による C サイドの歩み
- Mac で Ruby C 拡張機能を使用する
最後に、RGeo をチェックしてください。これは、活発な開発で広く使用されている C 拡張コードベースです。私の例のほとんどはここから来ています。
コーディングを楽しんでください!
追記Ruby Magic の投稿を報道後すぐに読みたい場合は、Ruby Magic ニュースレターを購読して、投稿を 1 つも見逃さないようにしてください。
ユリス・ブオノモ
私たちのゲスト著者である Ulysse は、元業界の Ruby 開発者であり、ほとんどの時間を世界中を旅することに費やしています。彼は余暇を RGeo と Ruby に費やしており、Ruby の内部をいじるのが大好きです。
Ulysse Buonomo によるすべての記事
-
Ruby および Rails 用の JIT コンパイラー:YJIT、MJIT、および TenderJIT でパフォーマンスを向上
プログラムは、実行前コンパイルとは異なる方法を使用して実行時にコンパイルされます。このプロセスは、ジャストインタイム コンパイルまたは動的変換として知られています。 この投稿では、利用可能ないくつかのオプション (YJIT、MJIT、TenderJIT) とそれらのインストール方法を説明する前に、JIT コンパイルが Ruby on Rails アプリに適した選択肢である理由を説明します。 まず最初に、JIT コンパイルはどのように機能するのでしょうか? JIT コンパイラの仕組み ジャストインタイム コンパイルは、プログラムの実行中にコンパイルが必要なコンピューター コードを実行する方
-
TCmallocを使用したRubyのメモリ割り当てのプロファイリング
Rubyではメモリ割り当てはどのように機能しますか? Rubyはページと呼ばれるチャンクでメモリを取得し、新しいオブジェクトはここに保存されます。 次に… これらのページがいっぱいになると、より多くのメモリが必要になります。 Rubyは、mallocを使用してオペレーティングシステムからより多くのメモリを要求します 機能。 このmalloc 関数はオペレーティングシステム自体の一部ですが、使用できる代替の実装があります。 それらの実装の1つは、Googleのtcmallocです。 TCmallocはGoogleパフォーマンスツールスイートの一部です。 これらのツールを使用し