NIFでNIFを使用してErlangアプリケーションを安全に加速する

この記事では、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 ++と同じくらい速く、安全で簡単に保守できるソリューションを取得したいと思います。 最も計算効率の高い言語を見てみましょう。




言語要件に関しては、注目に値します:
  1. 安全性 ソリューションはどのような状況でもErlang VMを壊してはいけません
  2. パフォーマンス。 C ++とパフォーマンスを同等にする
  3. NIFモードで使用する機能
  4. 開発スピード。 言語の便利なエコシステムを提供するには、優れた標準ライブラリと多数のサードパーティライブラリが必要です。

生産的な言語のコホートから、Rustが最も適しているようです。 優れたパフォーマンスと安全な開発モデル、およびアクティブなコミュニティを提供します。 Rustの追加の利点は、データの不変性と透過的なマルチスレッドモデルです。


別の最適化オプションがあることに注意してください。 EPMDを介した追加呼び出しの時間とオーバーヘッドを無視できる場合は、NIFの代わりにErlangノードの記述方法を選択できます。 この問題を解決するには、Java、Go、Rust、Ocaml(個人的な経験から)が適しています。 Erlangノードは、同じマシンで実行することも、地球の反対側で実行することもできます。


実装


Rustの既存のソリューションの概要


クイック検索の後、NIFを簡単に作成するためのライブラリがいくつかあります。 それらを考慮してください:


  1. ざわめき おそらく最も人気があり機能的なライブラリかもしれませんが、著者はElixirのサポートに力を注いでいます。 https://github.com/hansihe/rustler/issues/127では、ミックスをアーランプロジェクトにドラッグすること提案しています 。 Erlangで使用するドキュメントはありません。
  2. erlang-rust-nif 。 これは、NIFの低レベルの実装であり、拡張機能を構築するためのアプローチです。 コードはCからの単純な翻訳のように見えます。アセンブリには境界条件があり、普遍的ではありません。
  3. erlang_nif-sys 。 低レベルでフル機能のバンドル。 Rustlerの基礎です。 NIFの作成には手間と時間がかかります。
  4. 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"使用します


拡張機能は次の機能を実装します。


  1. new(bitmap_size, items_count) -フィルターの初期化。 bitmap_sizeitems_count計算値。既製の計算機がたくさんあります。
  2. serialize() -後続のディスクへの保存またはネットワーク経由の送信などのために、パッケージをフィルターします。
  3. deserialize() -保存された状態からフィルターを復元します。
  4. set(key) -セットに要素を追加します。
  5. check(key) -要素がセットに属しているかどうかを確認します。
  6. clear() -フィルターをクリアします。

アーラン


拡張機能をErlangにロードすることは、完全に透過的なプロセスであることに注意してください。 モジュールをロードした後、on_loadが呼び出されます。erlangでnifロードを実装する必要があります:load_nif / 2。 この場合、コール処理はすでにRustで透過的に発生します。


良い経験則は、NIFがロードされていない場合、erlang:nif_error / 1エラーを生成することです。


プロジェクトを構築するための環境の詳細な説明は、 この記事に記載されています


まとめ


行われた作業の結果、生産的で安全な拡張を受けました。 私たちのプロジェクトでは、この拡張機能により、場合によってはデータウェアハウスへの呼び出しの量を最大10倍に減らし、マシンごとに500k RPSを超える呼び出しのストリームを処理できます。


拡張機能のソースコードはgithubで入手できます。



Source: https://habr.com/ru/post/J349398/


All Articles