Goはどのようにすばやくコンパイルするのですか?


216

私はGodを使ってGoのウェブサイトをあちこち調べましたが、Goの異常なビルド時間の説明を見つけることができません。それらは言語機能(またはその欠如)の製品、高度に最適化されたコンパイラ、または何か他のものですか?私は囲碁を宣伝するつもりはありません。気になるだけです。


12
@サポート、私はそれを知っています。目立った速さでコンパイルできるようにコンパイラーを実装することは、時期尚早な最適化ではないでしょう。おそらく、それは優れたソフトウェア設計と開発プラクティスの結果を表しています。また、Knuthの言葉が文脈から外れて誤って適用されるのを見るのも我慢できません。
アダムクロスランド

55
この質問の悲観論者のバージョンは、「なぜC ++のコンパイルが遅いのですか?」です。stackoverflow.com/questions/588884/...
dan04

14
この質問は意見に基づくものではないため、私はこの質問を再開することを投票しました。コンパイル速度を向上させる言語および/またはコンパイラーの選択について、技術的な(意見のない)優れた概要を示すことができます。
Martin Tournoij 2016年

小さなプロジェクトの場合、Goは私には遅いようです。これは、おそらく何千倍も遅いコンピュータでは、Turbo-Pascalがはるかに速いことを覚えているからです。prog21.dadgum.com/47.html?repost=true。「go build」と入力するたびに、数秒間何も起こらないので、無愛想な古いFortranコンパイラーとパンチカードに思いを巡らせます。YMMV。TLDR:「遅い」と「速い」は相対的な用語です。
RedGrittyBrick

より詳細な洞察については、dave.cheney.net / 2014/06/07 / five
Karthik

回答:


192

依存関係分析。

ゴーよくある質問は、次の文を格納するために使用しました:

Goは、依存関係の分析を容易にし、Cスタイルのインクルードファイルとライブラリのオーバーヘッドの多くを回避するソフトウェア構築のモデルを提供します。

このフレーズはFAQにはもう含まれていませんが、このトピックはGo at Googleのトークで詳しく説明されており、C / C ++とGoの依存関係分析アプローチを比較しています。

これが高速コンパイルの主な理由です。これは仕様によるものです。


このフレーズはGo FAQにはもう含まれていませんが、C / C ++とPascal / Modula / Goのアプローチを比較する「依存関係分析」トピックの詳細な説明は、Go at Google
rob74

76

Goコンパイラが高速であるというのではなく、他のコンパイラが低速であると思います。

CおよびC ++コンパイラは、大量のヘッダーを解析する必要があります。たとえば、C ++の「hello world」をコンパイルするには、18万行のコードをコンパイルする必要があります。これは、ソースのほぼ半メガバイトです。

$ cpp hello.cpp | wc
  18364   40513  433334

JavaとC#コンパイラはVMで実行されます。つまり、何かをコンパイルする前に、オペレーティングシステムはVM全体をロードする必要があり、バイトコードからネイティブコードにJITコンパイルする必要があります。そのすべてに時間がかかります。

コンパイルの速度はいくつかの要因に依存します。

一部の言語は高速にコンパイルされるように設計されています。たとえば、Pascalはシングルパスコンパイラを使用してコンパイルするように設計されています。

コンパイラ自体も最適化できます。たとえば、Turbo Pascalコンパイラは手動で最適化されたアセンブラで記述されており、言語設計と組み合わされて、286クラスのハードウェアで動作する非常に高速なコンパイラをもたらしました。今でも、最新のPascalコンパイラー(FreePascalなど)はGoコンパイラーよりも高速だと思います。


19
MicrosoftのC#コンパイラはVMでは実行されません。それは主にパフォーマンス上の理由から、まだC ++で書かれています。
blucz、

19
Turbo Pascal以降のDelphiは、非常に高速なコンパイラの最良の例です。両方のアーキテクトがマイクロソフトに移行した後、MSコンパイラと言語の両方で大幅な改善が見られました。それは偶然の偶然ではありません。
TheBlastOne 2011

7
コードの18k行(正確には18364行)は433334バイト(〜0,5MB)
el.pescado

9
C#コンパイラーは、2011年以降C#でコンパイルされています。誰かがこれを後で読む場合に備えて、単なるアップデートです。
Kurt Koller、2015年

3
ただし、C#コンパイラと、生成されたMSILを実行するCLRは異なります。CLRがC#で記述されていないことは間違いありません。
jocull 2016年

39

