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_size



    items_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で入手できます。








All Articles