C ++コンパイル時間を短縮するために使用できるテクニックは何ですか?


249

C ++コンパイル時間を短縮するために使用できるテクニックは何ですか?

この質問は、Stack Overflowの質問C ++プログラミングスタイルへのコメントで出てきました。どんなアイデアがあるのか​​知りたいです。

私は関連する質問を見ました、なぜC ++コンパイルはそんなに時間がかかるのですか?、しかしそれは多くの解決策を提供しません。


1
コンテキストを教えていただけますか?または、非常に一般的な答えを探していますか?
Pyrolistical 2008


一般的な答え。多くの人が書いた非常に大きなコードベースを持っています。それを攻撃する方法についてのアイデアは良いでしょう。また、新しく作成されたコードのコンパイルを迅速に維持するための提案も興味深いでしょう。
スコットランガム

多くの場合、ビルド時間の関連部分はコンパイラではなくビルドスクリプトで使用されることに注意してください
2015

1
私はこのページを読み飛ばしましたが、測定値についての言及はありませんでした。受け取った入力の各行にタイムスタンプを追加する小さなシェルスクリプトを作成したので、パイプを使用して「make」呼び出しを実行できます。これにより、タイムスタンプを比較するだけで、どのターゲットが最も高価であるか、合計コンパイル時間またはリンク時間などを確認できます。このアプローチを試す場合、タイムスタンプは並列ビルドでは不正確になることに注意してください。
ジョンP

回答:


257

言語テクニック

Pimplイディオム

ここここで、不透明なポインターまたはハンドルクラスとも呼ばれるPimplイディオムを 見てください。コンパイルを高速化するだけでなく、スローしないスワップ関数と組み合わせると例外の安全性も向上します。Pimplイディオムを使用すると、ヘッダー間の依存関係を減らし、実行する必要がある再コンパイルの量を減らすことができます。

前方宣言

可能な限り、前方宣言を使用してください。コンパイラーがそれSomeIdentifierが構造体またはポインターなどであることだけを知る必要がある場合は、定義全体を含めずに、コンパイラーに必要以上の処理を強います。これにはカスケード効果があり、必要な速度よりも遅くなります。

I / Oの流れは特にビルドを減速のために知られています。ヘッダーファイルで必要な場合は、<iosfwd>代わりに<iostream>#includeを試し、#includeを<iostream>実装ファイルのみに含めます。<iosfwd>ヘッダーには、前方宣言のみを保持しています。残念ながら、他の標準ヘッダーにはそれぞれの宣言ヘッダーがありません。

関数シグネチャでは、値渡しよりも参照渡しを優先します。これにより、ヘッダーファイルにそれぞれの型定義を#includeする必要がなくなり、型を前方宣言するだけで済みます。もちろん、不明瞭なバグを避けるために、const参照を非const参照よりも優先しますが、これは別の質問の問題です。

ガード条件

ガード条件を使用して、ヘッダーファイルが1つの変換単位に複数回含まれないようにします。

#pragma once
#ifndef filename_h
#define filename_h

// Header declarations / definitions

#endif

プラグマとifndefの両方を使用することにより、プレーンマクロソリューションの移植性と、一部のコンパイラーがpragma onceディレクティブの存在下で実行できるコンパイル速度の最適化が得られます。

相互依存を減らす

一般に、コード設計がモジュール化され、相互依存性が低いほど、すべてを再コンパイルする必要が少なくなります。また、追跡する必要が少ないという事実により、コンパイラが個々のブロックに対して同時に実行する必要がある作業量を減らすことにもなります。

コンパイラーオプション

プリコンパイル済みヘッダー

これらは、含まれるヘッダーの共通セクションを一度に多くの翻訳単位用にコンパイルするために使用されます。コンパイラはそれを1回コンパイルし、その内部状態を保存します。次に、その状態をすばやくロードして、同じヘッダーのセットを持つ別のファイルをコンパイルする際に有利なスタートを切ることができます。

まれに変更されるものだけをプリコンパイル済みヘッダーに含めるか、必要以上に頻繁に完全な再構築を行うことに注意してください。これは、STLヘッダーやその他のライブラリインクルードファイルに適した場所です。

ccacheは、キャッシングテクニックを利用して処理速度を上げる別のユーティリティです。

