Як Go компілюється так швидко?


217

Я погуглив і роздумував веб-сайт Go, але, здається, не можу знайти пояснення надзвичайним часом збирання Go. Це продукти мовних особливостей (або їх відсутність), високооптимізований компілятор чи щось інше? Я не намагаюся просувати Go; Мені просто цікаво.


12
@Support, я знаю про це. Я думаю, що реалізація компілятора таким чином, щоб він компілювався із помітною швидкістю - це що завгодно, крім передчасної оптимізації. Більш ніж ймовірно, це є результатом належної практики розробки та розробки програмного забезпечення. Крім того, я не можу витримати слова Кнута, винесені з контексту та неправильно застосовані.
Адам Кросленд

55
Версія песиміста цього питання "Чому C ++ збирається так повільно?" stackoverflow.com/questions/588884 / ...
dan04

14
Я проголосував за повторне відкриття цього питання, оскільки воно не ґрунтується на думці. Можна дати хороший технічний (невмілий) огляд вибору мови та / або компілятора, яка швидкість компіляції.
Martin Tournoij

Що стосується невеликих проектів, Go мені здається повільним. Це тому, що я пам’ятаю, що Turbo-Pascal був набагато швидшим на комп'ютері, який, мабуть, був у тисячі разів повільнішим. prog21.dadgum.com/47.html?repost=true . Кожен раз, коли я набираю "йти будувати", і нічого не відбувається протягом декількох секунд, я повертаюсь до старого стислого компілятора Fortran та перфокарт. YMMV. TLDR: "повільний" і "швидкий" відносні терміни.
RedGrittyBrick

Однозначно рекомендую прочитати dave.cheney.net/2014/06/07/five-things-that-make-go-fast для більш детального розуміння
Karthik

Відповіді:


193

Аналіз залежності.

Go FAQ використовується , щоб мати таку фразу:

Go пропонує модель для побудови програмного забезпечення, яка робить аналіз залежності простим і дозволяє уникнути значних витрат на C-стилі, включаючи файли та бібліотеки.

Хоча ця фраза більше не знаходиться в FAQ, ця тема розроблена в бесіді Go в Google , де порівнюється підхід до аналізу залежності C / C ++ та Go.

Це головна причина швидкого складання. І це за задумом.


Ця фраза більше не міститься у FAQ FAQ, але більш детальне пояснення теми "аналіз залежностей", що порівнює C / C ++ та Pascal / Modula / Go, доступне в бесіді Go в Google
rob74

76

Я думаю, що компілятори Go не швидкі , а інші компілятори повільні .

Компілятори C і C ++ повинні проаналізувати величезну кількість заголовків - наприклад, для складання C ++ "привіт світ" потрібно зібрати 18k рядків коду, що майже в половину мегабайт джерел!

$ cpp hello.cpp | wc
  18364   40513  433334

Компілятори Java та C # працюють у VM, що означає, що перш ніж вони зможуть що-небудь скласти, операційна система повинна завантажити весь VM, тоді вони повинні бути JIT-компільовані з байт-коду до нативного коду, і все це потребує певного часу.

Швидкість складання залежить від кількох факторів.

Деякі мови розроблені для швидкого складання. Наприклад, Pascal був розроблений для складання за допомогою компілятора з одним проходом.

Самі компілятори також можна оптимізувати. Наприклад, компілятор Turbo Pascal був написаний вручну оптимізованим асемблером, що в поєднанні з мовним дизайном призвело до дійсно швидкого компілятора, який працює на апаратному забезпеченні 286 класу. Я думаю, що навіть зараз сучасні компілятори Pascal (наприклад, FreePascal) швидші, ніж компілятори Go.


19
Компілятор C # від Microsoft не працює у віртуальній машині. Він все ще написаний на C ++, в першу чергу з міркувань продуктивності.
blucz

19
Turbo Pascal і пізніші Delphi - найкращі приклади для надзвичайно швидких компіляторів. Після того, як архітектор обох перейшов до Microsoft, ми побачили значні вдосконалення як у компіляторах MS, так і в мовах. Це не випадковий збіг.
TheBlastOne

7
18k рядків (18364 точніше) коду - 433334 байт (~ 0,5 МБ)
el.pescado

9
Компілятор C # складається з C # з 2011 року. Просто оновлення, якщо хтось прочитає це пізніше.
Курт Коллер

3
Однак компілятор C # і CLR, який запускає сформований MSIL, - це різні речі. Я впевнений, що CLR не записаний на C #.
jocull

39

