Чи використовують компілятори багатопотокові для швидшого часу компіляції?


16

Якщо я добре пам’ятаю свій курс компіляторів, типовий компілятор має такий спрощений контур:

  • Лексичний аналізатор сканує (або викликає якусь функцію сканування) вихідного коду за символом
  • Рядок символів введення перевіряється на відповідність словнику лексем
  • Якщо лексема є дійсною, вона класифікується як лексема, якій вона відповідає
  • Аналізатор підтверджує синтаксис комбінації лексем; токен-на-токен .

Чи теоретично можливо розділити вихідний код на чверті (чи будь-який знаменник) і багатопотоково проаналізувати процес сканування та аналізу? Чи існують компілятори, які використовують багатопотоковість?




1
@RobertHarvey Перша відповідь першої посилання написала: "але самі компілятори все ще є однопоточними". Так це ні?
8протонів

Я пропоную вам прочитати решту відповідей, особливо цю , та друге посилання, яке я розмістив.
Роберт Харві

2
@RobertHarvey Друге посилання, яке ви опублікували, з мого розуміння того, що це говорить, - це компілятор, який генерує багатопоточну версію вашої компільованої програми. Йдеться не про сам компілятор. Дякуємо за спільні ресурси та знайшли час для відповіді.
8протонів

Відповіді:


29

Великі програмні проекти зазвичай складаються з безлічі одиниць компіляції, які можна скласти відносно незалежно, і тому компіляція часто паралелізується при дуже грубій деталізації, викликаючи компілятор кілька разів паралельно. Це відбувається на рівні процесів ОС і координується системою збірки, а не компілятором. Я усвідомлюю, що це не те, про що ви питали, але це найбільш близька паралелізація у більшості компіляторів.

Чому так? Ну, значна частина роботи, яку виконують компілятори, не піддається легкому паралелізації:

  • Ви не можете просто розділити вхід на кілька фрагментів і самостійно їх ввести. Для простоти ви хочете розділити межі лексеми (щоб жодна нитка не починалася посередині лексеми), але для визначення меж лексеми потенційно потрібно багато контексту. Наприклад, коли ви переходите в середину файлу, ви повинні переконатися, що ви не перейшли до рядкового літералу. Але для того, щоб перевірити це, ви повинні переглянути в основному кожного персонажа, який був раніше, що майже така ж робота, як і просто почати його. Крім того, лексинг рідко є вузьким місцем у компіляторах для сучасних мов.
  • Розбирати паралелі ще важче. Усі проблеми розщеплення вхідного тексту для лексингу ще більше стосуються розбиття лексем для розбору --- наприклад, визначення того, з чого функція починається, в основному настільки ж складна, як і для початку аналіз вмісту функції. Хоча для цього також можуть бути шляхи, вони, мабуть, будуть непропорційно складними для невеликої користі. Парсінг теж не є найбільшим вузьким місцем.
  • Після розбору вам зазвичай потрібно виконати дозвіл імен, але це призводить до величезної переплетення мереж відносин. Щоб вирішити виклик методу тут, вам, можливо, доведеться спочатку вирішити імпорт у цьому модулі, але для цього потрібно вирішити імена в іншому блоці компіляції тощо. Те ж саме для виводу типу, якщо ваша мова має це.

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

Все, що було сказано, я знаю про компілятор, який паралелізує роботу створення та оптимізації коду. Компілятор Rust може розділити роботу заднього кінця (LLVM, яка фактично включає оптимізацію коду, які традиційно вважаються "середнім кінцем") на кілька потоків. Це називається "кодово-генними одиницями". На відміну від інших можливостей паралелізації, розглянутих вище, це економічно, оскільки:

  1. Мова має досить великі одиниці компіляції (порівняно, скажімо, з C або Java), тому у польоті може бути менше одиниць компіляції, ніж у вас є ядра.
  2. Частина, яка проводиться паралельно, зазвичай займає переважну більшість часу на компіляцію.
  3. Зробоча робота, здебільшого, бентежно паралельна - просто оптимізуйте та перекладіть до машинного коду кожну функцію незалежно. Звичайно, існують міжпроцедурні оптимізації, і блоки кодегенів перешкоджають цьому і, таким чином, впливають на продуктивність, але семантичних проблем немає.

2

Компіляція - проблема "бентежно паралельна".

Ніхто не піклується про час для складання одного файлу. Люди дбають про час збирання 1000 файлів. І для 1000 файлів кожне ядро ​​процесора може із задоволенням збирати один файл за один раз, зберігаючи всі ядра повністю зайнятими.

Порада: "make" використовує кілька ядер, якщо ви надаєте правильний параметр командного рядка. Без цього він складе один файл за іншим у 16-ти основних системах. Це означає, що ви можете змусити його збиратись у 16 ​​разів швидше, змінивши один рядок у ваших параметрах збирання.

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