並列処理を使用する

多くのコンパイラ/ IDEは、複数のコア/ CPUを使用して同時にコンパイルすることをサポートしています。ではGNUのメイク(通常はGCCで使用)、使用-j [N]オプションを選択します。Visual Studioでは、複数のプロジェクトを並行してビルドできるようにするオプションが設定の下にあります。プロジェクトレベルの並列処理だけでなく、ファイルレベルの並列処理の/MPオプションを使用することもできます。

その他の並列ユーティリティ:

より低い最適化レベルを使用する

コンパイラーが最適化しようとすればするほど、動作しにくくなります。

共有ライブラリ

あまり変更されていないコードをライブラリに移動すると、コンパイル時間を短縮できます。共有ライブラリ(.soまたは.dll)を使用すると、リンク時間も短縮できます。

より高速なコンピュータを入手する

より多くのRAM、より高速なハードドライブ(SSDを含む)、およびより多くのCPU /コアは、すべてコンパイル速度に違いをもたらします。


11
ただし、プリコンパイル済みヘッダーは完全ではありません。それらを使用することの副作用は、必要以上に多くのファイルが含まれることです(すべてのコンパイル単位が同じプリコンパイル済みヘッダーを使用するため)。これにより、必要以上に頻繁に完全再コンパイルが強制される場合があります。心に留めておくべきこと。
2008

8
最近のコンパイラでは、#ifndefは#pragmaと同じくらい高速です(インクルードガードがファイルの先頭にある限り)。したがって、コンパイル速度の点で#pragmaを1回使用してもメリットはありません
jalf

7
2008ではなくVS 2005しかない場合でも、/ MPスイッチをコンパイルオプションに追加して、.cppレベルでの並列ビルドを有効にすることができます。
macbirdie 2009

6
SSDは、この回答が書かれたときは法外に高価でしたが、今日では、C ++をコンパイルするときに最適です。コンパイル時に多くの小さなファイルにアクセスします;。SSDが提供する多くのIOPSが必要です。
MSalters 2010

14
関数シグネチャでは、値渡しよりも参照渡しを優先します。これにより、ヘッダーファイルにそれぞれの型定義を#includeする必要がなくなります。これは誤りです。値で渡す関数を宣言するために完全な型を持っている必要はありません。その関数を実装または使用するには、完全な型だけが必要です。が、ほとんどの場合(通話を転送するだけの場合を除き)、とにかくその定義が必要になります。
デビッドロドリゲス-11

43

私は、非常にテンプレート化されたC ++ライブラリであるSTAPLプロジェクトに取り組んでいます。たまには、コンパイル時間を短縮するためにすべての手法を再検討する必要があります。ここでは、私たちが使用するテクニックをまとめました。これらのテクニックのいくつかは、すでに上記にリストされています。

最も時間がかかるセクションを見つける

シンボル長とコンパイル時間の間に実証済みの相関関係はありませんが、平均シンボルサイズを小さくすると、すべてのコンパイラでコンパイル時間を改善できることがわかっています。したがって、最初の目標は、コード内で最大のシンボルを見つけることです。

方法1-サイズに基づいて記号を並べ替える

nmコマンドを使用して、サイズに基づいてシンボルを一覧表示できます。

nm --print-size --size-sort --radix=d YOUR_BINARY

このコマンドでは--radix=d、サイズを10進数で表示できます(デフォルトは16進数です)。次に、最大のシンボルを見て、対応するクラスを分割できるかどうかを確認し、基本クラスのテンプレート化されていない部分を因数分解するか、クラスを複数のクラスに分割して再設計してみます。

方法2-長さに基づいて記号を並べ替える

通常のnmコマンドを実行し、それをお気に入りのスクリプト(AWKPythonなど)にパイプして、長さに基づいてシンボルを並べ替えることができます。私たちの経験に基づいて、この方法は、方法1よりも候補をより良くする最大の問題を特定します。

方法3-Templightを使用する

Templightは、テンプレートのインスタンス化の時間とメモリ消費をプロファイルし、インタラクティブなデバッグセッションを実行して、テンプレートのインスタンス化プロセスを内省するためのClangベースのツールです。」

