0

私は最初の計算機を実装しようとしています。私の古いコード(switch-case):

enum arithmetic_type{
    add = 0,
    subtract = 1,
    multiply = 2,
    divide = 3
};

inline void calculate(double &var, double value, arithmetic_type type){

    switch(type)
    {
        case add : var += value;break;
        case subtract : var -= value;break;
        case multiply : var *= value;break;
        case divide : var /= value;break;
    }

}

「関数定義へのポインタ」を見て、新しいアイデアを思いつきました。代わりに、別々の関数を使用します。今私のコードは次のようになります:

typedef void(*arithmetic_type)(double &var, double value); //template

inline void add(double &var, double value){var+=value;} //components
inline void subtract(double &var, double value){var-=value;}
inline void multiply(double &var, double value){var*=value;}
inline void divide(double &var, double value){var/=value;}
////////////////////////////////////////////////////////////////
struct VAR
{
    double var_value;
    arithmetic_type operator_type;
    inline void calculate(double value){operator_type(var_value, value);}
};

switch-caseよりもはるかに簡単だと思います。さらに重要なのは、関係演算子などの他の演算子を追加することです。したがって、この新しいソリューションは、古いスイッチケースソリューションよりも明確で便利だと思います。:)

しかし、私はまだコードの速度とパフォーマンスについて疑問を持っています。パフォーマンスは速くなりますか?

4

3 に答える 3

1

私は個人的に関数ポインタバージョンを好みます。それはあなたが望むものをより直接的にエンコードします:それぞれが関数である一連の操作。

速度の違いは、あるとしてもごくわずかです。intポインタとはほとんどのシステムで同じサイズであり、列挙型はint内部と同じサイズを使用して格納される可能性が高いため、引数の受け渡しに違いはありません。どちらかといえば、ポインターの逆参照とこれらの関数の呼び出しは、切り替えを実行するよりも高速である可能性があります。これは、一連の比較と条件付きジャンプに要約される可能性があります。

もちろん、操作が非常に単純であるため、関数にそれらを格納するのはやり過ぎであり、不必要に高度な抽象化であると言う人もいるかもしれません。これらの関数はプログラムの本質的なコアを提供し、それらすべてを可能な限り純粋かつ均一に処理する必要があるため、私は同意しません。それらの1つをより複雑にする必要がある場合はswitch、すべての関数が肥大化する可能性がありますが、この関数ポインターベースの設計には影響しません。

ですから、本質的に、私はあなたの新しいアプローチによる利益だけを見ています。

于 2013-01-29T13:05:38.353 に答える
0
  1. 「インライン」を使用したため、関数コードは使用する場所にコピーされ、そこでは利益がありません。
  2. ポインタは確かにパフォーマンスを最適化しますが、小さなサイズの変数を使用するため、ここではほとんど目立ちません(MBのメモリを使用する巨大な写真とは異なります)。
  3. コードはそれほど読みやすくはありません。このコードを簡単に理解できることは確かですが、この規則を数千行のコードとして想像してください。

したがって、私の最終的な考えは、このようなポインタを使用せず、メモリを消費する大きな変数で使用することです。

于 2013-01-29T13:08:37.357 に答える
0

まず第一に-原則として、あなたは有限の資源を持っています。多くの場合、最も価値のあるリソースはプログラミングの時間です。このような場合は、最も簡単で読みやすいバージョンを使用する必要があります。問題の説明から、(最適化のおもちゃの例でない限り)時期尚早に最適化を開始したように見えます。

とはいえ、パフォーマンスを気にする場合もあります。通常、既存の実装がありますが、パフォーマンスは目標を満たしていません(たとえば、計算には数時間ではなく数日かかります)。「単純な計算機」がそのような問題に近づいたとは思えませんが、議論のためにそれを仮定しましょう。次に、使用できるマクロ最適化のブランチ全体があります。つまり、全体像(関数ポインターとケース)ではなく、全体像(アルゴリズムの変更など)について考える必要があります。まず最初に、速度低下の原因を突き止める必要があります(プログラムの実行に10%の時間がかかり、50%スピードアップした場合、全体的な改善は5%になりますが、残りのプログラムを改善した場合は25%パフォーマンスが22%向上します)。

さらにまれなケースでは、このような最適化を行った後でも、パフォーマンスの目標が十分に高いため、コードがまだ一致していません。これは通常、プログラムが大量に計算され、多くのマシンで数か月または数年実行される場合にのみ発生します。たとえば、タンパク質のフォールディングは、そのような方法で最適化する価値がありますが、人気のあるプログラムでさえ、そうではありません。通常、この時点で知っておく必要があります。

  • 最適化する正確なプラットフォーム
  • 基本的なコンピュータアーキテクチャ(パイプライン、ILP、キャッシュアーキテクチャ、キャッシュコヒーレンスプロトコルなど)
  • 使用しているコンパイラ

これに参加している場合は、BTBのみを使用している関数ポインターを使用しながら、分岐予測をより有効に活用できるため、ケース/スイッチのパフォーマンスが向上する可能性があります。一方、アーキテクチャと非常に単純なCコンパイラを使用している場合は、ケース/スイッチがIキャッシュをオーバーフローする可能性があります(したがって、関数ポインタのパフォーマンスが向上します)。


総括する:

  1. 最適化しないでください
  2. まだ最適化しないでください(専門家のみ)

場合によっては、通常は最適化する必要があることは事実ですが、マクロ最適化から始めて、速度低下の原因を認識することをお勧めします。あなたのプログラムがとにかくI/Oを待っているなら(私が電卓で疑う場合)、あなたのプログラムがnsより速く応答するかどうかは誰にもわかりません。同様に、パーサー/トークンライザーがボトルネックである場合、実行の最適化はあまり役に立ちません。

マクロ最適化が十分でなく、実行時間の1%の改善が数週間の作業に値する場合は、プロセッサーとコンパイラーの両方を知っているマイクロ最適化を検討することをお勧めします。

于 2013-01-29T13:45:10.460 に答える