Clang vs GCC - どちらが高速なバイナリを生成しますか? [closed] 質問する

Clang vs GCC - どちらが高速なバイナリを生成しますか? [closed] 質問する

現在、GCC を使用していますが、最近 Clang を発見し、切り替えを検討しています。ただし、決定的な要因が 1 つあります。それは、生成されるバイナリの品質 (速度、メモリ フットプリント、信頼性) です。1 gcc -O3% 高速に実行されるバイナリを生成できる場合、または Clang バイナリがより多くのメモリを占有する場合、またはコンパイラのバグが原因で失敗する場合は、決定的な要因になります。

Clang は GCC よりもコンパイル速度が速く、コンパイル時のメモリ使用量が少ないのが特長ですが、コンパイルされたソフトウェアのベンチマークや比較に非常に興味があります。既存のリソースや独自のベンチマークを教えていただけますか?

ベストアンサー1

以下は、GCC 4.7.2 および C++ 用の Clang 3.2 に関する、最新の、ただし限定的な私の調査結果です。

更新: GCC 4.8.1 と clang 3.3 の比較を以下に追加しました。

更新: GCC 4.8.2 と clang 3.4 の比較が追加されました。

私は、Linux 用に GCC と Clang の両方、および Windows 用の Microsoft コンパイラを使用して構築された OSS ツールを管理しています。このツールcoanは、C/C++ ソース ファイルやそのコードラインのプリプロセッサおよびアナライザです。その計算プロファイルは、再帰降下解析とファイル処理に重点を置いています。開発ブランチ (これらの結果が関係するブランチ) は、現在、約 90 個のファイルで約 11K LOC で構成されています。現在は、ポリモーフィズムとテンプレートが豊富な C++ でコーディングされていますが、それほど遠くない過去の、寄せ集めの C による多くのパッチにまだ悩まされています。移動セマンティクスは明示的には利用されていません。シングル スレッドです。私は、このツールの最適化に真剣に取り組んでいませんが、その「アーキテクチャ」は大部分が ToDo のままです。

3.2 より前の Clang は、コンパイル速度と診断機能が優れているにもかかわらず、C++11 標準サポートが coan が実行している点で現在の GCC バージョンに遅れをとっていたため、実験的なコンパイラとしてのみ使用していました。3.2 では、このギャップは解消されました。

現在の COAN 開発用の Linux テスト ハーネスは、1 つのファイルのパーサー テスト ケース、数千のファイルを使用するストレス テスト、および 1,000 未満のファイルを使用するシナリオ テストが混在する、およそ 70,000 個のソース ファイルを処理します。

テスト結果を報告するだけでなく、ハーネスは、消費されたファイルの合計と、coan で消費された実行時間を蓄積して表示します (各 coan コマンド ラインを Linuxtimeコマンドに渡し、報告された数値をキャプチャして合計するだけです)。測定可能な時間が 0 であるテストは、すべて合計すると 0 になるため、タイミングはより正確になりますが、そのようなテストの影響はごくわずかです。タイミングの統計は、make check次のようにして最後に表示されます。

coan_test_timer: info: coan processed 70844 input_files.
coan_test_timer: info: run time in coan: 16.4 secs.
coan_test_timer: info: Average processing time per input file: 0.000231 secs.

コンパイラー以外はすべて同じとして、GCC 4.7.2 と Clang 3.2 のテスト ハーネスのパフォーマンスを比較しました。Clang 3.2 では、GCC がコンパイルするコード領域と Clang の代替コード領域との間でプリプロセッサを区別する必要がなくなりました。いずれの場合も同じ C++ ライブラリ (GCC の) をビルドし、すべての比較を同じターミナル セッションで連続して実行しました。

私のリリース ビルドのデフォルトの最適化レベルは -O2 です。-O3 でのビルドのテストも成功しました。各構成を 3 回連続してテストし、3 つの結果を平均すると、次のようになります。データ セル内の数字は、coan 実行ファイルが約 70K の入力ファイルのそれぞれを処理するのにかかった平均マイクロ秒数 (読み取り、解析、および出力と診断の書き込み) です。

          | -O2 | -O3 |O2/O3|
----------|-----|-----|-----|
GCC-4.7.2 | 231 | 237 |0.97 |
----------|-----|-----|-----|
Clang-3.2 | 234 | 186 |1.25 |
----------|-----|-----|------
GCC/Clang |0.99 | 1.27|

特定のアプリケーションは、コンパイラの長所や短所に不公平に作用する特性を持っている可能性が非常に高いです。厳密なベンチマークでは、さまざまなアプリケーションを使用します。そのことを念頭に置くと、これらのデータの注目すべき特徴は次のとおりです。

  1. -O3最適化はGCCにわずかに悪影響を及ぼした
  2. -O3最適化はClangにとって非常に有益でした
  3. -O2最適化では、GCCはClangよりわずかに速かった。
  4. -O3 最適化では、Clang は GCC よりも大幅に高速でした。

