2

関数の実装の1つがユーザーに依存するクラスを書いています。現在、私はそれを仮想関数として持っており、ユーザーはその実装を提供するために私のクラスをオーバーライドする必要があります。ユーザーが登録できるように、functor(boost :: function)/function_pointerにすることを考えています。アプリケーションはパフォーマンスが非常に重要であり、見栄えの良いクラスよりも速度が重要です。ファンクターに変更すると、パフォーマンスが向上しますか?

ほとんどの場合、それは無料の関数になるので、関数ポインタは問題ないはずです。しかし、状態が必要になる場合があり、したがってそれはファンクターである必要があると思います。

function_ptrまたはfunctorのいずれかを登録し、ブール値に基づいて適切なものを呼び出すことを許可することで、パフォーマンス上の利点が得られますか?これに似たもの。

class Myclass
{
   public:
    registerFuncPtr(FuncPtrSignature);
    registerFunctor(boost::function func);
   private:    
   someMethod(someArgs)
   {
    ...
    if(_isFuncPtr)
        _func_ptr(args);
    else
        _functor(args);
    ...
   }
   bool _isFuncPtr;
   FuncptrSignature _func_ptr;
   boost::function _functor;
}

アップデート:

私は共有ライブラリを書いていますが、クライアントはそれに対して動的にリンクします。C++0xはありません。:(

4

3 に答える 3

6

これは、特定のユースケースとユーザーがコードを追加する方法によって異なります。以下は純粋に理論的な分析ですが、実際に決定する前に、現実的なシナリオを作成し、パフォーマンスをテストする必要があります。

コンパイル時にコードを追加する場合(つまり、ユーザーにコードを提供し、ユーザーがロジックを作成してすべてを一緒にコンパイルする場合)、おそらく最善のアプローチは、Functor型引数を取るテンプレートを提供することです。

template <typename Functor> void doProcessing( Functor f) {
   f( data ); 
}

利点は、コンパイラがコード全体にアクセスできることです。つまり、コンパイラがコードをインライン化するかどうかについて最善の決定を下すことができ、インライン化の場合はさらに最適化できる可能性があります。欠点は、新しいロジックごとにプログラムを再コンパイルする必要があることと、製品をユーザー拡張機能と一緒にコンパイルする必要があることです。多くの場合、それは不可能です。

メイン製品のコンパイル後に拡張機能を実行する場合、つまりクライアントが独自の拡張機能を作成し、コンパイルされた実行可能ファイル(プラグインを考えてください)に使用できる場合は、代替案を検討する必要があります。

  • 関数ポインタを受け入れる(Cウェイ)

  • その操作(Javaの方法)だけでインターフェース(基本クラス)を提供します

  • 型消去を実行するファンクターラッパー(boost::function/ )を使用するstd::function

これらは純粋なパフォーマンス順に並べられています。Cウェイのコストは、3つのうち小さい方ですが、ほとんどの場合、インターフェイスオプションとの違いはごくわずかであり(関数呼び出しごとに余分な間接参照)、呼び出し元のオブジェクトに状態を保持するオプションが購入されます(これにより、最初のオプションでグローバル状態を介して実行されます)。

3番目のオプションは、呼び出し可能なユーザーに型消去を適用するため、最も一般的であり、コンパイラーがサポートしている場合はラムダなどの関数アダプターを使用して、ユーザーが既存のコードを再利用できるようにします。これは最も一般的であり、ユーザーにほとんどの選択肢を残します。ただし、それに関連するコストがあります。型消去には仮想ディスパッチに加えて、実際のコードへの追加の関数呼び出しがあります。boost::bindstd::bind

ユーザーがインターフェースをコードに適合させるための関数を作成する必要がある場合、手動で作成されたアダプターのコストはおそらく同等であるため、汎用性が必要な場合はboost::function/std::functionが最適です。

As of the difference in costs of the alternatives, they are most probably very small overall, and whether scratching one or two operations matters will depend on how tight the loop is and how expensive the user code is. If the user code is going to take a couple hundred instructions, there is probably no point in not using the most generic solution. If the loop is run a few thousand times per second, scratching a few instructions will not make a difference either. Go back, write a realistic scenario and test, and while testing be aware of what really matters to the user. Scratching a few seconds of an operation that takes minutes is not worth loosing the flexibility of the higher level solutions.

于 2011-08-27T11:12:33.763 に答える
1

パフォーマンスが必要な場合は、裸の関数ポインターを使用してください。状態が必要な場合はvoid*、関数にタイプの個別の状態引数を指定し、ユーザーが状態を登録できるようにします。ファンクターと仮想関数、およびその他すべては、多かれ少なかれタイプセーフなエンベロープにラップされた関数ポインターであるため、C++構造をそれよりも高速にする魔法はありません。

さらに重要なことに、最適化について話すときは、プロファイラー以外は何も信頼してはなりません(これには上記の段落が含まれます)。

インライン化は理論的にはパフォーマンスを向上させることができますが、ユーザーの関数をインライン化することはできません(コンパイラーはまだ記述されていないコードをインライン化することはできません)。ライブラリをコンパイルせずにソースコードとして配布する場合は、インライン化によってパフォーマンスが向上する可能性があります。いつものように、プロファイラーだけが知ることができます。

関数オブジェクトが関数ポインターよりも優れている可能性が高い1つのケースは、それらを関数テンプレートに渡すことです。テンプレート自体がインライン化されていない場合、関数オブジェクトはテンプレートのインスタンス化内でインライン化される可能性が高く、関数ポインターはインライン化されません。

于 2011-08-27T11:49:42.750 に答える
0

コンパイラーは、関数ポインターを介した関数の呼び出しではなく、Functor呼び出しを簡単にインライン化できます。したがって、関数ポインターよりもFunctorを優先します。

于 2011-08-27T10:29:00.247 に答える