9

((1 つのスレッドで複数の質問をすることをお許しください。それらは関連していると思います。))

こんにちは、モジュールごとのプリコンパイル済みデータに関して、Erlang に存在するベスト プラクティスについて知りたいと思いました。

例:私は、以前から知られている非常に複雑な正規表現を多用するモジュールを持っています。re:compile/2 のドキュメントには次のように書かれています。re の mp() データ型はまったく指定されておらず、ターゲットに依存しないビームが必要な場合はコンパイル時に配置できないため、実行時に RegEx をコンパイルする必要があります。((注: re:compile/2 は単なる例です。メモ化する複雑な関数はすべて私の質問に適合します。))

Erlang のモジュール (できます)には、モジュールがロードされたときに 1 回-on_load(F/A)実行する必要があるメソッドを示す属性があります。そのため、このメソッドでコンパイルする正規表現を配置し、結果をという名前の新しいets テーブルに保存できます。?MODULE

ダンの回答後に更新されました。

私の質問は次のとおりです。

  • 私がetsを正しく理解している場合、そのデータは別のプロセスに保存され(プロセス辞書とは異なる方法で)、etsテーブルの値を取得するには非常にコストがかかります。(私が間違っている場合は、私が間違っていることを証明してください!) スピードアップのために、ets の内容をプロセス辞書にコピーする必要がありますか? (注意: データは決して更新されません。)
  • すべてのデータを (多くのテーブル項目ではなく) 1 つのレコードとして ets/process ディクショナリに入れることの (かなりの) 欠点はありますか?

作業例:

-module(memoization).
-export([is_ipv4/1, fillCacheLoop/0]).
-record(?MODULE, { re_ipv4 = re_ipv4() }).
-on_load(fillCache/0).

fillCacheLoop() ->
    receive
        { replace, NewData, Callback, Ref } ->
            true = ets:insert(?MODULE, [{ data, {self(), NewData} }]),
            Callback ! { on_load, Ref, ok },
            ?MODULE:fillCacheLoop();
        purge ->
            ok
    end
.
fillCache() ->
    Callback = self(),
    Ref = make_ref(),
    process_flag(trap_exit, true),
    Pid = spawn_link(fun() ->
        case catch ets:lookup(?MODULE, data) of
            [{data, {TableOwner,_} }] ->
                TableOwner ! { replace, #?MODULE{}, self(), Ref },
                receive
                    { on_load, Ref, Result } ->
                        Callback ! { on_load, Ref, Result }
                end,
                ok;
            _ ->
                ?MODULE = ets:new(?MODULE, [named_table, {read_concurrency,true}]),
                true = ets:insert_new(?MODULE, [{ data, {self(), #?MODULE{}} }]),
                Callback ! { on_load, Ref, ok },
                fillCacheLoop()
        end
    end),
    receive
        { on_load, Ref, Result } ->
            unlink(Pid),
            Result;
        { 'EXIT', Pid, Result } ->
            Result
    after 1000 ->
        error
    end
.

is_ipv4(Addr) ->
    Data = case get(?MODULE.data) of
        undefined ->
            [{data, {_,Result} }] = ets:lookup(?MODULE, data),
            put(?MODULE.data, Result),
            Result;
        SomeDatum -> SomeDatum
    end,
    re:run(Addr, Data#?MODULE.re_ipv4)
.

re_ipv4() ->
    {ok, Result} = re:compile("^0*"
            "([1-9]?\\d|1\\d\\d|2[0-4]\\d|25[0-5])\\.0*"
            "([1-9]?\\d|1\\d\\d|2[0-4]\\d|25[0-5])\\.0*"
            "([1-9]?\\d|1\\d\\d|2[0-4]\\d|25[0-5])\\.0*"
            "([1-9]?\\d|1\\d\\d|2[0-4]\\d|25[0-5])$"),
    Result
.
4

3 に答える 3

7

別のオプションがあります。正規表現のコンパイルされた形式を事前に計算し、それを直接参照できます。これを行う 1 つの方法は、次のようなこの目的のために特別に設計されたモジュールを使用することですct_expand

この値を定数として返す関数を使用してオンザフライでモジュールを生成することにより、独自のロールを作成することもできます (定数プールを利用): http://erlang.org/pipermail/erlang-questions/2011-January/ 056007.html

または、シェルで実行re:compileし、結果をコピーしてコードに貼り付けることもできます。粗雑だが効果的。実装が変更された場合、これは移植できません。

明確にするために: これらはすべて定数プールを利用して、毎回の再計算を回避します。しかしもちろん、これにより複雑さが増し、コストがかかります。

元の質問に戻ります。プロセス ディクショナリの問題は、それ自体のプロセスでしか使用できないことです。このモジュールの関数は同じプロセスによってのみ呼び出されると確信していますか? ETS テーブルでさえ、それらを作成するプロセスに関連付けられており (ただし、ETS自体はプロセスとメッセージ パッシングを使用して実装されているわけではありません)、そのプロセスが終了すると終了します。

于 2011-05-31T03:20:51.873 に答える
5

ETS はプロセスに実装されておらず、別のプロセス ヒープにデータがありませんが、すべてのプロセスの外部にある別の領域にデータがあります。これは、ETS テーブルのデータを読み書きするときに、プロセスとの間でデータをコピーする必要があることを意味します。もちろん、これがどれほどコストがかかるかは、コピーされるデータの量によって異なります。これが、データがコピーされる前により複雑な選択ルールを許可するets:match_objectやのような関数がある理由の 1 つです。ets:select

データを ETS テーブルに保持する利点の 1 つは、テーブルを所有するプロセスだけでなく、すべてのプロセスがデータにアクセスできることです。これにより、データをサーバーに保持するよりも効率的になります。また、データに対して実行する操作の種類によっても異なります。ETS は単なるデータ ストアであり、限られた原子性しか提供しません。あなたの場合、それはおそらく問題ありません。

アクセス速度が大幅に向上するため、コンパイルされた正規表現ごとに 1 つずつ、別々のレコードにデータを保存する必要があります。その後、目的の re を直接取得できます。それ以外の場合は、それらをすべて取得してから、目的の re を再度検索します。そのようなことは、それらを ETS に入れるという点を無効にします。

on_load関数でETS テーブルを作成するなどのことはできますが、ETS テーブルの場合はお勧めできません。これは、ETS がプロセスによって所有され、プロセスが終了すると削除されるためです。on_load関数がどのプロセスで呼び出されるかを実際に知ることはできません。また、モジュールは完了するまでロードされたと見なされないため、長時間かかる可能性があることも避ける必要があります。

re を直接コードにコンパイルした結果を静的に挿入するために解析変換を生成することは、特に re が実際にそれほど静的に定義されている場合は、優れたアイデアです。モジュールを動的に生成し、コンパイルして、システムにロードするという考え方も同様です。繰り返しますが、データが静的である場合、コンパイル時にこのモジュールを生成できます。

于 2011-05-31T23:02:45.620 に答える
3

mochiglobal は、定数を格納する新しいモジュールをコンパイルすることでこれを実装します。ここでの利点は、メモリがプロセス間で共有されることです。ここでは、メモリはコピーされ、プロセス ディクショナリでは、その 1 つのプロセスに対してローカルになります。

https://github.com/mochi/mochiweb/blob/master/src/mochiglobal.erl

于 2011-05-31T01:23:16.310 に答える