これらの発見の直後に、2 つのコンパイラのさらに興味深い比較が偶然に浮かび上がりました。Coan はスマート ポインターを自由に使用しており、そのうちの 1 つはファイル処理で頻繁に使用されます。この特定のスマート ポインター型は、コンパイラーの差別化のために以前のリリースで typedef されており、std::unique_ptr<X>構成されたコンパイラーが としての使用に対して十分に成熟したサポートを持っている場合は 、そうでない場合は でしたstd::shared_ptr<X>。これらのポインターは実際には転送されていたため、 への偏りはstd::unique_ptr愚かでしたが、C++11 バリアントが私にとって目新しい時点では、std::unique_ptr置き換えるためのより適切なオプションのように見えました。std::auto_ptr

Clang 3.2 で引き続きこの機能や同様の差別化が必要であるかどうかを判断するための実験的なビルドの過程で、std::shared_ptr<X>をビルドするつもりだったのにstd::unique_ptr<X>、 を誤ってビルドしてしまいました。その結果得られた実行ファイルは、デフォルトの -O2 最適化により、これまで見た中で最速であり、入力ファイルごとに 184 ミリ秒を達成することもあり、驚きました。ソース コードにこの 1 つの変更を加えただけで、対応する結果は次のようになりました。

          | -O2 | -O3 |O2/O3|
----------|-----|-----|-----|
GCC-4.7.2 | 234 | 234 |1.00 |
----------|-----|-----|-----|
Clang-3.2 | 188 | 187 |1.00 |
----------|-----|-----|------
GCC/Clang |1.24 |1.25 |

ここで注目すべき点は次のとおりです。

  1. どちらのコンパイラも、現在では -O3 最適化の恩恵をまったく受けていません。
  2. Clang は、最適化の各レベルにおいて同様に重要な点で GCC を上回ります。
  3. GCC のパフォーマンスは、スマート ポインター型の変更によってわずかに影響を受けるだけです。
  4. Clang の -O2 パフォーマンスは、スマート ポインター型の変更によって大きく影響を受けます。

スマート ポインター型の変更の前後で、Clang は -O3 最適化で大幅に高速な coan 実行可能ファイルを構築でき、そのポインター型がstd::shared_ptr<X>ジョブに最適な場合は -O2 と -O3 でも同様に高速な実行可能ファイルを構築できます。

私にはコメントする資格のない明白な疑問は、頻繁に使用されるスマート ポインター型が一意から共有に変更されたときに、Clang ではアプリケーションで 25% の -O2 速度向上が見られるのに、GCC は同じ変更に無関心なのはなぜかということです。また、Clang の -O2 最適化が、私のスマート ポインター選択の賢明さに非常に敏感であるという発見を喜ぶべきか、それともブーイングすべきか、私にはわかりません。

更新: GCC 4.8.1 v clang 3.3

対応する結果は次のとおりです。

          | -O2 | -O3 |O2/O3|
----------|-----|-----|-----|
GCC-4.8.1 | 442 | 443 |1.00 |
----------|-----|-----|-----|
Clang-3.3 | 374 | 370 |1.01 |
----------|-----|-----|------
GCC/Clang |1.18 |1.20 |

4 つの実行可能ファイルすべてが 1 つのファイルを処理するのに以前よりも平均的に長い時間がかかるようになったという事実は、最新のコンパイラのパフォーマンスを反映しているわけでありません。これは、テスト アプリケーションの後続の開発ブランチがその間に解析の高度化を大幅に進め、速度が向上したためです。重要なのは比率だけです。

注目すべき点は、目新しいものではありません。

  • GCCは-O3最適化には無関心です
  • clangは-O3最適化からほんのわずかながら恩恵を受ける
  • clang は、最適化の各レベルにおいて、同様に重要な差で GCC を上回ります。

これらの結果を GCC 4.7.2 および clang 3.2 の結果と比較すると、GCC が各最適化レベルで clang のリードの約 4 分の 1 を取り戻したことが際立っています。ただし、テスト アプリケーションはその間に大幅に開発されているため、これを GCC のコード生成の追い上げに確実に帰することはできません。(今回は、タイミングを取得したアプリケーション スナップショットを書き留めておいたので、これを再度使用できます。)

更新: GCC 4.8.2 v clang 3.4

GCC 4.8.1 v Clang 3.3 のアップデートを終え、今後のアップデートでは同じ coan スナップショットを使用するつもりだと言いました。しかし、代わりにそのスナップショット (リビジョン 301)と、テスト スイートに合格した最新の開発スナップショット (リビジョン 619) でテストすることにしました。これにより結果に少し経度が加わりますが、別の動機もありました。

