C++ファイルのコンパイルは、C#やJavaと比較すると非常に時間がかかります。通常のサイズのPythonスクリプトを実行するよりも、C++ファイルをコンパイルするのにかなり時間がかかります。現在、VC++を使用していますが、どのコンパイラでも同じです。どうしてこれなの?
私が考えることができる2つの理由は、ヘッダーファイルをロードしてプリプロセッサを実行することでしたが、それがなぜそんなに時間がかかるのかを説明すべきではないようです。
いくつかの理由
すべてのコンパイル単位には、(1)ロードおよび(2)コンパイルするために数百または数千ものヘッダーが必要です。プリプロセッサは、ヘッダー(might (マクロは、ヘッダーの内容を変更する1つのコンパイル単位で定義できます)。
これはおそらくthe主な理由です。これは、すべてのコンパイル単位で膨大な量のコードをコンパイルする必要があり、さらに、すべてのヘッダーを複数回コンパイルする必要があるためです(それを含むすべてのコンパイル単位で1回) 。
コンパイルしたら、すべてのオブジェクトファイルをリンクする必要があります。これは基本的にモノリシックなプロセスであり、あまり並列化できず、プロジェクト全体を処理する必要があります。
構文は解析が非常に複雑で、コンテキストに大きく依存しており、明確にするのは非常に困難です。これには多くの時間がかかります。
C#では、プログラムでListのインスタンスをいくつ作成しても、List<T>
だけがコンパイルされます。 C++では、vector<int>
はvector<float>
とは完全に別の型であり、それぞれを個別にコンパイルする必要があります。
これに加えて、テンプレートはコンパイラが解釈しなければならない完全なチューリング完全な「サブ言語」を構成し、これは途方もなく複雑になる可能性があります。比較的単純なテンプレートメタプログラミングコードでさえ、多数のテンプレートのインスタンス化を作成する再帰的なテンプレートを定義できます。テンプレートは、非常に複雑なタイプになり、とんでもないほど長い名前になり、リンカーに多くの余分な作業を追加します。 (多くのシンボル名を比較する必要があり、これらの名前が何千もの文字に成長する可能性がある場合、かなり高価になる可能性があります)。
そしてもちろん、テンプレートは一般にヘッダーで定義する必要があるため、ヘッダーファイルに関する問題を悪化させます。つまり、コンパイル単位ごとにはるかに多くのコードを解析およびコンパイルする必要があります。通常のCコードでは、ヘッダーには通常、前方宣言のみが含まれますが、実際のコードはほとんどありません。 C++では、ほとんどすべてのコードがヘッダーファイルに存在することは珍しくありません。
C++では、非常に劇的な最適化が可能です。 C#またはJavaでは、クラスを完全に削除することはできません(リフレクションの目的で存在する必要があります)が、単純なC++テンプレートメタプログラムでさえ、数十または数百のクラスを簡単に生成でき、それらはすべてインライン化され、最適化で再び削除されます段階。
さらに、C++プログラムはコンパイラーによって完全に最適化される必要があります。 C#プログラムは、ロード時に追加の最適化を実行するためにJITコンパイラに依存できますが、C++はそのような「二度目のチャンス」を取得しません。コンパイラーが生成するものは、得られる限り最適化されています。
C++は、バイトコードJavaや.Net Use(特にx86の場合)よりもやや複雑なマシンコードにコンパイルされます。 (これは、コメントなどで言及されているために完全性から言及されています。実際には、このステップは総コンパイル時間のほんの一部以上を要することはほとんどありません)。
これらの要因のほとんどは、実際にかなり効率的にコンパイルされるCコードによって共有されます。解析ステップはC++でははるかに複雑であり、かなり時間がかかる可能性がありますが、主な違反者はおそらくテンプレートです。これらは便利であり、C++をはるかに強力な言語にしますが、コンパイル速度の点でも負担がかかります。
スローダウンは、どのコンパイラでも必ずしも同じではありません。
DelphiやKylixを使用したことはありませんが、MS-DOS時代にはTurbo Pascalプログラムがほぼ瞬時にコンパイルされ、同等のTurbo C++プログラムはクロールするだけでした。
2つの主な違いは、非常に強力なモジュールシステムと、シングルパスコンパイルが可能な構文です。
コンパイル速度がC++コンパイラ開発者にとって優先事項ではなかった可能性は確かにありますが、C/C++構文には、処理が難しくなる固有の複雑さもいくつかあります。 (私はCの専門家ではありませんが、Walter Brightは、さまざまな商用C/C++コンパイラを構築した後、D言語を作成しました。 彼の変更の1つ コンテキストフリーの文法を実施して、言語の解析を容易にしました。)
また、一般的にMakefileはすべてのファイルがCで個別にコンパイルされるように設定されているため、10個のソースファイルがすべて同じインクルードファイルを使用する場合、そのインクルードファイルは10回処理されます。
解析とコード生成は実際にはかなり高速です。本当の問題は、ファイルを開いたり閉じたりすることです。インクルードガードを使用した場合でも、コンパイラーは.Hファイルを開いたまま、各行を読み取ります(そして無視します)。
ある友人が(仕事で退屈している間)彼の会社のアプリケーションを取り、すべてのソースファイルとヘッダーファイルをすべて1つの大きなファイルに入れました。コンパイル時間が3時間から7分に短縮されました。
C++はマシンコードにコンパイルされます。プリプロセッサ、コンパイラ、オプティマイザ、最後にアセンブラがあり、すべて実行する必要があります。
JavaとC#はバイトコード/ ILにコンパイルされ、Java仮想マシン/.NET Frameworkは実行前に実行されます(またはJITがマシンコードにコンパイルされます)。
Pythonは、バイトコードにコンパイルされるインタープリター言語です。
これには他の理由もあると確信していますが、一般に、ネイティブのマシン言語にコンパイルする必要がないため、時間を節約できます。
別の理由は、宣言を見つけるためのCプリプロセッサの使用です。ヘッダーガードを使用しても、.hが含まれるたびに何度も解析する必要があります。一部のコンパイラは、これに役立つプリコンパイル済みヘッダーをサポートしていますが、常に使用されるとは限りません。
参照: C++よくある質問と回答
最大の問題は次のとおりです。
1)無限ヘッダーの再解析。すでに述べた。緩和策(#pragma onceなど)は通常、ビルドごとではなくコンパイル単位ごとにのみ機能します。
2)ツールチェーンが多くの場合、複数のバイナリ(極端な場合はmake、preprocessor、compiler、assembler、archiver、impdef、linker、およびdlltool)に分割されているという事実コンパイラー、アセンブラー)またはファイルのすべてのカップル(アーカイバー、リンカー、およびdlltool)。
Comp.compilersのこの議論も参照してください。 http://compilers.iecc.com/comparch/article/03-11-078 特にこれ:
http://compilers.iecc.com/comparch/article/02-07-128
Comp.compilersのモデレーターであるJohnは同意するようです。これは、ツールチェーンを完全に統合し、プリコンパイル済みヘッダーを実装する場合、Cでも同様の速度を達成できることを意味することに注意してください。多くの商用Cコンパイラはある程度これを行います。
すべてを個別のバイナリに分解するUnixモデルは、Windowsの最悪のモデルの一種です(プロセスの作成が遅い)。特にmake/configureシステムが情報を取得するためだけにいくつかのプログラムを呼び出す場合は、Windowsと* nixの間でGCCビルド時間を比較するときに非常に注目に値します。
C/C++の構築:実際に何が起こるのか、なぜそんなに時間がかかるのか
ソフトウェア開発時間の比較的大部分は、コードの作成、実行、デバッグ、さらには設計に費やされず、コンパイルが完了するのを待っています。物事を速くするためには、まずC/C++ソフトウェアがコンパイルされたときに何が起こっているのかを理解する必要があります。手順はおおよそ次のとおりです。
ここで、各ステップをより速くする方法に焦点を当てて、各ステップをより詳しく見ていきます。
設定
これは、ビルドを開始する最初のステップです。通常、構成スクリプトまたはCMake、Gyp、SCons、またはその他のツールを実行することを意味します。非常に大きなAutotoolsベースの構成スクリプトの場合、これには1秒から数分かかります。
このステップは比較的まれにしか発生しません。構成の変更時またはビルド構成の変更時にのみ実行する必要があります。ビルドシステムを変更する以外は、このステップを高速化するために行うことはあまりありません。
ビルドツールの起動
これは、makeを実行するか、IDE(通常はmakeのエイリアス)のビルドアイコンをクリックすると発生します。ビルドツールバイナリが起動し、その構成ファイルとビルド構成を読み取ります。これらは通常は同じものです。
ビルドの複雑さとサイズに応じて、これには数分の一秒から数秒かかることがあります。これ自体はそれほど悪くないでしょう。残念ながら、ほとんどのmakeベースのビルドシステムでは、makeが1つのビルドごとに数十回から数百回呼び出されます。通常、これはmakeの再帰的な使用が原因です(これは悪いことです)。
Makeが非常に遅い理由は、実装のバグではないことに注意してください。 Makefileの構文には、非常に高速な実装をほとんど不可能にするいくつかの癖があります。この問題は、次のステップと組み合わせるとさらに顕著になります。
依存性チェック
ビルドツールがその構成を読み取った後、どのファイルが変更され、どのファイルを再コンパイルする必要があるかを判断する必要があります。構成ファイルには、ビルドの依存関係を説明する有向非循環グラフが含まれています。このグラフは通常、構成ステップ中に作成されます。ビルドツールの起動時間と依存関係スキャナーは、ビルドごとに実行されます。組み合わせたランタイムは、編集-コンパイル-デバッグサイクルの下限を決定します。小さなプロジェクトの場合、この時間は通常数秒程度です。これは許容範囲です。 Makeに代わるものがあります。最速のものは忍者です。これは、Chromium向けにGoogleのエンジニアが作成したものです。 CMakeまたはGypを使用してビルドする場合は、Ninjaバックエンドに切り替えてください。ビルドファイル自体を変更する必要はありません。スピードブーストをお楽しみください。ただし、ほとんどのディストリビューションではNinjaはパッケージ化されていないため、自分でインストールする必要があります。
コンパイル
この時点で、最終的にコンパイラを呼び出します。いくつかのコーナーをカットして、おおよその手順を以下に示します。
一般的な信念に反して、C++のコンパイルは実際にはそれほど遅くありません。 STLは低速で、C++のコンパイルに使用されるほとんどのビルドツールは低速です。ただし、言語の遅い部分を緩和するためのより高速なツールと方法があります。
それらを使用するには、多少のエルボグリスが必要ですが、メリットは否定できません。ビルド時間の短縮は、開発者の満足度、俊敏性の向上、そして最終的にはコードの改善につながります。
コンパイルされた言語は常に、インタープリター言語よりも大きな初期オーバーヘッドを必要とします。さらに、おそらく、C++コードをうまく構築できなかったかもしれません。例えば:
#include "BigClass.h"
class SmallClass
{
BigClass m_bigClass;
}
以下よりもはるかに遅いコンパイル:
class BigClass;
class SmallClass
{
BigClass* m_bigClass;
}
大規模なC++プロジェクトでコンパイル時間を短縮する簡単な方法は、プロジェクトのすべてのcppファイルを含む* .cppインクルードファイルを作成してコンパイルすることです。これにより、ヘッダーの爆発の問題が1回に減ります。これの利点は、コンパイルエラーが引き続き正しいファイルを参照することです。
たとえば、a.cpp、b.cpp、c.cppがあるとします。ファイルを作成します:everything.cpp:
#include "a.cpp"
#include "b.cpp"
#include "c.cpp"
次に、すべてのものを作成してプロジェクトをコンパイルします。cpp
いくつかの理由は次のとおりです。
1)C++の文法はC#やJavaよりも複雑で、解析に時間がかかります。
2)(より重要)C++コンパイラーはマシンコードを生成し、コンパイル中にすべての最適化を行います。 C#とJavaは半分だけ進んで、これらの手順をJITに任せます。
あなたが得ているトレードオフは、プログラムがほんの少し速く走るということです。これは開発中は冷たく感じるかもしれませんが、開発が完了し、プログラムがユーザーによって実行されていると、非常に大きな問題になる可能性があります。
ほとんどの答えは、C++ではコンパイル時に1回しか実行されないアクションの実行コストのためにC#の実行が常に遅くなることに言及して少し不明瞭です。このパフォーマンスコストはランタイム依存性によっても影響を受けますC#プログラムは常により高いメモリフットプリントを持ち、パフォーマンスは利用可能なハードウェアの機能により密接に関連することは言うまでもありません。 VMに解釈されるか、VMに依存する他の言語についても同様です。
C++でのプログラムのコンパイル速度に影響を与える可能性があると考えられる2つの問題があります。
可能性のある問題#1-ヘッダーのコンパイル:(これは、別の回答またはコメントで既に対処されている場合も、されていない場合もあります。)Microsoft Visual C++(A.K.A. VC++)はプリコンパイル済みヘッダーをサポートしています。新しいプロジェクトを作成し、作成しているプログラムの種類を選択すると、セットアップウィザードウィンドウが画面に表示されます。下部にある[次へ]ボタンをクリックすると、ウィンドウが表示され、いくつかの機能のリストがあるページに移動します。 [プリコンパイル済みヘッダー]オプションの横にあるチェックボックスがオンになっていることを確認してください。 (注:これはC++のWin32コンソールアプリケーションでの私の経験でしたが、C++のすべての種類のプログラムではそうではないかもしれません。)
可能性のある問題#2-配置場所:この夏、私はプログラミングコースを受講し、使用しているラボのコンピューターとして、すべてのプロジェクトを8GBフラッシュドライブに保存する必要がありました。毎晩真夜中に拭かれてしまい、すべての作業が消去されてしまいます。移植性/セキュリティなどのために外部ストレージデバイスにコンパイルする場合、非常に長い時間がかかることがあります(プリコンパイルされた場合でも上記のヘッダー)、特にかなり大きなプログラムの場合、プログラムをコンパイルします。この場合のあなたへの私のアドバイスは、使用しているコンピューターのハードドライブ上でプログラムを作成してコンパイルし、何らかの理由でプロジェクトの作業を停止したい/必要なときはいつでも、それらを外部に転送することですストレージデバイス]をクリックし、[ハードウェアの安全な取り外しとメディアの取り出し]アイコンをクリックします。アイコンは、小さな緑色の円の後ろに白いチェックマークが付いた小さなフラッシュドライブとして表示されます。
これがあなたのお役に立てば幸いです。もしそうなら教えてください! :)
既にコメントしたように、コンパイラはテンプレートのインスタンス化と再インスタンス化に多くの時間を費やします。その特定の項目に焦点を当てるプロジェクトがあり、いくつかの本当に有利なケースで観察可能な30倍のスピードアップを主張するような拡張まで。 http://www.zapcc.com を参照してください。