Architecture Resolve @ja - aki2o/plsense GitHub Wiki

plsenseのルート解決について


ルート解決とは

代入情報を集約し、変数に代入されている値を特定するルートの生成、もしくは特定する処理自体を差します。 PlSense::AddressRouter によって行われます。

代入情報とは

主には、以下のような代入式で得られる、あるデータ領域への値の格納を示す情報です。

my $hoge = $fuga;

一つの代入情報は、左辺のアドレス、右辺のアドレス/エンティティ、で形成されます。 代入情報は PlSense::SubstituteKeeper によって管理されます。 プラグイン により追加することもできます。

ルートとは

全ての代入情報の中で、変数の値の特定に至った一連の代入情報です。 つまり、代入情報の中から、使えるものがルートとして扱われるようになります。

アドレスとは

代入情報におけるデータ領域を表すためのユニークな文字列です。 アドレスは以下のように構成されます。

シンボルタイプ モジュール名 :: メソッド名 :: 変数名 エンティティアドレス

  • シンボルタイプは、 $ / @ / % / & のいずれかです。
  • モジュール名は、mainモジュールの場合、 main[ファイルパス] です。
  • 変数を表す際、メソッド名はメソッドに属す場合のみ指定されます。
  • メソッドの戻り値は、 メソッド名 で表します。
  • メソッドの引数は、 メソッド名[番号] で表します。
  • エンティティアドレスについては、 エンティティとは を参照して下さい。
  • アドレス/エンティティの取得は、 PlSense::AddressFinder で可能です。

エンティティとは

Perlのデータ構造を表すオブジェクトです。 以下のような変数ではないリテラル値を表現するために使われます。

my $hoge = { key => "", value => undef, };

エンティティ一覧

  • スカラー
  • 配列
  • ハッシュ
  • リファレンス
  • インスタンス
  • NULL

エンティティの保持する要素のアドレス

エンティティは、値や別のエンティティを保持している場合があり、 そのアドレスは、以下の文字列を . で区切って表現します。

配列の要素 A
ハッシュの値 H:キー名
リファレンスの実体 R
インスタンスのメソッド W:メソッド名

アドレス例

/tmp/test.plが以下の内容だった場合

package Hoge::Fuga;

my %bar;

sub foo {
    my $self = shift;
    # 左辺 : $Hoge::Fuga::foo::self
    # 右辺 : &Hoge::Fuga::foo[1]

    return \%bar;
    # 左辺 : &Hoge::Fuga::foo
    # 右辺 : %Hoge::Fuga::bar.R
}

package main;

my $hoge = Hoge::Fuga->foo()->{key}[0]->get_hoge("fuga");
# 左辺 : $main[/tmp/test.pl]::hoge
# 右辺 : &Hoge::Fuga::foo.R.H:key.A.W:get_hoge

不明な引数

インスタンスメソッドの引数は、そのメソッドがルート解決されるまでは、 どのメソッドに対する引数なのかわかりません。

package Hoge::Fuga;
my ($hoge, $fuga);
$hoge->do_something($fuga);
# 左辺 : do_somethingメソッドの引数だが、何のモジュールのdo_somethingメソッドか不明
# 右辺 : $Hoge::Fuga::fuga

この情報も、代入情報として PlSense::SubstituteKeeper で管理されますが、 登録するためのメソッドが異なります。

代入情報/ルートの生成/利用

  1. BuildWorkerが モジュールのビルド において、代入情報を収集し、モジュール毎にキャッシュに保存
  2. ResolveServerは、対象のキャッシュをロードし、ルート解決を実施
  3. 解決されたプロジェクト全体のルートをキャッシュに保存
  4. MainServerは、プロジェクト全体のルートをロードして利用

BuildWorker

基本的には単純に代入情報を収集するだけですが、右辺がリテラル値の代入情報は、直接ルートに登録します。

ResolveServer

前回処理時のプロジェクトで利用可能な全モジュールの代入情報/ルートを保持しています。 そこに新たにビルドされたモジュールの代入情報/ルートを追加/更新することで、プロジェクト全体のルートを算出します。 そのため、処理対象が属するプロジェクトが現在のプロジェクトと異なる場合、 プロジェクトの代入情報/ルートをロードし直す処理が必要になります。

このプロジェクト全体のロードは、インストールモジュールなどの複数プロジェクトで共有されるデータがあるため、 各モジュール毎のキャッシュを一つずつロードしルート解決し直す必要があります。 つまり、処理対象のプロジェクトが頻繁に切り替わることがあると、パフォーマンスの低下を招くかも知れません。

ルートのデータ構造

ルートは、クライアントからの要求に対して迅速に返答することを第一に考え、 現状では、単一のハッシュとしてメモリ空間に構築されます。

キー 左辺のアドレス
右辺のアドレス/エンティティ

あるアドレスのルート解決を要求された時、上記のハッシュに対して、以下の処理を行います。

  1. そのアドレスに最も合致するハッシュキーを探す
  2. 合致した部分をハッシュ値と置換する
  3. 置換後のアドレス全体がエンティティに解決されるまで、(1)から繰り返す

上記は簡潔な説明ですが、このように単一のハッシュを用いて実現しており、 登録できるルート数(ハッシュの要素数)には、実質上限があると思って下さい。 簡単なテストでは、ハッシュの要素数が多くなると、ある時点(5000000とか)から パフォーマンス低下や、使用メモリ上昇がみられました。

ちなみに、plsenseのプロジェクトで、モジュール数:約230、ルート数:約5000でした。

⚠️ **GitHub.com Fallback** ⚠️