GoコンパイラがほとんどのC / C ++コンパイラよりもはるかに高速である理由はいくつかあります。

  • 主な理由:ほとんどのC / C ++コンパイラは、(コンパイル速度の観点から)非常に悪いデザインを示しています。また、コンパイル速度の観点から、C / C ++エコシステムの一部(プログラマーがコードを記述しているエディターなど)は、コンパイル速度を考慮して設計されていません。

  • 一番の理由:GoコンパイラとGo言語では、高速なコンパイル速度が意識された選択でした

  • Goコンパイラーには、C / C ++コンパイラーよりも単純なオプティマイザーがあります。

  • C ++とは異なり、Goにはテンプレートもインライン関数もありません。これは、Goがテンプレートや関数のインスタンス化を実行する必要がないことを意味します。

  • Goコンパイラは低レベルのアセンブリコードをより早く生成し、オプティマイザはアセンブリコードを処理しますが、一般的なC / C ++コンパイラでは、最適化パスは元のソースコードの内部表現を処理します。C / C ++コンパイラの余分なオーバーヘッドは、内部表現を生成する必要があるという事実から生じます。

  • Goコンパイラーは使用されているすべてのアセンブリコードを通過し、C / C ++の他の追加アクションも実行しているため、Goプログラムの最終リンク(5l / 6l / 8l)は、C / C ++プログラムのリンクよりも遅くなる可能性があります。リンカーはやっていない

  • 一部のC / C ++コンパイラー(GCC)は(アセンブラーに渡される)テキスト形式で命令を生成しますが、Goコンパイラーはバイナリー形式で命令を生成します。テキストをバイナリに変換するために、追加の作業(多くは必要ありません)を実行する必要があります。

  • Goコンパイラは少数のCPUアーキテクチャのみをターゲットにしていますが、GCCコンパイラは多数のCPUをターゲットにしています

  • Jikesなどの高いコンパイル速度を目標に設計されたコンパイラは高速です。2GHzのCPUでは、Jikesは毎秒20000行以上のJavaコードをコンパイルできます(コンパイルのインクリメンタルモードはさらに効率的です)。


17
Goのコンパイラは小さな関数をインライン化します。少数のCPUをターゲットにすると速度がどのように遅くなるかわかりません... x86用にコンパイルしている間、gccがPPCコードを生成していないと思います。
ブラッドフィッツパトリック

@BradFitzpatrickは古いコメントを復活させるのが嫌いですが、より少数のプラットフォームを対象とすることで、コンパイラの開発者はそれぞれのコメントを最適化するためにより多くの時間を費やすことができます。
永続性

中間形式を使用すると、新しいアーキテクチャごとに新しいバックエンドを作成するだけで済むため、より多くのアーキテクチャをサポートできます
phuclv

34

コンパイル効率は主要な設計目標でした。

最後に、高速であることを目的としています。単一のコンピューターで大きな実行可能ファイルをビルドするには、最大で数秒かかるはずです。これらの目標を達成するには、多くの言語学的問題に対処する必要があります。同時実行とガベージコレクション。厳密な依存関係の指定; 等々。よくある質問

言語のFAQは、解析に関連する特定の言語機能に関して非常に興味深いものです。

第2に、言語は分析しやすく、シンボルテーブルなしで解析できるように設計されています。


6
それは真実ではない。シンボルテーブルがないと、Goソースコードを完全に解析できません。

12
ガベージコレクションがコンパイル時間を向上させる理由もわかりません。そうではありません。
TheBlastOne 2011

3
これらはFAQからの引用です:golang.org/doc/go_faq.html彼らが目標を達成できなかったのか(シンボルテーブル)、ロジックが間違っているのか(GC)はわかりません。
Larry OBrien

5
@FUZxxlに移動golang.org/ref/spec#Primary_expressionsとは、2つの配列【オペランド、コール]と[変換]を検討してください。例Goソースコード:identifier1(identifier2)。シンボルテーブルがなければ、この例が呼び出しであるか変換であるかを判断することは不可能です。| どの言語も、シンボルテーブルなしである程度解析できます。Goソースコードのほとんどの部分がシンボルテーブルなしで解析できることは事実ですが、golang仕様で定義されているすべての文法要素を認識できるとは限りません。

3
@Atomパーサーがエラーを報告するコードにならないようにするために、一生懸命に取り組んでいます。パーサーは通常、首尾一貫したエラーメッセージを報告するという貧弱な仕事をします。ここでaTypeは、変数参照であるかのように式の解析ツリーを作成し、後で意味分析フェーズで、その時点で意味のあるエラーを出力していないことが判明した場合を作成します。
Sam Harwell 14年

26

上記のほとんどは真実ですが、実際には言及しなかった非常に重要なポイントが1つあります。それは、依存関係の管理です。

