Bagaimana cara Go kompilasi dengan begitu cepat?

216

Saya telah mencari di Google dan menelusuri situs web Go, tetapi sepertinya saya tidak dapat menemukan penjelasan untuk waktu pembuatan Go yang luar biasa. Apakah mereka produk dari fitur bahasa (atau ketiadaan), kompiler yang sangat optimal, atau yang lain? Saya tidak mencoba mempromosikan Go; Saya hanya penasaran.

Evan Kroske
sumber
12
@ Dukungan, saya tahu itu. Saya pikir bahwa mengimplementasikan kompiler sedemikian rupa sehingga ia mengkompilasi dengan kecepatan yang terlihat adalah apa-apa selain optimasi prematur. Lebih dari itu, ini mewakili hasil dari perancangan dan pengembangan perangkat lunak yang baik. Juga, saya tidak tahan melihat kata-kata Knuth dikeluarkan dari konteks dan diterapkan secara tidak benar.
Adam Crossland
55
Versi pesimis dari pertanyaan ini adalah "Mengapa C ++ mengkompilasi begitu lambat?" stackoverflow.com/questions/588884/…
dan04
14
Saya memilih untuk membuka kembali pertanyaan ini karena tidak berdasarkan pendapat. Seseorang dapat memberikan gambaran teknis yang baik (tanpa pendapat) dari pilihan bahasa dan / atau kompiler yang kecepatan kompilasi fasilitasnya.
Martin Tournoij
Untuk proyek-proyek kecil, Go terasa lambat bagiku. Ini karena saya ingat Turbo-Pascal jauh lebih cepat di komputer yang mungkin ribuan kali lebih lambat. prog21.dadgum.com/47.html?repost=true . Setiap kali saya mengetik "go build" dan tidak ada yang terjadi selama beberapa detik, saya berpikir kembali ke kompiler Fortran yang sudah tua dan kartu yang dilubangi. YMMV. TLDR: "lambat" dan "cepat" adalah istilah relatif.
RedGrittyBrick
Merekomendasikan membaca dave.cheney.net/2014/06/07/five-things-that-make-go-fast untuk wawasan yang lebih detail
Karthik

Jawaban:

192

Analisis ketergantungan.

The Go FAQ digunakan untuk berisi kalimat berikut:

Go menyediakan model untuk konstruksi perangkat lunak yang membuat analisis dependensi menjadi mudah dan menghindari banyak overhead dari gaya-C termasuk file dan perpustakaan.

Meskipun frasa tersebut tidak ada dalam FAQ lagi, topik ini dijabarkan lebih lanjut dalam pembicaraan Go at Google , yang membandingkan pendekatan ketergantungan analisis C / C ++ dan Go.

Itulah alasan utama kompilasi cepat. Dan ini dengan desain.

Igor Krivokon
sumber
Frasa ini tidak ada dalam Go FAQ lagi, tetapi penjelasan yang lebih terinci tentang topik "analisis ketergantungan" yang membandingkan pendekatan C / C ++ dan Pascal / Modula / Go tersedia dalam pembicaraan Go di Google
rob74
76

Saya pikir itu bukan karena kompiler Go cepat , itu kompiler lain lambat .

Kompiler C dan C ++ harus mem-parsing sejumlah besar header - misalnya, mengkompilasi C ++ "hello world" membutuhkan kompilasi 18k baris kode, yang hampir setengah megabyte sumber!

$ cpp hello.cpp | wc
  18364   40513  433334

Kompiler Java dan C # dijalankan dalam VM, yang berarti bahwa sebelum mereka dapat mengkompilasi apa pun, sistem operasi harus memuat seluruh VM, maka mereka harus dikompilasi dengan JIT dari bytecode ke kode asli, yang semuanya membutuhkan waktu.

Kecepatan kompilasi tergantung pada beberapa faktor.

Beberapa bahasa dirancang untuk dikompilasi dengan cepat. Sebagai contoh, Pascal dirancang untuk dikompilasi menggunakan kompiler single-pass.

Kompiler itu sendiri dapat dioptimalkan juga. Sebagai contoh, kompiler Turbo Pascal ditulis dalam assembler yang dioptimalkan dengan tangan, yang dikombinasikan dengan desain bahasa, menghasilkan kompiler yang sangat cepat yang bekerja pada perangkat keras kelas 286. Saya pikir bahkan sekarang, kompiler Pascal modern (misalnya FreePascal) lebih cepat daripada kompiler Go.

