この記事では、Burton Bloom確率的データ構造の実装例により、ErlangとRustの統合に焦点を当てています。これにより、要素が必要な精度でセットに属することを検証できます。
言語選択
計算タスクに基づくパフォーマンステストにより、Erlangがどのリーグでプレーするかが明確になります。
Erlangには超高速の算術演算がないため、複雑な計算上の問題を解決するのは奇妙に思えます。 ただし、キューシステムの開発および運用中に発生する問題には適しています。 Erlangは、優れたスケジューラーとガベージコレクター、高速ネットワークおよびバイナリデータ処理と相まって、競争の激しい分散環境を処理します。 このように、私にとっては、分散サーバーアプリケーションのアーキテクチャにおけるシステム接着剤の役割をErlangに割り当てました。
実際のシステムでは、ローカルコンピューティングの問題が発生し、システムが遅くなり、全体的なUXが低下します。 コードの1%が遅くなり、システムの残りの99%に悪影響を及ぼすことがよくあります。 Erlangでこの問題を解決するには、バージョンR13B03以降、Native Implemented Functions(NIF)のメカニズムがあります。
2.7項のErlang神話リストで、開発者はNIFインターフェースの使用が最後の手段であるべきであると警告します。NIFの実装の欠陥に起因するVMクラッシュの可能性があるため、NIFの使用は危険であり、常に速度の向上を保証するものではありません。
Cの公式NIF実装例が利用可能です。CおよびC ++コードは、たとえば構造体または配列のメモリ境界を超えたり、割り当てられたリソースを解放する操作をスキップしたりするなど、簡単に安全ではありません。 私の意見では、この問題はコンテキスト切り替え要因によって悪化します。主にErlangコードを開発するプログラマーが低レベルCに切り替えると、特に期限内に上記の問題が発生する可能性が高くなります。
したがって、C / C ++と同じくらい速く、安全で簡単に保守できるソリューションを取得したいと思います。 最も計算効率の高い言語を見てみましょう。
言語要件に関しては、注目に値します:
- 安全性 ソリューションはどのような状況でもErlang VMを壊してはいけません
- パフォーマンス。 C ++とパフォーマンスを同等にする
- NIFモードで使用する機能
- 開発スピード。 言語の便利なエコシステムを提供するには、優れた標準ライブラリと多数のサードパーティライブラリが必要です。
生産的な言語のコホートから、Rustが最も適しているようです。 優れたパフォーマンスと安全な開発モデル、およびアクティブなコミュニティを提供します。 Rustの追加の利点は、データの不変性と透過的なマルチスレッドモデルです。
別の最適化オプションがあることに注意してください。 EPMDを介した追加呼び出しの時間とオーバーヘッドを無視できる場合は、NIFの代わりにErlangノードの記述方法を選択できます。 この問題を解決するには、Java、Go、Rust、Ocaml(個人的な経験から)が適しています。 Erlangノードは、同じマシンで実行することも、地球の反対側で実行することもできます。
実装
Rustの既存のソリューションの概要
クイック検索の後、NIFを簡単に作成するためのライブラリがいくつかあります。 それらを考慮してください:
- ざわめき おそらく最も人気があり機能的なライブラリかもしれませんが、著者はElixirのサポートに力を注いでいます。 https://github.com/hansihe/rustler/issues/127では、ミックスをアーランプロジェクトにドラッグすることを提案しています 。 Erlangで使用するドキュメントはありません。
- erlang-rust-nif 。 これは、NIFの低レベルの実装であり、拡張機能を構築するためのアプローチです。 コードはCからの単純な翻訳のように見えます。アセンブリには境界条件があり、普遍的ではありません。
- erlang_nif-sys 。 低レベルでフル機能のバンドル。 Rustlerの基礎です。 NIFの作成には手間と時間がかかります。
- bitwise_rust 。 スケジューラでの動作を示します。 また、C APIを介した構文糖衣のないラッパー。
要件の1つは開発速度であるため、Rustlerは最も魅力的に見えます。 ただし、Elixirとミックスビルダーの形式で追加の依存関係を作成することは望ましくありません。
ラストラー
「なぜエリクサープロジェクトをアーランにドラッグするのか?」という質問に答え、KISSの原則に従って、追加の依存関係なしで、ラスラーを使用することにしました。 ビルドシステムとして、rebar3が使用されます。 最も簡単で最速のステップは、錆びたコードをコンパイルするpre_hooksを定義することです。
これを行うには、テストプロファイルにフックを追加します。
{pre_hooks, [ {"(linux|darwin|solaris|freebsd)", compile, "sh -c \"cd crates/bloom && cargo build && cp target/debug/libbloom.so ../../priv/\""} ]}
戦闘環境では、 --release
オプションを追加します。そのため、戦闘プロファイルに以下を追加します。
{pre_hooks, [ {"(linux|darwin|solaris|freebsd)", compile, "sh -c \"cd crates/bloom && cargo build --release && cp target/release/libbloom.so ../../priv/\""} ]}
これらの操作の後、ダイナミックライブラリpriv/libbloom.so
されます。これは、Erlang VMにロードする準備が完全に整っています。
アーランプロジェクトでのrustlerの使用の詳細と例は、プロジェクトリポジトリhttps://github.com/Vonmo/erbloomにあります。
ブルームフィルター
rustエコシステムはブルームフィルターの既製の実装を提供するため、適切なものを選択し、それをcargo.toml
追加しcargo.toml
。 このプロジェクトはbloomfilter = "0.0.12"
使用します
拡張機能は次の機能を実装します。
-
new(bitmap_size, items_count)
-フィルターの初期化。bitmap_size
とitems_count
計算値。既製の計算機がたくさんあります。 -
serialize()
-後続のディスクへの保存またはネットワーク経由の送信などのために、パッケージをフィルターします。 -
deserialize()
-保存された状態からフィルターを復元します。 -
set(key)
-セットに要素を追加します。 -
check(key)
-要素がセットに属しているかどうかを確認します。 -
clear()
-フィルターをクリアします。
アーラン
拡張機能をErlangにロードすることは、完全に透過的なプロセスであることに注意してください。 モジュールをロードした後、on_loadが呼び出されます。erlangでnifロードを実装する必要があります:load_nif / 2。 この場合、コール処理はすでにRustで透過的に発生します。
良い経験則は、NIFがロードされていない場合、erlang:nif_error / 1エラーを生成することです。
プロジェクトを構築するための環境の詳細な説明は、 この記事に記載されています 。
まとめ
行われた作業の結果、生産的で安全な拡張を受けました。 私たちのプロジェクトでは、この拡張機能により、場合によってはデータウェアハウスへの呼び出しの量を最大10倍に減らし、マシンごとに500k RPSを超える呼び出しのストリームを処理できます。
拡張機能のソースコードはgithubで入手できます。