Templightをインストールするには、LLVMとClang(手順)をチェックアウトし、Templightパッチを適用します。LLVMとClangのデフォルト設定はデバッグとアサーションにあり、これらはコンパイル時間に大きな影響を与える可能性があります。Templightには両方が必要なようですので、デフォルト設定を使用する必要があります。LLVMとClangのインストールプロセスには、約1時間ほどかかります。

パッチを適用した後templight++、インストール時に指定したビルドフォルダーにあるコードをコンパイルするために使用できます。

それtemplight++がPATHにあることを確認してください。コンパイルするには、次のスイッチをCXXFLAGSMakefileのに追加するか、コマンドラインオプションに追加します。

CXXFLAGS+=-Xtemplight -profiler -Xtemplight -memory -Xtemplight -ignore-system

または

templight++ -Xtemplight -profiler -Xtemplight -memory -Xtemplight -ignore-system

コンパイルが完了すると、同じフォルダーに.trace.memory.pbfと.trace.pbfが生成されます。これらのトレースを視覚化するには、これらを他の形式に変換できるTemplightツールを使用できます。templight-convertをインストールするには、次の手順に従います。通常、callgrind出力を使用します。プロジェクトが小さい場合は、GraphViz出力を使用することもできます。

$ templight-convert --format callgrind YOUR_BINARY --output YOUR_BINARY.trace

$ templight-convert --format graphviz YOUR_BINARY --output YOUR_BINARY.dot

生成されたcallgrindファイルは、kcachegrindを使用して開くことができます。ここでは、最も時間とメモリを消費するインスタンス化を追跡できます。

テンプレートのインスタンス化の数を減らす

テンプレートのインスタンス化の数を減らすための正確な解決策はありませんが、役立ついくつかのガイドラインがあります。

複数のテンプレート引数を使用してクラスをリファクタリングする

たとえば、クラスがある場合、

template <typename T, typename U>
struct foo { };

そして、の両方TU10の異なるオプションを持つことができ、これを解決するために、あなたが100に、このクラスの可能なテンプレートのインスタンス化を増加している一つの方法は、抽象に別のクラスへのコードの共通部分です。もう1つの方法は、継承の逆転(クラス階層の逆転)を使用することですが、この手法を使用する前に、設計目標が損なわれていないことを確認してください。

テンプレート化されていないコードを個々の翻訳単位にリファクタリングする

この手法を使用すると、共通セクションを一度コンパイルして、後で他のTU(翻訳単位)とリンクできます。

externテンプレートのインスタンス化を使用する(C ++ 11以降)

クラスのすべての可能なインスタンス化がわかっている場合は、この手法を使用して、すべてのケースを別の翻訳単位でコンパイルできます。

たとえば、次の場所にあります。

enum class PossibleChoices = {Option1, Option2, Option3}

template <PossibleChoices pc>
struct foo { };

このクラスには3つのインスタンス化が考えられます。

template class foo<PossibleChoices::Option1>;
template class foo<PossibleChoices::Option2>;
template class foo<PossibleChoices::Option3>;

上記を翻訳単位に入れ、ヘッダーファイルのクラス定義の下にexternキーワードを使用します。

extern template class foo<PossibleChoices::Option1>;
extern template class foo<PossibleChoices::Option2>;
extern template class foo<PossibleChoices::Option3>;

この手法は、インスタンス化の共通セットを使用して異なるテストをコンパイルする場合に時間を節約できます。

注:MPICH2は、この時点での明示的なインスタンス化を無視し、インスタンス化されたクラスを常にすべてのコンパイル単位でコンパイルします。

Unityビルドを使用する

ユニティビルドの全体的な考え方は、使用するすべての.ccファイルを1つのファイルに含め、そのファイルを1回だけコンパイルすることです。この方法を使用すると、さまざまなファイルの共通セクションの再インスタンス化を回避でき、プロジェクトに多くの共通ファイルが含まれている場合は、おそらくディスクアクセスも節約できます。

例として、3つのファイルfoo1.ccfoo2.ccfoo3.ccあり、それらすべてがSTLtupleからインクルードされていると仮定します。次のようなを作成できます。foo-all.cc

#include "foo1.cc"
#include "foo2.cc"
#include "foo3.cc"