Є кілька причин, чому компілятор Go набагато швидший, ніж більшість компіляторів C / C ++:

  • Основна причина : Більшість компіляторів C / C ++ мають надзвичайно погані конструкції (з точки зору швидкості компіляції). Крім того, з точки зору швидкості компіляції деякі частини екосистеми C / C ++ (наприклад, редактори, в яких програмісти пишуть свої коди) не розроблені з урахуванням швидкості компіляції.

  • Основна причина : швидка швидкість компіляції була свідомим вибором у компіляторі Go, а також у мові Go

  • Компілятор Go має простіший оптимізатор, ніж компілятори C / C ++

  • На відміну від C ++, Go не має шаблонів і ніяких вбудованих функцій. Це означає, що Go не потрібно виконувати будь-які інстанції шаблонів чи функцій.

  • Компілятор Go швидше генерує код складання низького рівня, і оптимізатор працює над кодом складання, тоді як у типовому компіляторі C / C ++ оптимізація передає роботу над внутрішнім представленням вихідного вихідного коду. Додаткові накладні витрати в компіляторі C / C ++ виникають через те, що необхідно створити внутрішнє представлення.

  • Остаточне посилання (5l / 6l / 8l) програми Go може бути повільніше, ніж зв'язування програми C / C ++, тому що компілятор Go переживає весь використаний код складання і, можливо, він також виконує інші додаткові дії, що C / C ++ лінкери не роблять

  • Деякі компілятори C / C ++ (GCC) генерують інструкції у текстовій формі (передаються асемблеру), тоді як компілятор Go генерує інструкції у двійковій формі. Додаткову роботу (але не дуже багато) потрібно зробити, щоб перетворити текст у бінарний.

  • Компілятор Go націлений на лише невелику кількість архітектур процесора, тоді як компілятор GCC орієнтований на велику кількість процесорів

  • Компілятори, які були розроблені з метою високої швидкості компіляції, такі як Jikes, швидкі. У процесорі 2 ГГц Jikes може компілювати 20000+ рядків коду Java в секунду (а інкрементальний режим компіляції є ще більш ефективним).


17
Компілятор Go виділяє невеликі функції. Я не впевнений, як націлювання на невелику кількість процесорів робить вас швидшими повільніше ... Я припускаю, що gcc не генерує код PPC, коли я компілюю для x86.
Бред Фіцпатрік

@BradFitzpatrick ненавидить відновити старий коментар, але, орієнтуючись на меншу кількість платформ, розробники компілятора можуть витратити більше часу, оптимізуючи його для кожної.
Наполегливість

використання проміжної форми дозволяє підтримувати набагато більше архітектур, оскільки тепер вам потрібно лише написати новий бекенд для кожної нової архітектури
phuclv

34

Ефективність компіляції була головною метою дизайну:

Нарешті, він повинен бути швидким: для створення великого виконуваного файлу на одному комп’ютері потрібно щонайбільше декількох секунд. Для досягнення цих цілей потрібно вирішити ряд мовних питань: виразну, але легку систему; паралельність та вивезення сміття; специфікація жорсткої залежності; і так далі. FAQ

FAQ щодо мови є досить цікавим щодо конкретних мовних особливостей, що стосуються розбору:

По-друге, мова розроблена так, щоб її було легко проаналізувати і її можна розібрати без таблиці символів.


6
Це не правда. Не можна повністю проаналізувати вихідний код Go без таблиці символів.

12
Я також не бачу, чому збирання сміття збільшує час збирання. Це просто ні.
TheBlastOne

3
Це цитати з FAQ: golang.org/doc/go_faq.html Я не можу сказати, чи не вдалося досягти своїх цілей (таблиця символів) або якщо їх логіка несправна (GC).
Ларрі OBrien

5
@FUZxxl Перейдіть на golang.org/ref/spec#Primary_expressions і розгляньте дві послідовності [Operand, Call] та [Conversion]. Приклад Перейти вихідний код: identifier1 (identifier2). Без таблиці символів неможливо вирішити, чи є цей приклад викликом або перетворенням. | Будь-яка мова може бути певною мірою проаналізована без символьної таблиці. Це правда, що більшість частин вихідних кодів Go можна проаналізувати без таблиці символів, але це неправда, що можна розпізнати всі граматичні елементи, визначені в специфіці голанг.

3
@Atom Ви наполегливо працюєте, щоб аналізатор не став частиною коду, який повідомляє про помилку. Як правило, парсери погано працюють над повідомленням про узгоджені повідомлення про помилки. Тут ви створюєте дерево розбору для виразу ніби aTypeє змінною посиланням, а пізніше на етапі семантичного аналізу, коли дізнаєтесь, що не ви друкуєте значущу помилку на той час.
Сем Харвелл

26

Хоча більшість із зазначеного вище правда, є один дуже важливий момент, який насправді не згадувався: управління залежністю.