el.pescado
sumber
19
Kompiler C # Microsoft tidak berjalan dalam VM. Itu masih ditulis dalam C ++, terutama karena alasan kinerja.
blucz
19
Turbo Pascal dan Delphi yang lebih baru adalah contoh terbaik untuk kompiler yang sangat cepat. Setelah arsitek keduanya bermigrasi ke Microsoft, kami telah melihat peningkatan besar dalam kompiler MS, dan bahasa. Itu bukan kebetulan acak.
TheBlastOne
7
18k baris (tepatnya 18364) dari kode adalah 433334 byte (~ 0,5MB)
el.pescado
9
Compiler C # telah dikompilasi dengan C # sejak 2011. Hanya pembaruan jika ada yang membacanya nanti.
Kurt Koller
3
Namun, kompiler C # dan CLR yang menjalankan MSIL berbeda. Saya cukup yakin bahwa CLR tidak ditulis dalam C #.
jocull
39

Ada beberapa alasan mengapa kompiler Go jauh lebih cepat daripada kebanyakan kompiler C / C ++:

  • Alasan utama : Kebanyakan kompiler C / C ++ menunjukkan desain yang sangat buruk (dari perspektif kecepatan kompilasi). Juga, dari perspektif kecepatan kompilasi, beberapa bagian dari ekosistem C / C ++ (seperti editor di mana programmer menulis kode mereka) tidak dirancang dengan kecepatan-kompilasi dalam pikiran.

  • Alasan utama : Kecepatan kompilasi yang cepat adalah pilihan sadar dalam kompiler Go dan juga dalam bahasa Go

  • Kompiler Go memiliki pengoptimal yang lebih sederhana daripada kompiler C / C ++

  • Tidak seperti C ++, Go tidak memiliki template dan tidak ada fungsi inline. Ini berarti bahwa Go tidak perlu melakukan template atau fungsi instantiation.

  • Kompilator Go menghasilkan kode rakitan tingkat rendah lebih cepat dan pengoptimal bekerja pada kode rakitan, sedangkan dalam kompiler C / C ++ tipikal, optimasi yang dilewati bekerja pada representasi internal dari kode sumber asli. Overhead tambahan dalam kompiler C / C ++ berasal dari fakta bahwa representasi internal perlu dihasilkan.

  • Tautan akhir (5l / 6l / 8l) dari program Go dapat lebih lambat daripada menautkan program C / C ++, karena kompilator Go sedang melalui semua kode assembly yang digunakan dan mungkin juga melakukan tindakan tambahan lainnya yaitu C / C ++ penghubung tidak melakukan

  • Beberapa kompiler C / C ++ (GCC) menghasilkan instruksi dalam bentuk teks (untuk diteruskan ke assembler), sedangkan kompiler Go menghasilkan instruksi dalam bentuk biner. Pekerjaan ekstra (tapi tidak banyak) perlu dilakukan untuk mengubah teks menjadi biner.

  • Kompiler Go hanya menargetkan sejumlah kecil arsitektur CPU, sedangkan kompiler GCC menargetkan sejumlah besar CPU

  • Kompiler yang dirancang dengan tujuan kecepatan kompilasi tinggi, seperti Jikes, cepat. Pada CPU 2GHz, Jikes dapat mengkompilasi 20000+ baris kode Java per detik (dan mode kompilasi tambahan bahkan lebih efisien).

pengguna811773
sumber
17
Kompilator Go menampilkan fungsi-fungsi kecil. Saya tidak yakin bagaimana menargetkan sejumlah kecil CPU membuat Anda lebih cepat lebih lambat ... Saya menganggap gcc tidak menghasilkan kode PPC saat saya sedang mengkompilasi untuk x86.
Brad Fitzpatrick
@BradFitzpatrick benci untuk menghidupkan kembali komentar lama tetapi dengan menargetkan sejumlah kecil platform, pengembang kompiler dapat menghabiskan lebih banyak waktu untuk mengoptimalkannya untuk masing-masing.
Kegigihan
menggunakan bentuk peralihan memungkinkan Anda untuk mendukung lebih banyak arsitektur karena sekarang Anda hanya perlu menulis backend baru untuk setiap arsitektur baru
phuclv
34

Efisiensi kompilasi adalah tujuan desain utama:

Akhirnya, ini dimaksudkan untuk menjadi cepat: harus paling lama beberapa detik untuk membangun executable besar di satu komputer. Untuk memenuhi tujuan-tujuan ini diperlukan mengatasi sejumlah masalah linguistik: sistem tipe ekspresif tetapi ringan; konkurensi dan pengumpulan sampah; spesifikasi ketergantungan yang kaku; dan seterusnya. Faq

FAQ bahasa cukup menarik terkait fitur bahasa tertentu yang berkaitan dengan penguraian:

Kedua, bahasa telah dirancang agar mudah dianalisis dan dapat diurai tanpa tabel simbol.

Larry OBrien
sumber
6
Itu tidak benar. Anda tidak dapat sepenuhnya mem-parsing kode sumber Go tanpa tabel simbol.
12
Saya juga tidak mengerti mengapa pengumpulan sampah meningkatkan waktu kompilasi. Hanya saja tidak.
TheBlastOne
3
Ini adalah kutipan dari FAQ: golang.org/doc/go_faq.html Saya tidak bisa mengatakan apakah mereka gagal mencapai tujuan mereka (tabel simbol) atau jika logikanya salah (GC).
Larry OBrien
5
@FUZxxl Pergi ke golang.org/ref/spec#Primary_expressions dan perhatikan dua urutan [Operand, Call] dan [Conversion]. Contoh kode sumber Go: identifier1 (identifier2). Tanpa tabel simbol, tidak mungkin untuk memutuskan apakah contoh ini adalah panggilan atau konversi. | Bahasa apa pun dapat sampai batas tertentu diuraikan tanpa tabel simbol. Memang benar bahwa sebagian besar kode sumber Go dapat diuraikan tanpa tabel simbol, tetapi tidak benar bahwa adalah mungkin untuk mengenali semua elemen tata bahasa yang didefinisikan dalam spec golang.
3
@ Otom Anda bekerja keras untuk mencegah pengurai menjadi bagian dari kode yang melaporkan kesalahan. Parser umumnya melakukan pekerjaan yang buruk untuk melaporkan pesan kesalahan yang masuk akal. Di sini, Anda membuat parse tree untuk ekspresi seolah-olah aTypeadalah referensi variabel, dan kemudian dalam fase analisis semantik ketika Anda mengetahui itu bukan Anda mencetak kesalahan yang berarti pada saat itu.
Sam Harwell
26

Sementara sebagian besar di atas benar, ada satu hal yang sangat penting yang tidak benar-benar disebutkan: manajemen ketergantungan.

Go hanya perlu menyertakan paket yang Anda impor langsung (seperti yang sudah mengimpor apa yang mereka butuhkan). Ini sangat kontras dengan C / C ++, di mana setiap file tunggal mulai termasuk x header, yang mencakup header y dll. Intinya: Pengompilasi Go membutuhkan waktu linier wrt dengan jumlah paket yang diimpor, di mana C / C ++ membutuhkan waktu yang eksponensial.

Kosta
sumber
22

Tes yang baik untuk efisiensi terjemahan kompiler adalah kompilasi sendiri: berapa lama kompiler yang diberikan untuk mengkompilasi dirinya sendiri? Untuk C ++ dibutuhkan waktu yang sangat lama (jam?). Sebagai perbandingan, kompiler Pascal / Modula-2 / Oberon akan dikompilasi sendiri dalam waktu kurang dari satu detik pada mesin modern [1].

Go telah terinspirasi oleh bahasa-bahasa ini, tetapi beberapa alasan utama untuk efisiensi ini termasuk:

  1. Sintaks yang didefinisikan dengan jelas yang secara matematis suara, untuk pemindaian dan penguraian efisien

  2. Tipe-aman dan dikompilasi secara statis yang menggunakan kompilasi terpisah dengan dependensi dan tipe memeriksa melintasi batas-batas modul, untuk menghindari pembacaan ulang file header yang tidak perlu dan kompilasi ulang modul lain - yang bertentangan dengan kompilasi independen seperti di C / C ++ di mana tidak ada pemeriksaan modul silang yang dilakukan oleh kompiler (karena itu perlu membaca ulang semua file header itu berulang-ulang, bahkan untuk program "hello world" sederhana satu-baris).

  3. Implementasi compiler yang efisien (mis. Single-pass, recursive-descent top-down parsing) - yang tentu saja sangat terbantu oleh poin 1 dan 2 di atas.

Prinsip-prinsip ini telah dikenal dan diimplementasikan secara penuh pada tahun 1970-an dan 1980-an dalam bahasa seperti Mesa, Ada, Modula-2 / Oberon dan beberapa lainnya, dan baru sekarang (pada tahun 2010) menemukan jalan mereka ke bahasa modern seperti Go (Google) , Swift (Apple), C # (Microsoft) dan beberapa lainnya.

