1

最近、次のような表現を使い始めました。

res += (i + n / i) * !(n % i);

私が想定している場合、これ!(n % i)は常に1または0の値になるため、次のような長いifステートメントを記述する代わりに、直接計算に使用できます。

if(!(n % i))
    res += (i + n / i);

ご参考までに、これらの行は、数値の適切な除数の合計を計算するために作成した関数から取得されていますn

unsigned int sum_of_divisors(unsigned int n)
{
    unsigned int res = 1;

    unsigned int i;
    for(i = 2; i < sqrt(n); ++i)
            res += (i + n / i) * !(n % i);
    res += i * (i * i == n);

    return res;
}

私の質問は、このコードは私が意図したとおりに動作することが保証されているかどうかです。これによるパフォーマンスへのおおよその影響は何ですか(乗算と条件付きジャンプ)?必要に応じて、コンパイラはとにかくそれを行いますか?

編集:私は実際のコードのパフォーマンスについて特に心配していないことに注意してください。私は、純粋な専門家の関心から、2つのうちどちらがより優れたパフォーマンスを発揮するのか、そしてその理由、そしてコンパイラーがそれぞれのケースをどのように処理するのかを知りたいと思います。

私がそのように書いた理由については、それは私の脳でうまく機能します:)

説明するのは難しいですが、少なくとも場合によっては、三項演算子やifステートメントの代わりに1または0を乗算する方が良い感じがします。

ありがとう、アンディ

4

3 に答える 3

3

はい、コンパイラは期待どおりに動作することが保証されています。

いいえ、コンパイラの品質が本当に低い場合を除き、コードが高速になることはありません。コンパイラは、両方のバージョンのコードをほぼ同等に扱い、条件付きロジックを実行するのに最適と思われる方法を選択する必要があります。

ちなみに、原則として、!演算子は条件分岐です。一部の実装 (CPU アーキテクチャ) には、実際のプログラム カウンター ブランチを必要としないように最適化する方法がある場合がありますが、ほとんどの条件に対して同じ方法が機能します。

最適化の観点から、コードが「より良い」方法が 1 つある場合があることに注意してください。書面で:

res += (i + n / i) * !(n % i);

res両方のコード パスに書き込む権限をコンパイラに与えています。形式:

if(!(n % i))
    res += (i + n / i);

resコンパイラは、条件が真の場合にのみ書き込むことができます。がローカルであり、そのアドレスが漏洩していない場合res、コンパイラは余分な書き込みを実行しても安全であると判断できますが、のアドレスがres関数の外で見える場合、コンパイラは他のスレッドがそれにアクセスできる可能性があり、そのコード パスを想定する必要があります。抽象マシンで変更resしないものは、生成されたコードでそれを変更してはなりません (安全に変更するために必要なロックを保持していない可能性があるため)。

于 2013-02-03T20:53:21.563 に答える
2

「いつ最適化するか」についての一般的な答えはないと思います。この件に関する私の個人的な見解は、すべての最適化は、直接実行するように依頼されるまで時期尚早であるということです。何度も聞かれるのを待つこともあります (誰かが本当に必要としているという安全な側にいるためです)。

それ以外に、最初のステートメントは次のように書くこともできます。

res += (n % i) ? 0 : (i + n / i);

このままでは掛け算と否定が分かりにくい。

編集

このコードは、意図したとおりに動作することが保証されていますか?

ただし、最初の部分 (0 または 1 で乗算する部分) に副作用 (誰かが ++ を実行するなど) がある場合、微妙なバグにつながる可能性があります。私が言ったように、最も自然な形を使用し、後で速度について心配してください.

于 2013-02-03T20:41:34.067 に答える
1

演算子は常に 0 または 1 に評価される!ため、仮定は正しいです。

パフォーマンスへの影響に関しては、そのソルトに値する最適化コンパイラは、ifステートメントまたは三項演算子と同一の (または同等の、パフォーマンス上の) コードを生成する必要があります。疑わしい場合は、アセンブリの出力 ( -Sgcc を使用している場合はスイッチ) を確認するか、コードをベンチマークしてください。

于 2013-02-03T20:52:26.813 に答える