Потрібно лише включити пакунки, які ви безпосередньо імпортуєте (як ті, що вже імпортували те, що їм потрібно). Це суворо на відміну від C / C ++, де кожен файл починається, включаючи х заголовки, які включають y заголовки тощо. Підсумок: Компіляція Go займає лінійний wrt часу до кількості імпортованих пакетів, де C / C ++ займає експоненціальний час.


22

Хорошим тестом на ефективність перекладу компілятора є самокомпіляція: скільки часу потрібно для компіляції даному компілятору? Для C ++ потрібно дуже багато часу (години?). Для порівняння, компілятор Pascal / Modula-2 / Oberon склав би менше ніж одну секунду на сучасній машині [1].

Go надихнувся цими мовами, але деякі основні причини такої ефективності включають:

  1. Чітко визначений синтаксис, який є математично здоровим, для ефективного сканування та аналізу.

  2. Безпечна для типу та статично складена мова, яка використовує окрему компіляцію із залежністю та перевіркою типів через межі модулів, щоб уникнути зайвого повторного читання файлів заголовків та перекомпіляції інших модулів - на відміну від незалежної компіляції, як у C / C ++, де такі перехресні модульні перевірки не виконуються компілятором (звідси необхідність перечитувати всі ці файли заголовків знову і знову, навіть для простої однорядкової програми "привіт світ").

  3. Ефективна реалізація компілятора (наприклад, однопрохідний, рекурсивно-низхідний розбір згори вниз) - що, звичайно, дуже допомагає пунктам 1 і 2 вище.

Ці принципи вже були відомі та повністю реалізовані у 1970-х та 1980-х роках у таких мовах, як Mesa, Ada, Modula-2 / Oberon та декілька інших, і лише зараз (у 2010-х роках) знаходять дорогу до сучасних мов, таких як Go (Google) , Swift (Apple), C # (Microsoft) та кілька інших.

Будемо сподіватися, що це скоро стане нормою, а не винятком. Щоб потрапити туди, потрібно статися дві речі:

  1. По-перше, постачальники програмних платформ, такі як Google, Microsoft та Apple, повинні почати, заохочуючи розробників додатків використовувати нову методологію компіляції, дозволяючи їм повторно використовувати існуючу базу коду. Це те, що Apple зараз намагається зробити з мовою програмування Swift, яка може співіснувати з Objective-C (оскільки вона використовує те саме середовище виконання).

  2. По-друге, самі програмні платформи з часом повинні бути перезаписані з часом, використовуючи ці принципи, одночасно переробляючи ієрархію модулів, щоб зробити їх менш монолітними. Це, звичайно, завдання мамонта і, можливо, може зайняти кращу частину десятиліття (якщо вони досить сміливі, щоб насправді це зробити - що я зовсім не впевнений у випадку Google).

У будь-якому випадку, саме платформа рухає мовою, а не навпаки.

Список літератури:

[1] http://www.inf.ethz.ch/personal/wirth/ProjectOberon/PO.System.pdf , сторінка 6: "Компілятор збирається за приблизно 3 секунди". Ця цитата - це недорога плата розвитку Xilinx Spartan-3 FPGA, яка працює на тактовій частоті 25 МГц і має 1 Мбайт основної пам'яті. З цього можна легко екстраполювати на «менше 1 секунди» для сучасного процесора, що працює на тактовій частоті набагато вище 1 ГГц та декількох ГБ основної пам’яті (тобто на кілька порядків потужніший, ніж плата Xilinx Spartan-3 FPGA), навіть при врахуванні швидкості вводу / виводу. Вже в 1990 році, коли Oberon був запущений на 25 МГц процесорі NS32X32 з 2-4 Мбайт основної пам'яті, компілятор склав себе всього за кілька секунд. Поняття насправді чекатищоб компілятор закінчив цикл компіляції, програмістам Oberon ще тоді було невідомо. Для типових програм завжди потрібно було більше часу, щоб вийняти палець з кнопки миші, яка запустила команду компіляції, ніж чекати, коли компілятор завершить компіляцію, щойно запустилася. Це було справді миттєвим задоволенням, з майже нульовим часом очікування. А якість виробленого коду, навіть не завжди повністю нарівні з найкращими компіляторами, наявними тоді, була надзвичайно хорошою для більшості завдань і цілком прийнятною в цілому.


1
Компілятор Pascal / Modula-2 / Oberon / Oberon-2 склав би менше ніж одну секунду на сучасній машині [потрібна цитата]
CoffeeandCode

1
Цитування додано, див. Посилання [1].
Андреас

1
"... принципи ... пошук свого шляху до сучасних мов, таких як Go (Google), Swift (Apple)" Не впевнений, як Swift увійшов до цього списку: компілятор Swift є льодовиковим . На нещодавній зустрічі з CocoaHeads Berlin в Берліні хтось надав деякі номери для середнього розміру, вони доходили до 16 LOC в секунду.
mpw