このファイルをコンパイルするのは1回だけであり、3つのファイル間の一般的なインスタンス化を減らす可能性があります。一般に、改善が有意であるかどうかを予測することは困難です。しかし、明らかな事実の1つは、ビルドで並列処理が失われることです(3つのファイルを同時にコンパイルすることはできなくなります)。

さらに、これらのファイルのいずれかに大量のメモリが使用されると、コンパイルが完了する前に実際にメモリ不足になる可能性があります。GCCなどの一部のコンパイラでは、メモリ不足のためコンパイラにICE(内部コンパイラエラー)が発生する場合があります。そのため、長所と短所をすべて理解していない限り、この手法を使用しないでください。

プリコンパイル済みヘッダー

プリコンパイル済みヘッダー(PCH)は、ヘッダーファイルをコンパイラーが認識できる中間表現にコンパイルすることにより、コンパイル時間を大幅に節約できます。プリコンパイル済みヘッダーファイルを生成するには、通常のコンパイルコマンドでヘッダーファイルをコンパイルするだけです。たとえば、GCCの場合:

$ g++ YOUR_HEADER.hpp

これは、生成されますYOUR_HEADER.hpp.gch file.gch同じフォルダにGCCでPCHファイルの拡張子です)。これは、YOUR_HEADER.hpp他のファイルにインクルードする場合、コンパイラーは以前に同じフォルダーにではYOUR_HEADER.hpp.gchなく自分を使用することを意味しYOUR_HEADER.hppます。

この手法には2つの問題があります。

  1. プリコンパイルされるヘッダーファイルが安定していて変更されないことを確認する必要があります(makefileはいつでも変更できます)。
  2. (ほとんどのコンパイラでは)コンパイル単位ごとにPCHを1つだけ含めることができます。つまり、プリコンパイルするヘッダーファイルが複数ある場合は、それらを1つのファイルに含める必要があります(例:)all-my-headers.hpp。しかし、それはすべての場所に新しいファイルを含める必要があることを意味します。幸い、GCCにはこの問題の解決策があります。-include新しいヘッダーファイルを使用して提供します。この手法を使用して、異なるファイルをカンマで区切ることができます。

例えば:

g++ foo.cc -include all-my-headers.hpp

名前のない名前空間または匿名の名前空間を使用する

名前なし名前空間(別名匿名名前空間)は、生成されるバイナリサイズを大幅に削減できます。名前空間は内部リンケージを使用しています。つまり、これらの名前空間で生成されたシンボルは、他のTU(翻訳またはコンパイル単位)からは見えません。コンパイラは通常、名前のない名前空間に一意の名前を生成します。これは、ファイルfoo.hppがある場合、

namespace {

template <typename T>
struct foo { };
} // Anonymous namespace
using A = foo<int>;

そして、あなたはたまたまこのファイルを2つのTU(2つの.ccファイルに含め、それらを別々にコンパイル)に含めます。2つのfooテンプレートインスタンスは同じではありません。これは、One Definition Rule(ODR)に違反しています。同じ理由で、名前のない名前空間をヘッダーファイルで使用することはお勧めしません。.ccバイナリファイルにシンボルが表示されないように、ファイルで自由に使用してください。場合によっては、.ccファイルのすべての内部詳細を変更すると、生成されるバイナリサイズが10%減少することがわかりました。

可視性オプションの変更

新しいコンパイラでは、動的共有オブジェクト(DSO)でシンボルを表示または非表示に選択できます。理想的には、可視性を変更すると、コンパイラーのパフォーマンス、リンク時最適化(LTO)、および生成されるバイナリー・サイズが改善される可能性があります。GCCのSTLヘッダーファイルを見ると、広く使用されていることがわかります。可視性の選択を可能にするには、関数ごと、クラスごと、変数ごと、さらに重要なことにはコンパイラごとにコードを変更する必要があります。

可視性の助けを借りて、生成された共有オブジェクトから非公開と見なすシンボルを非表示にすることができます。GCCでは-visibility、コンパイラのオプションにデフォルトまたは非表示を渡すことにより、シンボルの可視性を制御できます。これはある意味で名前のない名前空間に似ていますが、より複雑で煩わしい方法です。

ケースごとの可視性を指定する場合は、関数、変数、およびクラスに次の属性を追加する必要があります。