最初に投稿した記事では、coan の速度を最適化する努力をまったくしていないと書きました。これは、リビジョン 301 の時点でも変わりませんでした。しかし、タイミング装置を coan テスト ハーネスに組み込んだ後、テスト スイートを実行するたびに、最新の変更によるパフォーマンスへの影響が目の前に現れました。その影響は驚くほど大きく、機能の向上によって得られるメリットよりも大幅にマイナスの傾向にあることがわかりました。

改訂 308 では、テスト スイートの入力ファイルあたりの平均処理時間は、ここでの最初の投稿以来 2 倍以上になりました。この時点で、私は 10 年間パフォーマンスを気にしないという方針を一転しました。改訂 619 までの集中的な改訂では、パフォーマンスは常に考慮され、その多くは、主要な負荷ベアラを根本的に高速な行に書き直すことだけに集中しました (ただし、そのために非標準のコンパイラ機能を使用することはありませんでした)。この一転に対する各コンパイラの反応を見るのは興味深いでしょう。

以下は、最新の 2 つのコンパイラの rev.301 ビルドの、今ではおなじみのタイミング マトリックスです。

coan - rev.301 の結果

          | -O2 | -O3 |O2/O3|
----------|-----|-----|-----|
GCC-4.8.2 | 428 | 428 |1.00 |
----------|-----|-----|-----|
Clang-3.4 | 390 | 365 |1.07 |
----------|-----|-----|------
GCC/Clang | 1.1 | 1.17|

ここでの状況は、GCC-4.8.1 および Clang-3.3 からわずかに変化しただけです。GCC の結果はわずかに向上し、Clang の結果はわずかに低下しました。ノイズが原因と考えられます。Clang は依然として優位に立っており-O2-O3その差はほとんどのアプリケーションでは問題になりませんが、かなりの数のアプリケーションでは問題になります。

そしてこちらが rev. 619 のマトリックスです。

coan - rev.619 の結果

          | -O2 | -O3 |O2/O3|
----------|-----|-----|-----|
GCC-4.8.2 | 210 | 208 |1.01 |
----------|-----|-----|-----|
Clang-3.4 | 252 | 250 |1.01 |
----------|-----|-----|------
GCC/Clang |0.83 | 0.83|

301 と 619 の数字を並べて見ると、いくつかの点が浮かび上がってきます。

  • 私はより高速なコードを書くことを目指していましたが、両方のコンパイラは私の努力をはっきりと証明してくれました。しかし:

  • GCC は、Clang よりもはるかに寛大にそれらの努力に報います。-O2最適化では、Clang の 619 ビルドは 301 ビルドよりも 46% 高速です。Clang-O3の改善は 31% です。良好ですが、各最適化レベルで、GCC の 619 ビルドは 301 よりも 2 倍以上高速です。

  • GCC は Clang の以前の優位性を覆す以上の成果を上げています。そして、各最適化レベルで GCC は Clang を 17% 上回っています。

  • 301 ビルドでは Clang は-O3最適化によって GCC よりも大きな効果を得られましたが、619 ビルドではそれがなくなりました。どちらのコンパイラも から大きな利益を得ることはありません-O3

この運命の逆転に私はかなり驚いて、誤って clang 3.4 自体のビルドが遅くなってしまったのではないかと疑いました (ソースからビルドしたため)。そこで、ディストリビューションの標準の Clang 3.3 で 619 テストを再実行しました。結果は 3.4 の場合と実質的に同じでした。

それで、この方向転換に対する反応についてですが、ここでの数字を見ると、私が Clang に何も手伝っていなかったとき、C++ コードの高速処理において Clang は GCC よりもはるかに優れたパフォーマンスを示しました。私が手伝うことに決めたとき、GCC は Clang よりもはるかに優れたパフォーマンスを示しました。

私はその観察を原則にまで高めるつもりはありませんが、「どのコンパイラがより優れたバイナリを生成するか」という質問は、答えが相対的となるテスト スイートを指定したとしても、バイナリのタイミングだけの問題ではないという教訓を得ています。

優れたバイナリは最速のバイナリでしょうか、それとも安価に作成されたコードを最もうまく補うものなのでしょうか。それとも、速度よりも保守性と再利用性を優先した、高価に作成されたコードを最もうまく補うものなのでしょうか。それは、バイナリを作成する動機の性質と相対的な重み、およびバイナリを作成する際の制約によって異なります。

いずれにせよ、「最良」のバイナリを構築することに深い関心がある場合は、コードの連続した反復を通じて、コンパイラの連続した反復が「最良」のアイデアをどのように実現するかを継続的に確認することをお勧めします。

おすすめ記事