13

Go був розроблений так, щоб він був швидким, і це показує.

  1. Управління залежністю: файлу заголовка немає, вам просто потрібно переглянути пакети, які безпосередньо імпортуються (не потрібно турбуватися про те, що вони імпортують), таким чином у вас є лінійні залежності.
  2. Граматика: граматика мови проста, тому легко розбирається. Хоча кількість функцій зменшується, таким чином, сам код компілятора є обмеженим (мало шляхів).
  3. Не допускається перевантаження: ви бачите символ, знаєте, до якого методу він відноситься.
  4. Тривіально можливо компілювати Go паралельно, тому що кожен пакет може бути складений незалежно.

Зауважте, що GO не єдина мова з такими можливостями (модулі є нормою в сучасних мовах), але вони зробили це добре.


Точка (4) не зовсім вірна. Модулі, які залежать один від одного, повинні бути складені в порядку залежності, щоб мати можливість вбудовувати міжрядкові модулі та ін.
fuz

1
@FUZxxl: Це стосується лише етапу оптимізації, однак ви можете мати ідеальний паралелізм аж до зародження ІЧ-генерації; Таким чином, стосується лише міжмодульної оптимізації, що може бути зроблено на етапі зв'язку, і зв'язок все одно не паралельний. Звичайно, якщо ви не хочете дублювати свою роботу (повторний аналіз), вам краще компілювати «решіткою»: 1 / модулі без залежності, 2 / модулі залежно лише від (1), 3 / модулі залежно лише від (1) та (2), ...
Матьє М.

2
Що абсолютно легко зробити за допомогою основних утиліт, таких як Makefile.
fuz

12

Цитуючи з книги " Мова програмування Go " Алана Донована та Брайана Керніган:

Компіляція Go Go помітно швидша, ніж у більшості інших мов, що складені, навіть при побудові з нуля. Існує три основні причини швидкості компілятора. По-перше, весь імпорт повинен бути чітко вказаний на початку кожного вихідного файлу, тому компілятору не потрібно читати та обробляти весь файл, щоб визначити його залежності. По-друге, залежності пакету утворюють спрямований ациклічний графік, а оскільки циклів немає, пакунки можуть бути складені окремо і, можливо, паралельно. Нарешті, об’єктний файл для складеного пакету Go записує інформацію про експорт не лише для самого пакету, але і для його залежностей. Під час компіляції пакета компілятор повинен прочитати один об'єктний файл для кожного імпорту, але не повинен виходити за межі цих файлів.


9

Основна ідея складання насправді дуже проста. Рекурсивний аналізатор спуску, в принципі, може працювати з обмеженою швидкістю вводу / виводу. Генерація коду - це в основному дуже простий процес. Таблиця символів і система базового типу - це не те, що вимагає великих обчислень.

Однак уповільнити компілятор не важко.

Якщо є фаза препроцесора, з багаторівневим включенням директив, макроозначень та умовної компіляції, настільки ж корисних, як і ці речі, не важко її завантажити. (Для одного прикладу я думаю про файли заголовків Windows та MFC.) Тому необхідні попередньо складені заголовки.

Що стосується оптимізації згенерованого коду, то немає обмеження в кількості обробки, яка може бути додана до цієї фази.


7

Просто (моїми власними словами), тому що синтаксис дуже простий (аналізувати та аналізувати)

Наприклад, відсутність успадкування типів не означає не проблематичний аналіз, щоб з’ясувати, чи дотримується новий тип правил, накладених базовим типом.

Наприклад, у цьому прикладі коду: "інтерфейси" компілятора не переходять і перевіряють, чи призначений тип реалізує даний інтерфейс під час аналізу цього типу. Тільки до тих пір, поки вона не буде використана (і якщо вона використовується) перевірка виконується.

Інший приклад, компілятор повідомляє вам, якщо ви оголошуєте змінну і не використовуєте її (або якщо ви повинні містити повернене значення, а ви не використовуєте)

Далі не компілюється:

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

Такі види застосувань та принципів роблять отриманий код більш безпечним, і компілятору не потрібно виконувати додаткові перевірки, які може робити програміст.

В цілому всі ці деталі полегшують розбір мови, що призводить до швидких компіляцій.

Знову ж таки, власними словами.


3

Я думаю, що Go розроблявся паралельно зі створенням компілятора, тому вони були найкращими друзями з народження. (ІМО)


0
  • Перейдіть залежність від імпорту один раз для всіх файлів, тому час імпорту не збільшується експоненціально з розміром проекту.
  • Простіша лінгвістика означає, що для їх інтерпретації потрібно менше обчислень.

Що ще?

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.