web-dev-qa-db-ja.com

高性能APIは、低パフォーマンスユーティリティ関数を公開する必要がありますか?

コンテキスト:私は オープンソースプロジェクト に取り組んでおり、アドテックおよびソーシャルメディアのデータマイニングで発生する問題を解決しています:ブール式ツリーにインデックスを付け、それらを受信ドキュメントと照合します。ユーザーからの式ツリーは、インデックスに挿入する前にいくつかの変換を行う必要があります。これはO(2 ^ N)演算であり、最終的に追加するのが非常に速いバイナリルールを生成します。

このバイナリ形式を再度追加したい場合(つまり、次にアプリケーションを起動したとき、または複数のノードに分散する場合)に、ユーザーにこのバイナリ形式をキャッシュすることをお勧めします。ライブラリ自体は完全にメモリ内で実行されるため、外部データストアにキャッシュする必要があります。

現在、Java APIからルールを追加する方法は2つあります。

// Alternative (A): two-step method
byte[] rule = RuleDB.treeToBinary(...);   // static method to encode it (potentially slow)
db.addRuleBinary(rule);                   // actually add it (quick)

// Alternative (B): do both at once
db.addRuleTree(...);

バージョン(B)はAとほとんど同じです。唯一の違いは、Javaに渡すためにバッファの追加コピーを作成することです( GCed)、および別のJNI呼び出しが必要です。これらのコストは、ルールの変換に比べてわずかですが、200万個のルールを追加していて、これらの中間バッファをキャッシュできない場合でも、実際のコストです。

(B)を提供する必要があるかどうか、私は疑問に思っています。潜在的にわずかに高速で、もう少し便利です。ただし、可能な場合はユーザーに代替(A)を使用することも推奨します。単独では(A)は特別なことは何もしませんが、ユーザーがドキュメントを読まずに自分で結論を出せるように、ルールをキャッシュできることを強くお勧めします(正直に言うと、おそらくそうしません)。

6
Robert Fraser

相互運用性からパフォーマンス、バージョニング、下位バイナリ互換性などのさまざまな問題に取り組むAPIの場合、そのような便利な機能やユーティリティ関数を省略しても、長期的にはそれほど痛くない(そして、それを示す傷跡がある)。 「未加工」のエクスポートされたAPI自体から、維持する小さなレガシーを優先します。

私がやりたい最後のことは、ライブラリをより使いやすくすることだけを目的として、バージョン管理とABIの苦労をエクスポートされた関数に拡張する必要があることです。このようなライブラリインターフェイスの設計を何十年も維持することは、便宜上、これらのインターフェイスにあらゆる種類の追加機能を追加(およびエクスポート)することにより、「ネイティブ/ロー」形式でできるだけ使いやすくすることなしに維持することは十分に困難です。その代わりに、私はそこにミニマリズムと最大の余裕を持ち、非推奨の可能性と後方互換性に課されるあらゆる種類の追加のハードルを最小限に抑え、ライブラリの「生の」形式のユーザーの利便性を犠牲にしています。

上部のラッパー

とは言っても、実際には使用するのが難しくなるという犠牲を払って、ライブラリを実装/保守しやすくすることはお勧めしません。そのような場合に私が提案するのは、ターゲット言語での便利な使用法を基本的な懸念事項として扱うラッパーソートライブラリを上に作成することです。私がこの種のラッパーアプローチを好む多くの場合、実際には、「生の」、たとえばC APIを使用してラッパーを好まないようにしています。

また、ターゲット言語に重点を置いています。たとえば、APIがC APIである場合、FFIまたはJNIとこの種のものに必要になる可能性があるため、あなたがJavaのような他の言語からそれを使用しているなら、どんなに努力しても深刻な悲しみをもたらす可能性がある一方で、ネイティブC形式で使用することはそれほど便利でもなく、慣用的でも安全でもありません。長い目で見れば、レガシーコードのメンテナンスまで。したがって、このラッパーライブラリは、便利で安全な慣用的な使用法に取り組むため、Cライブラリを最小限に抑え、実装/維持/バージョンを簡単に保ちながら、Java)と書くことをお勧めします。たとえば、インターフェイスのバージョン管理や下位互換性の維持などの問題を最小限に抑えるために、.

ラッパーのメンテナンス

これは問題を引き起こすかもしれませんが、エクスポートされたAPIの上にラッパーを維持する必要があるのではないでしょうか?はい、ある程度は行っていますが、ユーザーのバイナリに内部的にリンクできるため、ABIレベルの下位互換性の問題はありません。したがって、10年前に作成されたものを古いバージョンのラッパーに対して壊すことなく、これらのラッパーを変更する余地がはるかにあります。