__attribute__((visibility("default"))) void  foo1() { }
__attribute__((visibility("hidden")))  void  foo2() { }
__attribute__((visibility("hidden")))  class foo3   { };
void foo4() { }

GCCのデフォルトの可視性はデフォルト(パブリック)です。つまり、上記を共有ライブラリ(-shared)メソッドとしてコンパイルするfoo2と、クラスfoo3は他のTU foo1からfoo4は見えなくなります(そして表示されます)。でコンパイルすると-visibility=hiddenfoo1表示されるだけになります。foo4隠されることさえあります。

可視性の詳細については、GCC wikiを参照してください


33

「内なるゲーム、インディーズゲームのデザインとプログラミング」の記事をお勧めします。

確かに、それらはかなり古いものです。現実的な結果を得るには、最新バージョン(または使用可能なバージョン)ですべてを再テストする必要があります。いずれにせよ、それはアイデアの良い情報源です。


17

過去に私にとって非常にうまく機能した1つの手法:複数のC ++ソースファイルを個別にコンパイルせず、次のように他のすべてのファイルを含む1つのC ++ファイルを生成します。

// myproject_all.cpp
// Automatically generated file - don't edit this by hand!
#include "main.cpp"
#include "mainwindow.cpp"
#include "filterdialog.cpp"
#include "database.cpp"

もちろん、これは、ソースのいずれかが変更された場合に、含まれているすべてのソースコードを再コンパイルする必要があることを意味するため、依存関係ツリーは悪化します。ただし、複数のソースファイルを1つの変換単位としてコンパイルすると(少なくとも、MSVCとGCCを使った実験では)高速になり、より小さなバイナリが生成されます。また、コンパイラーには最適化の可能性がより多く与えられると思います(一度により多くのコードを表示できるため)。

この手法はさまざまなケースで機能しません。たとえば、2つ以上のソースファイルが同じ名前のグローバル関数を宣言している場合、コンパイラーはベイルアウトします。私はこのテクニックを他の回答で説明することができなかったので、ここでそれについて言及します。

価値のあることとして、KDEプロジェクトは1999年以来、まったく同じ手法を使用して、最適化されたバイナリを(おそらくリリース用に)構築しました。ビルド構成スクリプトへの切り替えが呼び出されました--enable-final。考古学的な興味から、この機能を発表した投稿を掘り下げました:http : //lists.kde.org/?l=kde-devel&m=92722836009368&w=2


2
本当に同じことかどうかはわかりませんが、VC ++で「プログラム全体の最適化」をオンにすると(msdn.microsoft.com/en-us/library/0zza0de8%28VS.71%29.aspx)、ランタイムパフォーマンスへの影響は、提案されているものと同じです。ただし、コンパイル時間は確実にあなたのアプローチの方が優れています!
Philipp

1
@Frerich:OJの回答で言及されているUnityビルドについて説明しています。バルクビルドやマスタービルドと呼ばれるものも見ました。
idbrii 2012年

では、UBはWPO / LTCGと比べてどうですか?
ポール、2014年

これは、編集、ビルド、テストを繰り返す開発中ではなく、1回限りのコンパイルでのみ役立つ可能性があります。現代の世界では4つのコアが標準ですが、おそらく数年後、コア数は大幅に増えます。コンパイラとリンカーが複数のスレッドを利用できない場合、ファイルのリストは、適切な整数<core-count> + NNある並列にコンパイルされたサブリストに分割される可能性があります(システムメモリとマシンの使用方法によって異なります)。
FooF

15

このトピックに関する本全体は、Large-Scale C ++ Software Design(John Lakos著)です。

この本はテンプレートよりも古いため、その本の内容に「テンプレートを使用するとコンパイラが遅くなる可能性がある」と追加されています。


この本はこの種のトピックでしばしば言及されますが、私にとっては情報はまばらでした。基本的には、可能な限り前方宣言を使用し、依存関係を切り離すことが規定されています。これは、pimplイディオムを使用するとランタイムに欠点があることを除けば明らかです。
gast128

@ gast128そのポイントは、漸進的な再コンパイルを可能にするコーディングイディオムを使用することだと思います。つまり、ソースの少しをどこかに変更した場合、すべてを再コンパイルする必要はありません。
ChrisW

