デバッガービジュアライザー
これらは通常、デバッガーが情報を表示する前の最後のステップですが、結果は IDE のデバッガー API などのデバッグアダプターを通じてパイプされる場合があります。
「Visualizer」という用語は少し誤称です。本当の目的は単に出力を見栄えよくすることではなく、 ユーザーが操作するためのインターフェイスを、可能な限り有用なものとして提供することです。多くの場合、 これは元の型を Rust の表現に可能な限り近い形で再構築することを意味しますが、 常にそうとは限りません。
ビジュアライザーインターフェイスでは、「合成子要素」を生成できます。これは、デバッグ情報には存在しないものの、
言語や型自体に関する不変条件から導出できるフィールドです。簡単な例としては、
Vec<T> の *mut u8 ヒープポインター、長さ、容量だけでなく、Vec<T> の要素を操作できるようにすることが挙げられます。
rust-lldb、rust-gdb、および rust-windbg.cmd
これらのサポートスクリプトは Rust ツールチェーンとともに配布されます。これらは適切なデバッガーと ツールチェーンのビジュアライザースクリプトを見つけ、デバッグ対象が起動またはアタッチされる前に ビジュアライザースクリプトを読み込むための適切な引数を指定してデバッガーを起動します。
#![debugger_visualizer]
この属性により、Rust ライブラリの作者は、自分の型のためのプリティプリンターを ライブラリ自体に含めることができます。これらのプリティプリンターは一般的な ビジュアライザーと同じ形式ですが、コンパイル済みバイナリに直接埋め込まれます。これらのスクリプトは デバッガーによって自動的に読み込まれ、ユーザーにシームレスな体験を提供します。この属性は現在、 GDB および natvis スクリプトで機能します。
GDB の Python スクリプトは、バイナリの .debug_gdb_scripts セクションに埋め込まれます。詳細情報は
こちらで確認できます。Rustc はこれを rustc_codegen_llvm/src/debuginfo/gdb.rs で実現しています。
Natvis ファイルは /NATVIS リンカーオプションを使用して PDB デバッグ情報に埋め込むことができ、
型がどのビジュアライザーを使用するかを解決する際に最も高い優先度を持ちます。属性で指定された
ファイルは CrateInfo::natvis_debugger_visualizers に収集され、その後
rustc_codegen_ssa/src/back/linker.rs でリンカー引数として追加されます。
LLDB は現在サポートされていませんが、将来的にサポートを可能にする可能性のある方法がいくつかあります。
公式には、想定されている方法はフォーマッターバイトコードを介するものです。これは
GDB と同等の体験を提供しつつ、Python スクリプト全体を埋め込むことに伴う安全性上の懸念を避けるために作成されました。
オペコードは制限されていますが、Python のビジュアライザースクリプトとおおむね同じ方法で SBValue と SBType を扱えます。
これを実装するには、何らかの DSL/ミニコンパイラーを書く必要があります。
あるいは、GDB の戦略を完全にコピーすることも可能かもしれません。つまり、バイナリ内に専用セクションを作成し、 そこに Python スクリプトを埋め込む方法です。LLDB はそれを自動的には読み込みませんが、Python API では デバッグ情報の生セクションにアクセスできます。これにより、専用セクションから Python スクリプトを抽出し、Rust のビジュアライザースクリプトの起動時にそれを読み込める可能性があります。
パフォーマンス
ビジュアライザー自体に取り組む前に、これらがパフォーマンスに敏感なシステムの一部であることに注意することが重要です。 少しくだけた言い方になることをお許しください。私は、デバッグにかなりの時間を費やさなければならないと イライラします。デバッガーを待たされるとなると、腹が立ちます。
これらのビジュアライザーで費やされる 1 ミリ秒は、ユーザーが出力を見るまでに 1 ミリ秒余分にかかるということです。 これは、多数または大きなコンテナー型を含む大きなスタックフレームでは特に苦痛になり得ます。 VSCode などのデバッガー GUI はスタックフレーム全体を一度に要求するため、そのフレーム内の 任意の変数を操作できるようになるまでに、数十秒、場合によっては数分の遅延が発生することがあります。
Python コードを最適化するという考えに難色を示す傾向がありますが、実際には大きな影響を与えることがあります。 覚えておいてください。コードを高速に保つのを助けてくれるコンパイラーは存在しません。単純な変換でさえ 自動では行われません。Python を最適化する必要はないと勧める人々の雑音の中から Python のパフォーマンスに関するヒントを見つけるのは難しい場合があるため、これらのスクリプトに関連して 覚えておくべきことをいくつか示します。
- すべてが割り当てを行います。
intでさえそうです - 可能な場合はタプルを使用してください。
listは実質的にVec<Box<[Any]>>ですが、タプルはBox<[Any]>に相当します。タプルは間接参照の層が 1 つ少なく、余分な容量を持たず、 伸縮できないため、多くの場合に有利です。追加の利点として、Python はサイズ 20 までの すべてのタプルの基盤となる割り当てをキャッシュし、再利用します。 - 正規表現は遅いため、単純な文字列操作で済む場合は避けるべきです
- 文字列はイミュータブルであるため、多くの文字列操作は暗黙的に内容をコピーします。
- 文字列の大きなリストを連結する場合、通常は
"".join(iterable_of_strings)が最速の 方法です。 - f-string は通常、文字列を括弧で囲むなどの小さく単純な文字列変換を行う最速の方法です。
- 関数呼び出しという行為はやや遅いです(たとえ関数が完全に空であっても)。その コード区間が非常にホットである場合は、関数を手動でインライン化することを検討してください。
- ローカル変数アクセスは、グローバル変数や組み込み関数へのアクセスよりも大幅に高速です
.演算子によるメンバー/メソッドアクセスも遅いため、深くネストした値を ローカル変数に再代入してこのコストを避けることを検討してください(例:h = a.b.c.d.e.f.g.h)。- 継承されたメソッドやフィールドへのアクセスは、基底クラスのメソッドやフィールドよりも約 2 倍遅いです。 可能な限り継承を避けてください。
- 可能な限り
__slots__を使用してください。__slots__は クラスのフィールドが変化しないことを Python に示す方法であり、フィールドアクセスを 目に見えて高速化します。これには、事前にフィールドに名前を付け、__init__で初期化する必要がありますが、 得られる利点に対しては小さな代償です。 - match 文や if..elif..else はいかなる形でも最適化されません。条件は順番に、 1 つずつチェックされます。可能であれば、辞書ディスパッチや値のテーブルなどの代替手段を使用してください
- 可能な場合は遅延計算してください
- リスト内包表記は通常ループより高速で、ジェネレーター内包表記はリスト内包表記より少し遅いですが、
使用するメモリは少なくなります。内包表記は Rust の
iter.map()に相当すると考えることができます。 リスト内包表記は実質的に最後にcollect::<Vec<_>>を呼び出しますが、 ジェネレーター内包表記はそうしません。