Goには、直接インポートしているパッケージを含めるだけで済みます(必要なものをすでにインポートしているため)。これは、C / C ++とはまったく対照的です各ファイルには、xヘッダー(yヘッダーなどを含む)が含まれ始めます。結果:Goのコンパイルには、C / C ++が指数関数的な時間をとるインポートされたパッケージの数に対して線形時間がかかります。


22

コンパイラーの翻訳効率の良いテストは、自己コンパイルです。特定のコンパイラーがそれ自体をコンパイルするのにどのくらい時間がかかりますか?C ++の場合、非常に長い時間がかかります(時間?)。比較すると、Pascal / Modula-2 / Oberonコンパイラは、最新のマシンで1秒未満でコンパイルされます[1]。

Goはこれらの言語に触発されてきましたが、この効率の主な理由には次のようなものがあります。

  1. 効率的にスキャンして解析するために、数学的に正しい明確に定義された構文。

  2. C / C ++のような独立したコンパイルとは対照的に、ヘッダーファイルの不必要な再読み取りと他のモジュールの再コンパイルを回避するために、モジュールの境界を越え依存関係と型チェック伴う個別のコンパイル使用する、タイプセーフで静的にコンパイルされた言語そのようなクロスモジュールチェックはコンパイラーによって実行されません(したがって、単純な1行の「hello world」プログラムの場合でも、すべてのヘッダーファイルを何度も繰り返し読み取る必要があります)。

  3. 効率的なコンパイラーの実装(例:シングルパス、再帰下降トップダウン解析)-もちろん、これは上記の1と2の点で非常に役立ちます。

これらの原則は1970年代と1980年代にMesa、Ada、Modula-2 / Oberonなどの言語ですでに知られ、完全に実装されており、Go(Google)のような現代の言語への道を模索しているのは(2010年代)だけです。 、Swift(Apple)、C#(Microsoft)、その他いくつか。

これがすぐに標準になり、例外ではなくなることを期待しましょう。そこにたどり着くには、2つのことが起こる必要があります。

  1. まず、Google、Microsoft、Appleなどのソフトウェアプラットフォームプロバイダーは、既存のコードベースを再利用できるようにする一方で、アプリケーション開発者に新しいコンパイル方法を使用するように働きかける必要があります。これは現在AppleがSwiftプログラミング言語で試みていることであり、Objective-Cと共存できます(同じランタイム環境を使用しているため)。

  2. 第2に、基盤となるソフトウェアプラットフォーム自体は、これらの原則を使用して時間の経過とともに最終的に書き換えると同時に、プロセスのモジュール階層を再設計して、モノリシック性を低くする必要があります。これはもちろん巨大なタスクであり、10年のかなりの部分を占める可能性があります(実際にそれを実行するのに十分な勇気がある場合-Googleの場合はまったくわかりません)。

いずれにせよ、言語の採用を推進するのはプラットフォームであり、その逆ではありません。

参照:

[1] http://www.inf.ethz.ch/personal/wirth/ProjectOberon/PO.System.pdf、6ページ:「コンパイラは約3秒でコンパイルされます」。この見積もりは、25 MHzのクロック周波数で動作し、1 MByteのメインメモリを備えた低コストのザイリンクスSpartan-3 FPGA開発ボードを対象としています。これから、1 GHzをはるかに超えるクロック周波数と数ギガバイトのメインメモリ(つまり、ザイリンクスSpartan-3 FPGAボードよりも数桁強力)で動作する最新のプロセッサの「1秒未満」に簡単に推定できます。 I / O速度を考慮に入れる場合でも同様です。すでに1990年に、Oberonが2〜4 Mバイトのメインメモリを備えた25MHz NS32X32プロセッサで実行されたとき、コンパイラはわずか数秒でコンパイルされました。実際に待つという概念コンパイラがコンパイルサイクルを終了することは、当時のOberonプログラマにはまったく知られていませんでした。通常のプログラムでは、コンパイラーがトリガーされたコンパイルを完了するのを待つよりも、コンパイルコマンドをトリガーしたマウスボタンから指を離すのに常により多くの時間がかかりました。待ち時間がほぼゼロで、それは本当に即座の満足感でした。そして、生成されたコードの品質は、その時点で利用可能な最高のコンパイラーと必ずしも完全に同じであるとは限りませんが、ほとんどのタスクで著しく良好であり、一般的にはかなり受け入れられました。


1
Pascal / Modula-2 / Oberon / Oberon-2コンパイラは、最新のマシンで1秒未満でコンパイルされます [引用が必要]
CoffeeandCode

1
引用が追加されました。参照[1]を参照してください。
Andreas

1
「...原則... Go(Google)、Swift(Apple)のような現代の言語への道を見つける」Swiftがどのようにしてそのリストに入ったかはわかりません。Swiftコンパイラーは氷河的です。最近のCocoaHeadsベルリンの会合で、誰かが中規模のフレームワークにいくつかの数値を提供し、毎秒16 LOCに達しました。
mpw 2017

