アプリケーションアーキテクチャの非常に低いレベルのポイントでコードをマイクロ最適化しようとしています。これが私の具体的なシナリオです。
- グラフファイル(ノード、エッジ、隣接エントリなど)を解析するパーサークラスがあります。
- ファイル形式はバージョン管理されているため、バージョンごとに個別のクラス(ParserV1、ParserV2、...)として実装されるパーサーが存在します。
- パーサーは、アプリケーションの一部の上位層に同じ機能を提供します。したがって、それらは同じ「インターフェース」を実装します。
- C ++では、すべての関数が純粋な仮想である抽象クラスなどのインターフェイスを実装します。
- 仮想関数は別のメモリルックアップを必要とし、コンパイル時に静的にバインドすることはできず、さらに重要なことですが、パーサークラスで小さなメソッドをインライン化することはできないため、従来のサブクラス化イディオムを使用しても、私が達成できる最高のパフォーマンス。
[考えられる解決策を説明する前に、ここでマイクロ最適化を行っている理由を説明したいと思います(この段落はスキップできます):パーサークラスには多くの小さなメソッドがあります。「小さい」とは、あまり機能しないことを意味します。 。それらのほとんどは、キャッシュされたビットストリームから1バイトまたは2バイト、あるいは1ビットのみを読み取ります。したがって、非常に効率的な方法でそれらを実装できるはずです。関数呼び出しは、インライン化されたときに、ほんの一握りのマシンコマンドしか必要としません。メソッドは非常に大きなグラフ(世界規模の道路網)でノード属性を検索するため、アプリケーションで頻繁に呼び出されます。これは、ユーザーのリクエストごとに約100万回発生する可能性があり、そのようなリクエストは次のように高速である必要があります。可能。]
ここに行く方法はどれですか?私は問題を解決するために次の方法を見ることができます:
- 純粋仮想メソッドを使用してインターフェースを作成し、それをサブクラス化します。パフォーマンスが低下します。
- そのようなインターフェースを書かないでください。各パーサーは、それ自体で同じメソッドを定義します。(パーサーを使用する)上位層には、各バージョンのサブクラスへの(メンバーとしての)ポインターがあります。最初に、使用する必要がある特定のパーサーをインスタンス化します。関数にアクセスするときはいつでも、スイッチブロックを使用して、パーサーインスタンスを明示的なサブクラスにキャストします。パフォーマンスは向上しますか?(if / switchブロックと仮想テーブルルックアップ)。
- 2つのソリューションを組み合わせる1.+2 .:パフォーマンスがそれほど重要ではない、めったに使用されないメソッド用の純粋仮想メソッドを使用してインターフェースを作成します。重要な場合は、仮想メソッドを提供せずに、2番目のメソッドを使用してください。
- 2.の改善:抽象クラスに非仮想メソッドを提供します。抽象クラス(独自の実行時型情報の一種)のメンバー変数としてバージョン番号を保持し、これらのメソッドでif/switchブロックとキャストを実装します。次に、サブクラスのメソッドを呼び出します。これにより、インライン化と静的バインディングの両方が提供されます。
この問題を解決するためのより良い方法はありますか?このためのイディオムはありますか?
明確にするために、私はバージョンに依存しない(少なくとも今までは)多くの関数を持っているので、いくつかのスーパークラスに完全に適合しています。ほとんどの関数に標準のサブクラス化設計を使用しますが、この質問では、バージョンに依存する関数を最適化するためのソリューションのみを取り上げます。(それらのいくつかはあまり頻繁に呼び出されず、もちろんこれらの場合に仮想メソッドを使用できます。)これに加えて、パーサークラスにパフォーマンスが必要なメソッドとパフォーマンスが必要でないメソッドを決定させるというアイデアは好きではありません。 。(そうすることは可能ですが。)