15

私は他の回答にリンクするだけですどうすればコンパイル時間を短縮し、Visual C ++プロジェクト(ネイティブC ++)のリンク時間を短縮できますか?。追加したいもう1つのポイントですが、多くの場合問題が発生するのは、プリコンパイル済みヘッダーを使用することです。ただし、GUIツールキットのヘッダーなど、ほとんど変更されない部分にのみ使用してください。そうでなければ、彼らは最終的にあなたを救うよりもあなたに多くの時間を費やします。

別のオプションは、GNU makeで作業するときに-j<N>オプションをオンにすることです。

  -j [N], --jobs[=N]          Allow N jobs at once; infinite jobs with no arg.

私は3ここにデュアルコアを持っているので、私は通常それを持っています。次に、それらの間の依存関係がなければ、異なる翻訳単位に対してコンパイラーを並行して実行します。すべてのオブジェクトファイルをリンクするリンカプロセスが1つしかないため、リンクを並行して行うことはできません。

しかし、リンカー自体はスレッド化することができ、これがELFリンカーが行うことです。これは最適化されたスレッド化されたC ++コードで、ELFオブジェクトファイルを以前よりもはるかに高速にリンクすると言われています(実際にはbinutilsに含まれていました)。GNU gold ld


はい。検索してもその質問は表示されませんでした。
スコットランガム

申し訳ありませんでした。それはVisual C ++用でした。あなたの質問は、どのコンパイラにも当てはまるようです。それで結構です:)
Johannes Schaub-litb

12

ここに幾つかあります:

  • マルチコンパイルジョブを開始して、すべてのプロセッサコアを使用します(make -j2良い例です)。
  • オフ以下の最適化(例えば、GCCは、はるかに高速であると-O1比べ-O2又は-O3)。
  • プリコンパイル済みヘッダーを使用します。

12
参考までに、コアよりも多くのプロセスを開始する方が通常は速いと思います。たとえば、クアッドコアシステムでは、通常-j4ではなく-j8を使用します。これは、1つのプロセスがI / Oでブロックされると、他のプロセスがコンパイルされる可能性があるためです。
Fooz氏、2008

@MrFooz:i7-2700k(4コア、8スレッド、定数乗数を設定)でLinuxカーネルを(RAMストレージから)コンパイルして数年前にこれをテストしました。私は正確な最良の結果を忘れていますが、あなたが提案したように、-j12周り-j18はに比べてかなり高速-j8でした。メモリ帯域幅が制限要因になる前に、コアをいくつ持つことができるのか疑問に思います...
Mark K Cowan

@MarkKCowanそれは多くの要因に依存します。コンピュータによって、メモリ帯域幅は大きく異なります。最近のハイエンドプロセッサでは、メモリバスを飽和させるために複数のコアが必要です。また、I / OとCPUの間にはバランスがあります。一部のコードはコンパイルが非常に簡単ですが、他のコードは遅くなる可能性があります(たとえば、テンプレートがたくさんある場合)私の現在の経験則は-j、実際のコア数の2倍です。
Fooz氏、2015

11

上記のすべてのコードトリック(前方宣言、パブリックヘッダーへのヘッダーのインクルージョンの最小化、Pimplを使用した実装ファイル内のほとんどの詳細のプッシュ...)を適用し、言語に関して他に何も得られなくなったら、ビルドシステムを検討してください。 。Linuxを使用している場合は、distcc(分散コンパイラ)とccache(キャッシュコンパイラ)の使用を検討してください。

最初の1つであるdistccは、プリプロセッサーのステップをローカルで実行し、ネットワーク内の最初の使用可能なコンパイラーに出力を送信します。ネットワーク内のすべての構成済みノードで同じコンパイラーとライブラリーのバージョンが必要です。

後者のccacheはコンパイラキャッシュです。それは再びプリプロセッサを実行し、そのプリプロセッサフ​​ァイルが同じコンパイラパラメータですでにコンパイルされているかどうかを(ローカルディレクトリに保持されている)内部データベースでチェックします。含まれている場合は、バイナリをポップアップし、コンパイラの最初の実行から出力します。