Mari kita berharap bahwa ini akan segera menjadi norma dan bukan pengecualian. Untuk sampai di sana, dua hal perlu terjadi:

  1. Pertama, penyedia platform perangkat lunak seperti Google, Microsoft dan Apple harus memulai dengan mendorong pengembang aplikasi untuk menggunakan metodologi kompilasi yang baru, sambil memungkinkan mereka untuk menggunakan kembali basis kode yang ada. Inilah yang sekarang coba dilakukan Apple dengan bahasa pemrograman Swift, yang dapat hidup berdampingan dengan Objective-C (karena menggunakan lingkungan runtime yang sama).

  2. Kedua, platform perangkat lunak yang mendasari sendiri akhirnya harus ditulis ulang seiring waktu menggunakan prinsip-prinsip ini, sementara secara bersamaan mendesain ulang hirarki modul dalam proses untuk membuatnya kurang monolitik. Ini tentu saja merupakan tugas yang sangat besar dan mungkin mengambil bagian yang lebih baik dari satu dekade (jika mereka cukup berani untuk benar-benar melakukannya - yang saya sama sekali tidak yakin dalam kasus Google).

Bagaimanapun, itu adalah platform yang mendorong adopsi bahasa, dan bukan sebaliknya.

Referensi:

[1] http://www.inf.ethz.ch/personal/wirth/ProjectOberon/PO.System.pdf , halaman 6: "Kompilator mengkompilasi sendiri dalam waktu sekitar 3 detik". Kutipan ini untuk papan pengembangan FPGA Xilinx Spartan-3 berbiaya rendah yang beroperasi pada frekuensi clock 25 MHz dan menampilkan 1 MByte memori utama. Dari yang satu ini dapat dengan mudah diekstrapolasi menjadi "kurang dari 1 detik" untuk prosesor modern yang berjalan pada frekuensi clock jauh di atas 1 GHz dan beberapa GBytes memori utama (yaitu beberapa pesanan yang besarnya lebih kuat daripada papan FPGA Xilinx Spartan-3), bahkan ketika memperhitungkan kecepatan I / O. Sudah kembali pada tahun 1990 ketika Oberon dijalankan pada prosesor NS32X32 25MHz dengan memori utama 2-4 MB, kompiler mengkompilasi dirinya sendiri hanya dalam beberapa detik. Gagasan untuk benar-benar menunggubagi kompiler untuk menyelesaikan siklus kompilasi sama sekali tidak dikenal oleh programmer Oberon bahkan saat itu. Untuk program tipikal, selalu diperlukan lebih banyak waktu untuk menghapus jari dari tombol mouse yang memicu perintah kompilasi daripada menunggu kompiler menyelesaikan kompilasi yang baru saja dipicu. Itu benar-benar kepuasan instan, dengan waktu tunggu hampir nol. Dan kualitas kode yang dihasilkan, meskipun tidak selalu setara dengan kompiler terbaik yang tersedia saat itu, sangat bagus untuk sebagian besar tugas dan cukup dapat diterima secara umum.

Andreas
sumber
1
Kompiler Pascal / Modula-2 / Oberon / Oberon-2 akan mengkompilasi dirinya sendiri dalam waktu kurang dari satu detik pada mesin modern [rujukan?]
CoffeeandCode
1
Kutipan ditambahkan, lihat referensi [1].
Andreas
1
"... prinsip ... menemukan jalan mereka ke bahasa modern seperti Go (Google), Swift (Apple)" Tidak yakin bagaimana Swift masuk ke dalam daftar itu: kompiler Swift glasial . Pada pertemuan CocoaHeads Berlin baru-baru ini, seseorang memberikan beberapa angka untuk kerangka kerja ukuran sedang, mereka mencapai 16 LOC per detik.
mpw
13

Go dirancang untuk menjadi cepat, dan itu menunjukkan.

  1. Manajemen Ketergantungan: tanpa file header, Anda hanya perlu melihat paket yang diimpor langsung (tidak perlu khawatir tentang apa yang mereka impor) sehingga Anda memiliki dependensi linear.
  2. Tata bahasa: tata bahasa sederhana, sehingga mudah diurai. Meskipun jumlah fitur berkurang, sehingga kode kompiler itu sendiri ketat (beberapa jalur).
  3. Tidak diperbolehkan overload: Anda melihat simbol, Anda tahu metode mana yang dimaksud.
  4. Sepele mungkin untuk mengkompilasi Go secara paralel karena setiap paket dapat dikompilasi secara independen.

Perhatikan bahwa GO bukan satu-satunya bahasa dengan fitur seperti itu (modul adalah norma dalam bahasa modern), tetapi mereka melakukannya dengan baik.

