1

コードブローが私のクラスだとします。単純化されており、完全ではありません。の実装に焦点を当てましょうoperator()

class Delta{
public:
    long long operator()() {
        auto now = steady_clock::now(); 
        auto delta = (now - last).count();
        last = now;
        return delta;
    }
private:
    steady_clock::time_point last;
};

operator()1秒間に数千回呼び出されることがあります。変数の割り当てと割り当て解除を頻繁に行うnowdelta、のパフォーマンスが低下する可能性がありますoperator()。それで、私が速度を最大にしたいのであればnowdeltaのデータメンバーを作成する方が良いですか?class Deltaしかし、コンパイル時にローカル変数が存在しない場合もあると聞きました。したがって、どういうわけかオーバーヘッドも存在しません。

実際、この演算子の速度は、アプリケーションの速度に影響を与えません。コンパイラに中立な答えを知りたくありません。この状況が来るとき。データメンバーまたはローカル変数にする必要がありますか?

4

5 に答える 5

2

nowx86-64では、このコードは両方で終わりdelta、RAXに割り当てられると思います。アセンブリ言語では、コードは次の順序で表示されます。

assume RSI:ptr _Delta
call steady_clock::now()
sub rax, [rsi].last
mov [rsi].last, rax
ret

もちろん、実際のアセンブリ言語では、steady_clock::now()(一例として)の名前が壊れているのがわかりますが、一般的な考え方はわかります。非静的メンバー関数に入ると、それthisはいくつかのレジスターに含まれます。戻り値は常に。になりraxます。コンパイラが他の変数にスペースを割り当てる必要がある(または必要とする)理由は特にわかりません。

32ビットx86では、スタックスペースを使用する可能性がはるかに高くなりますが、EDX:EAXで64ビット値が返される可能性があります。その場合、上記とほぼ同じようになります。 、もう1つのレジスタを使用するだけです。

他のほとんどのプロセッサは、x86よりも多くのレジスタで開始するため、レジスタの圧力は低くなります。たとえば、SPARCでは、ルーチンは通常、8つのローカルレジスタが空いていてすぐに使用できる状態で開始されるためnow、レジスタに割り当てることはほぼ確実です。

結論:速度に大きな違いが見られる可能性は低いですが、違いが見られる場合は、メンバー変数よりもローカル変数を使用する方が好ましいと思います。

于 2013-01-19T03:47:23.993 に答える
1

それは(もしあれば)大きな違いはありません。OSは、ページ単位でメモリ(スタックを含む)を割り当てます。したがって、スタックはおそらくページを完成させないため、プロセスは別のページを取得するためにコンテキストスイッチを必要としません。

コンパイラの中立的な答えに関しては、速度はコンテキストスイッチング、プロセッサ上で実行されている他のものに要約されます...。

それに加えて、あなたのような一部の人々は、マイクロパフォーマンスの改善に焦点を合わせているようですが、全体像は避けています。ボトルネックが最初にどこにあるかを最初に見つけて、それらに集中するのが最善です。80/20の法則を 覚えておいてください。

于 2013-01-19T03:18:11.850 に答える
1

最適化は通常、コンパイラーに依存します。ただし、ある程度適切なコンパイラを使用していると仮定すると、パフォーマンスが低下することはないので、心配する必要はありません。それを証明するために、私はあなたのコードをgcc 4.7、最適化レベル3でコンパイルしました。

call   400770 <std::chrono::system_clock::now()@plt> ;; Call.
mov    rdx,rax             ;; Remembe temporary value in %rdx.
sub    rax,QWORD PTR [rbx] ;; Divide
mov    QWORD PTR [rbx],rdx ;; Wrie Back.

コンテキストによっては、さらに最適化される場合があります。または悪化する可能性があります。一時変数をスタックに作成できる場合の例を示すために、との間に多くのコードを配置するnowlast、レジスタ割り当てアルゴリズムではすべての変数をレジスタに配置できなくなり、スタックを使用することになります。したがって、実際の結果を得るには、生成されたマシンコードを確認する必要があります。しかし率直に言って、ここで最適化することは、明らかなことを除いて多くはありません。パフォーマンスをそれほど気にする場合に心配しなければならないのは、PLTを介した多くの呼び出しです。言い換えれば、を使用しないでくださいstd::chrono::system_clock::now()

于 2013-01-19T03:38:05.170 に答える
0

私は他の答えに同意しませんが、これを機械語なしで簡単な言葉で説明しようと思います。重要な実際の詳細は無視しますが、あなたが求めている概念は教えないでください。

これらの変数を持つ関数があるとしましょう

 int a;
 int b;
 int c;
 int d;

コンパイル時に、コンパイラはすべてのローカル変数のサイズを合計し、関数が呼び出されると、ランタイムコードはすべての変数に十分なスタックスペースを割り当てます。したがって、sizeof(int)が4の場合、上記の変数には16バイトのスタックスペースが必要です。ほとんどのコンパイラは、マシンレジスタを使用してスタックポインタ(sp)を保持するため、関数が呼び出されると、ランタイムコードは次のようになります。

sp = sp + 16

4つの変数用のスペースを予約します。関数に1つまたは1000のローカル変数がある場合、ローカル変数を割り当てるランタイムコードは同じ時間かかることに注意してください。変数ごとのコストはありません(呼び出すctorがない場合)。次のようなCステートメントがある場合

d = b;

疑似マシンコードは次のようになります

*(sp + 12)= *(sp + 4)

ここで、12はスタック上の変数dのオフセットであり、4はbのオフセットです。(オフセットはこれほど単純ではなく、スタックに他のものが割り当てられています。)

次のようなメンバー変数を使用して構造体/クラスを定義した場合

class X {
 int a;
 int b;
 int c;
 int d;
 void foo() { d = b; }
};

コンパイラはまた、すべての変数のサイズを合計し、それぞれにオフセットを割り当てます。しかし今、foo()内のコードは次のようになります

*(this+12) = *(this + 4)

spはほとんどの場合マシンレジスタに保持されますが、「this」ポインタはマシンレジスタにある可能性が高いだけです。最新のコンパイラは、どの変数が最も使用されているかを調べ、それらの変数をレジスタに格納します。'this'は通常、多くの場合(多くの場合暗黙的に)参照されるため、通常はレジスターに割り当てられます。'this'がレジスターにある場合、パフォーマンスは同じである必要があります。

于 2013-01-19T04:39:55.957 に答える
0

大まかに言うと、コードにパフォーマンスの問題があります。

スタック上でoperator()が呼び出されるたびに、2つの変数が作成され、破棄されます(実際には発生します)。

システムは常にスタック用にメモリを予約し、同じメモリにアクセスするたびに、短期間のパフォーマンスに気付くことはありません。

しかし、長期的には(パフォーマンスラン)、違いを確認することができます。

于 2013-01-19T03:04:55.063 に答える