両方を同時に使用できるため、ccacheにローカルコピーがない場合は、distccを使用してそれをネットを介して別のノードに送信できます。そうでない場合は、それ以上処理せずにソリューションを注入できます。


2
distccが、構成されたすべてのノードで同じライブラリバージョンを必要とするとは思いません。distccはコンパイルをリモートで行うだけで、リンクは行いません。また、前処理されたコードをネットワーク経由で送信するため、リモートシステムで使用可能なヘッダーは重要ではありません。
Frerich Raabe、2012

9

私が大学を卒業したとき、最初に見た実稼働に値するC ++コードには、ヘッダーが定義されているこれらの間の不可解な#ifndef ... #endifディレクティブがありました。これらの包括的なことについてコードを非常に素朴な方法で記述していて、大規模なプログラミングの世界に紹介された人に尋ねました。

要点に戻ると、ヘッダー定義の重複を防ぐためにディレクティブを使用することは、コンパイル時間の短縮に関して最初に学んだことです。


1
古いが金色。時々明白なことは忘れられます。
alcor 14

1
「ガードを含める」
gast128

8

より多くのRAM。

誰かがRAMドライブについて別の答えで話しました。私はこれを80286Turbo C ++(年齢を表示)で行い、結果は驚異的でした。マシンがクラッシュしたときのデータの損失も同様です。


ただし、DOSではメモリを多く使用できません
phuclv '12

6

可能な場合は、前方宣言を使用してください。クラス宣言で型へのポインターまたは参照のみを使用する場合は、それを前方宣言して、型のヘッダーを実装ファイルに含めるだけです。

例えば:

// T.h
class Class2; // Forward declaration

class T {
public:
    void doSomething(Class2 &c2);
private:
    Class2 *m_Class2Ptr;
};

// T.cpp
#include "Class2.h"
void Class2::doSomething(Class2 &c2) {
    // Whatever you want here
}

インクルードが少ないということは、十分に実行すれば、プリプロセッサーの作業がはるかに少ないことを意味します。


これは、同じヘッダーが複数の翻訳単位に含まれている場合にのみ重要ではないですか?翻訳単位が1つしかない場合(テンプレートが使用される場合はよくあることです)、影響がないように見えます。
AlwaysLearning

1
翻訳単位が1つしかない場合は、なぜそれをヘッダーに入れる必要があるのでしょうか。ソースファイルにコンテンツを置くだけの方が理にかなっているのではないでしょうか。ヘッダーの要点は、複数のソースファイルに含まれる可能性が高いということではありませんか?
エヴァンテラン


5

使用する

#pragma once

ヘッダーファイルの上部にあるため、翻訳単位に2回以上含まれている場合、ヘッダーのテキストは1回だけ含まれ、解析されます。


2
広くサポートされていますが、#pragma onceは非標準です。en.wikipedia.org/wiki/Pragma_onceを
ChrisInEdmonton 2008

7
そして最近では、通常のインクルードガードが同じ効果を持っています。それらがファイルの先頭にある限り、コンパイラーはそれらを#pragma onceとして完全に処理できます
jalf


4
  • コンピューターをアップグレードする

    1. クアッドコア(またはデュアルクアッドシステム)を入手する
    2. 大量のRAMを取得します。
    3. RAMドライブを使用して、ファイルI / O遅延を大幅に削減します。(ハードドライブのように機能するIDEおよびSATA RAMドライブを製造する会社があります)。
  • 次に、他のすべての典型的な提案があります

    1. 可能な場合は、プリコンパイル済みヘッダーを使用します。
    2. プロジェクトのパーツ間の結合の量を減らします。通常、1つのヘッダーファイルを変更しても、プロジェクト全体を再コンパイルする必要はありません。

4

RAMドライブの使用について考えました。結局のところ、私のプロジェクトではそれほど大きな違いはないことがわかりました。しかし、それらはまだかなり小さいです。それを試してみてください!どれだけ役に立ったか聞いてみたいと思います。


ええと。なぜ誰かがこれに反対票を投じたのですか?明日やってみます。
スコットランガム、

1
反対投票が大きな違いを生むことはないからです。未使用のRAMが十分にある場合、OSはそれをディスクキャッシュとしてインテリジェントに使用します。
MSalters 2010