13

Goは高速になるように設計されており、それが示しています。

  1. 依存関係の管理:ヘッダーファイルはありません。直接インポートされるパッケージを確認するだけで(インポート対象を気にする必要はありません)、線形の依存関係があります。
  2. 文法:言語の文法は単純なので、簡単に解析できます。機能の数は減りますが、コンパイラコード自体はタイトです(パスが少ない)。
  3. オーバーロードは許可されません。シンボルが表示され、それがどのメソッドを参照しているかがわかります。
  4. 各パッケージを個別にコンパイルできるため、Goを並行してコンパイルすることは簡単です。

GOがそのような機能を備えた唯一の言語ではないことに注意してください(モジュールは現代の言語では標準です)。


ポイント(4)は完全に真ではありません。相互に依存するモジュールは、モジュール間のインライン化などを可能にするために、依存関係の順にコンパイルする必要があります。
fuz

1
@FUZxxl:これは最適化段階にのみ関係しますが、バックエンドIR生成まで完全な並列処理を行うことができます。したがって、モジュール間最適化のみが関係します。これはリンク段階で行うことができ、リンクはいずれにしても並列ではありません。もちろん、作業を複製(再解析)したくない場合は、「格子」の方法でコンパイルすることをお勧めします。1/依存関係のないモジュール、2 /モジュールは(1)にのみ依存する、3 /モジュール(1)と(2)にのみ依存します、...
Matthieu M.

2
これは、Makefileなどの基本的なユーティリティを使用して簡単に実行できます。
fuz

12

Alan DonovanとBrian Kernighanによる本「The Go Programming Language」からの引用:

Goコンパイルは、ゼロからビルドする場合でも、他のほとんどのコンパイル済み言語よりも著しく高速です。コンパイラの速度には3つの主な理由があります。まず、すべてのインポートを各ソースファイルの先頭に明示的にリストする必要があるため、コンパイラはファイル全体を読み取って処理し、その依存関係を判断する必要がありません。第2に、パッケージの依存関係は有向非循環グラフを形成し、循環がないため、パッケージを個別に、おそらく並列にコンパイルできます。最後に、コンパイルされたGoパッケージのオブジェクトファイルは、パッケージ自体だけでなく、その依存関係のエクスポート情報も記録します。パッケージをコンパイルするとき、コンパイラーはインポートごとに1つのオブジェクトファイルを読み取る必要がありますが、これらのファイル以外を調べる必要はありません。


9

コンパイルの基本的な考え方は、実際には非常に単純です。再帰下降パーサーは、原則として、I / Oバウンド速度で実行できます。コード生成は基本的に非常に単純なプロセスです。シンボルテーブルと基本型システムは、多くの計算を必要とするものではありません。

ただし、コンパイラの速度を低下させることは難しくありません。

マルチレベルのインクルードディレクティブ、マクロ定義、条件付きコンパイルを使用するプリプロセッサフ​​ェーズがある場合、それらが有用である限り、それをロードするのは難しくありません。(たとえば、WindowsとMFCのヘッダーファイルを考えています。)そのため、プリコンパイル済みヘッダーが必要です。

生成されたコードの最適化に関して、そのフェーズに追加できる処理の量に制限はありません。


7

単純に(私自身の言葉で)、構文が非常に簡単であるため(分析および解析が容易)

たとえば、型の継承がないということは、新しい型が基本型によって課された規則に従っているかどうかを確認するための問題のある分析ではないということです。

たとえば、次のコード例では、「インターフェース」コンパイラーは移動せず、そのタイプの分析中に、意図したタイプが特定のインターフェースを実装しているかどうかをチェックします。使用されるまで(および使用される場合のみ)、チェックが実行されます。

他の例では、コンパイラーは、変数を宣言してそれを使用していないかどうかを通知します(または戻り値を保持することになっていて、そうではない場合)

以下はコンパイルされません:

package main
func main() {
    var a int 
    a = 0
}
notused.go:3: a declared and not used

この種の強制と原則により、結果のコードがより安全になり、コンパイラーはプログラマーが実行できる追加の検証を実行する必要がなくなります。

これらすべての詳細により、言語の解析が容易になり、コンパイルが高速になります。

もう一度、私自身の言葉で。


3

Goはコンパイラーの作成と並行して設計されたと思うので、彼らは誕生以来の親友でした。(IMO)


0
  • Go はすべてのファイルの依存関係を一度インポートするため、プロジェクトのサイズに応じてインポート時間が指数関数的に増加することはありません。
  • 言語学がシンプルになると、解釈に必要な計算量が少なくなります。

ほかに何か?

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