Matthieu M.
sumber
Poin (4) tidak sepenuhnya benar. Modul yang saling bergantung harus dikompilasi dalam urutan dependensi untuk memungkinkan inlining dan modul lintas modul.
fuz
1
@ FuZxxl: Ini hanya menyangkut tahap optimasi, Anda dapat memiliki paralelisme sempurna hingga generasi IR backend; hanya optimasi lintas-modul yang terkait, yang dapat dilakukan pada tahap tautan, dan tautan tersebut tidak paralel pula. Tentu saja, jika Anda tidak ingin menduplikasi pekerjaan Anda (penguraian ulang), Anda lebih baik mengkompilasi dengan cara "kisi": 1 / modul tanpa ketergantungan, 2 / modul hanya bergantung pada (1), 3 / modul hanya bergantung pada (1) dan (2), ...
Matthieu M.
2
Yang sangat mudah dilakukan dengan menggunakan utilitas dasar seperti Makefile.
fuz
12

Mengutip dari buku " The Go Programming Language " oleh Alan Donovan dan Brian Kernighan:

Kompilasi Go khususnya lebih cepat daripada kebanyakan bahasa yang dikompilasi lainnya, bahkan ketika membangun dari awal. Ada tiga alasan utama kecepatan kompiler. Pertama, semua impor harus secara eksplisit terdaftar di awal setiap file sumber, sehingga kompiler tidak harus membaca dan memproses seluruh file untuk menentukan dependensinya. Kedua, dependensi paket membentuk grafik asiklik terarah, dan karena tidak ada siklus, paket dapat dikompilasi secara terpisah dan mungkin secara paralel. Akhirnya, file objek untuk paket Go yang dikompilasi mencatat informasi ekspor tidak hanya untuk paket itu sendiri, tetapi juga untuk dependensinya. Ketika mengkompilasi sebuah paket, kompiler harus membaca satu file objek untuk setiap impor tetapi tidak perlu melihat melampaui file-file ini.

Bajingan
sumber
9

Ide dasar kompilasi sebenarnya sangat sederhana. Pengurai keturunan rekursif, pada prinsipnya, dapat berjalan pada kecepatan terikat I / O. Pembuatan kode pada dasarnya adalah proses yang sangat sederhana. Tabel simbol dan sistem tipe dasar bukanlah sesuatu yang membutuhkan banyak perhitungan.

Namun, tidak sulit untuk memperlambat kompiler.

Jika ada fase preprosesor, dengan multi-level menyertakan arahan, definisi makro, dan kompilasi bersyarat, sama berharganya dengan hal-hal itu, tidak sulit untuk memuatnya. (Sebagai contoh, saya sedang memikirkan file header Windows dan MFC.) Itulah mengapa header yang dikompilasi diperlukan.

Dalam hal mengoptimalkan kode yang dihasilkan, tidak ada batasan berapa banyak pemrosesan yang dapat ditambahkan ke fase itu.

Mike Dunlavey
sumber
7

Sederhananya (dengan kata-kata saya sendiri), karena sintaksinya sangat mudah (untuk menganalisis dan mengurai)

Misalnya, tanpa pewarisan jenis berarti, bukan analisis yang bermasalah untuk mengetahui apakah tipe baru tersebut mengikuti aturan yang diberlakukan oleh tipe dasar.

Sebagai contoh dalam contoh kode ini: "antarmuka" kompiler tidak pergi dan memeriksa apakah tipe yang dimaksud mengimplementasikan antarmuka yang diberikan saat menganalisis jenis itu. Hanya sampai digunakan (dan JIKA digunakan) pemeriksaan dilakukan.

Contoh lain, kompiler memberi tahu Anda jika Anda mendeklarasikan variabel dan tidak menggunakannya (atau jika Anda seharusnya memiliki nilai balik dan Anda tidak)

Berikut ini tidak dikompilasi:

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

Ini semacam penegakan dan prinsip membuat kode yang dihasilkan lebih aman, dan kompiler tidak harus melakukan validasi tambahan yang dapat dilakukan oleh programmer.

Pada dasarnya semua detail ini membuat bahasa lebih mudah diurai yang menghasilkan kompilasi cepat.

Sekali lagi, dengan kata-kata saya sendiri.

OscarRyz
sumber
3

Saya pikir Go dirancang secara paralel dengan kreasi kompiler, jadi mereka adalah teman terbaik sejak lahir. (IMO)

Andrey
sumber
0
  • Go mengimpor dependensi sekali untuk semua file, sehingga waktu impor tidak meningkat secara eksponensial dengan ukuran proyek.
  • Linguistik yang lebih sederhana berarti menafsirkannya membutuhkan lebih sedikit komputasi.

Apa lagi?

Alberto Salvia Novella
sumber