1
@MSalters-そして、どのくらい「十分」でしょうか?私は理論であることを知っているが、RAMドライブを使用して、いくつかの理由ではない、実際に大きな弾みをつけます。図を行く...
Vilx-

1
プロジェクトをコンパイルし、入力ファイルと一時ファイルをキャッシュするのに十分です。明らかに、GBのサイズはプロジェクトのサイズに直接依存します。古いOS(特にWinXP)では、ファイルキャッシュは非常に遅延しており、RAMは未使用のままでした。
MSalters 2010

ファイルがすでに大量の低速IOを最初に実行するのではなく、RAMにある場合、RAMドライブの方が確かに高速です。(変更されたファイルのライズリピート-ディスクなどに書き戻します)。
ポール、2013年

3

動的リンク(.so)は、静的リンク(.a)よりもはるかに高速です。特に、低速のネットワークドライブがある場合。これは、処理と書き出しが必要なすべてのコードが.aファイルにあるためです。さらに、はるかに大きな実行可能ファイルをディスクに書き出す必要があります。


ダイナミックリンクにより、多くの種類のリンク時の最適化が妨げられるため、多くの場合、出力が遅くなる可能性があります
phuclv

3

コンパイル時間ではなく、ビルド時間について:

  • ビルドファイルで作業しているときに同じファイルを再構築する必要がある場合は、ccacheを使用します

  • makeの代わりにninja-buildを使用してください。私は現在〜100個のソースファイルを使用してプロジェクトをコンパイルしています。忍者は5分、忍者は1未満にする必要があります。

でcmakeから忍者ファイルを生成できます-GNinja


3

あなたはどこで時間を過ごしていますか?CPUバウンドですか?メモリバウンド?ディスクバインド?より多くのコアを使用できますか?より多くのRAM?RAIDが必要ですか?現在のシステムの効率を向上させたいだけですか?

gcc / g ++の下で、ccacheを見ましたか?あなたがmake clean; makeたくさんやっているなら、それは役に立ちます。


2

より高速なハードディスク。

コンパイラは、多くの(場合によっては巨大な)ファイルをディスクに書き込みます。一般的なハードディスクの代わりにSSDを使用すると、コンパイル時間が大幅に短縮されます。



2

シークの待ち時間が長いため、ネットワーク共有はビルドを大幅に遅くします。Boostなどの場合、ネットワーク共有ドライブはかなり高速ですが、それは私にとって大きな違いをもたらしました。おもちゃのブーストプログラムをコンパイルする時間は、ネットワーク共有からローカルSSDに切り替えたときに、約1分から1秒になりました。


2

マルチコアプロセッサを使用している場合、Visual Studio(2005以降)とGCCの両方がマルチプロセッサコンパイルをサポートします。あなたがハードウェアを持っているなら、確かにそれは有効になるものです。


2
@Fellman、他の回答のいくつかをご覧ください--j#オプションを使用してください。
ストレッジャー2008

1

「テクニック」ではありませんが、多くのソースファイルを含むWin32プロジェクトが、「Hello World」の空のプロジェクトよりも速くコンパイルされる方法を理解できませんでした。したがって、これが私のような人に役立つことを願っています。

Visual Studioでは、コンパイル時間を増やす1つのオプションはインクリメンタルリンク(/ INCREMENTAL)です。リンク時コード生成(/ LTCG)と互換性がないため、リリースビルドを実行するときはインクリメンタルリンクを無効にしてください。


1
リンク時のコード生成を無効にすることは多くの最適化を無効にするため、良い提案ではありません。/INCREMENTALデバッグモードでのみ有効にする必要があります
phuclv '20

1

Visual Studio 2017以降、何が時間がかかるかについてのいくつかのコンパイラーメトリックを持つことができます。

これらのパラメーターをプロジェクトプロパティウィンドウのC / C ++->コマンドライン(追加オプション)に追加します。 /Bt+ /d2cgsummary /d1reportTime

あなたはこの投稿でより多くの情報持つことができます。


0

静的リンクの代わりに動的リンクを使用すると、コンパイラーをより高速に感じることができます。

Cmakeを使用する場合は、プロパティをアクティブにします。

set(BUILD_SHARED_LIBS ON)

静的リンクを使用してリリースをビルドすると、さらに最適化できます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.