関数をdylibs /共有ライブラリ全体にエクスポートする「raw」APIを使用すると、下位互換性が問題にならない限り、下位互換性が保たれている限り、そこにあるすべての関数を元の形式で保持する必要があります。 12年前に誰かが付け加えたのは、その署名が実装にグローバル変数を利用することを要求しているので、それはそこに多くの悲嘆を残しているのです。そのため、便利な使用法についてほとんど、またはまったく心配することなく、エクスポートされ、バージョン管理されたAPIを可能な限り小さく単純化したままにしておくほうがはるかに簡単です。

パフォーマンス

ここで、特にAPIでパフォーマンスの懸念が高い場合、変更が必要になる可能性を最小限に抑えるために、非常に使い勝手の悪い関数をほぼ意図的に設計することもお勧めします。変化する。具体的な例として、OpenGL APIの次の関数を考えます。

// glVertexAttribPointer — define an array of generic vertex attribute data
void glVertexAttribPointer(GLuint index,
                           GLint size,
                           GLenum type,
                           GLboolean normalized,
                           GLsizei stride,
                           const GLvoid* pointer);

キモい!最初に、使用しているデータ/形式のタイプを指定するtypeパラメーター(例:GL_UNSIGNED_INT_2_10_10_10_REV)を使用して、頂点データへのvoidポインターを受け入れることを優先して、タイプセーフを放棄しました。

次に、固定小数点フィールド専用の正規化パラメーターがあり、シェーダーからアクセスしたときに値を正規化する必要があるかどうかを指定します。これは、すべてのタイプに関連するものではありません。

ストライドパラメータもあり、密にパックされておらず、他のデータとインターリーブされる可能性のあるデータを渡すことができますが、レンチをタイプセーフにスローし、関数がビットとバイトをスキップして1つから取得するように読み取ります次へフィールド。

そして、一般的なSEの観点から見ると、これは間違いなく設計が不十分な関数であり、Cの観点から使用するのも不便です。しかし、OpenGLのパフォーマンスと下位互換性の要件を考慮すると、変更する必要がある理由がほとんどないため、かなりうまく設計されていると思います。ハーフフロートのような新しい頂点データ形式をサポートしたい場合、関数のシグネチャを変更したり、新しい関数を導入したりする必要はありません。彼らは単にGL_HALF_FLOATのような新しい定数を導入することができます。そのようなデータを関数内で必要に応じて外部よりも正規化する方が効率的である場合、そのベースはnormalizedパラメーターでカバーされます。パックされた形式とは対照的に、インターリーブでこのすべての頂点データを渡す方が効率的である場合、strideパラメーターは、変更や新しい関数の導入を必要とせずにそのケースを処理します。

したがって、ユーザーの利便性を犠牲にしながら将来の変更が必要になる可能性を最小限に抑えるように設計されているため、後方互換性の維持に関して障害が最小限に抑えられます。したがって、パフォーマンスと下位互換性の両方が十分に重要な要件である場合(特に2つを組み合わせると非常にトリッキーな要件であり、特に組み合わせると)、この方法で関数を設計することをお勧めします。 、安全性、APIの変更の可能性を最小限に抑えるために犠牲にする必要があるもの(莫大なコストがかかり、常に下位互換性を放棄するように誘惑される可能性があります)。 、およびそれらのラッパーは、エクスポートされたAPIよりも変更がはるかに安価です。

2
Dragon Energy

まあ、それは実際には珍しいことではありません。

GNU MP Bignum Library という有名なbignumライブラリを見てみましょう。

整数(mpz_@@)、 合理的な (mpq_@@)および浮動小数点(mpf_@@)算術。
また、いくつかの雑多な機能。
これが高レベルのインターフェイスです。

さらに、低レベルの関数(mpn_@@)これはonlyが数値の処理を行います。セットアップも、メモリ管理も、その他の問題もありません。
これは、残りのユーザーが使用する低レベルのインターフェイスです。

別の例として、ほぼすべてのGUIライブラリについて同等の観察を行うことができます。

より便利な使用を可能にする新しいレベルの抽象化を低いレベルで構築することは非常に一般的であり、抽象化が妨げる可能性がある場合、多くの場合、低いレベルへの浸漬が完全なパフォーマンスと能力のために明示的にサポートされます。
ホイールを赤にペイントすることだけを許可するためにホイールを再発明するのは、かなり面倒です。

8
Deduplicator

低レベルの代替を提供してください。

中間のbyte[]CompiledRuleクラスに置き換えることで、より明確かつ堅牢にサポートすることも検討できます。

このシナリオは、Java regexパッケージJava.util.regexを思い出します。これは、正規表現を文字列から Pattern クラスに変換します。

3
Marco