Майте на увазі, що нижче порівнюється лише різниця між рідною та JIT-компіляцією, і не охоплює специфіку будь-якої конкретної мови чи рамок. Можуть бути законні причини вибору певної платформи поза цим.
Коли ми стверджуємо, що власний код швидший, ми говоримо про типовий випадок використання нативно скомпільованого коду проти компільованого коду JIT, де типове використання застосованого JIT додатка має запускатися користувачем з негайними результатами (наприклад, ні очікування спочатку компілятора). У цьому випадку я не думаю, що ніхто може претендувати на пряме обличчя, що компільований JIT код може відповідати або перемагати нативний код.
Припустимо, що у нас є програма, написана якоюсь мовою X, і ми можемо її скласти за допомогою власного компілятора та знову за допомогою компілятора JIT. Кожен робочий потік має однакові етапи, які можна узагальнити (Код -> Проміжне представництво -> Машинний код -> Виконання). Велика різниця між двома полягає в тому, які етапи бачить користувач і які бачить програміст. При нативної компіляції програміст бачить усі, крім етапу виконання, але за допомогою рішення JIT, компіляцію до машинного коду бачить користувач, крім виконання.
Твердження, що A швидше B , посилається на час, необхідний для запуску програми, як це бачить користувач . Якщо припустити, що обидва фрагменти коду виконують однаково на етапі виконання, ми повинні припустити, що робочий потік JIT уповільнюється для користувача, оскільки він також повинен бачити час T компіляції до машинного коду, де T> 0. Отже , для будь-якої можливості робочого потоку JIT виконувати так само, як і власний робочий потік, користувачеві, ми повинні скоротити час виконання коду, таким чином, щоб Виконання + Компіляція до машинного коду були нижчими лише на етапі виконання рідного робочого потоку. Це означає, що ми повинні оптимізувати код краще у компіляції JIT, ніж у власній компіляції.
Це, однак, досить нездійсненно, оскільки для здійснення необхідних оптимізацій для прискорення виконання ми повинні витрачати більше часу на компіляцію до стадії машинного коду, і, таким чином, будь-який час, коли ми економимо в результаті оптимізованого коду, фактично втрачається, як ми додаємо його до компіляції. Іншими словами, "повільність" рішення, заснованого на JIT, пов'язане не лише з додатковим часом для компіляції JIT, але і код, що створюється цією компіляцією, працює повільніше, ніж нативне рішення.
Я буду використовувати приклад: Реєструвати розподіл. Оскільки доступ до пам’яті в тисячі разів повільніше, ніж доступ до реєстрації, ми в ідеалі хочемо використовувати регістри, де це можливо, і маємо якомога менше доступу до пам'яті, але у нас є обмежена кількість регістрів, і ми повинні перелити стан у пам'ять, коли нам це потрібно реєстр. Якщо ми використовуємо алгоритм розподілу реєстру, який займає 200 мс для обчислення, і в результаті ми економимо 2 мс часу виконання - ми не використовуємо найкраще час для компілятора JIT. Такі рішення, як алгоритм Chaitin, який може створювати високооптимізований код, є непридатними.
Роль компілятора JIT полягає в тому, щоб досягти найкращого балансу між часом компіляції та якістю виробленого коду, однак із великим ухилом щодо швидкого часу компіляції, оскільки ви не хочете залишати користувача в очікуванні. Продуктивність виконуваного коду у випадку JIT повільніше, оскільки власний компілятор не обмежений (значно) часом оптимізації коду, тому вільний у використанні найкращі алгоритми. Можливість того, що загальна компіляція + виконання для компілятора JIT може бити лише час виконання для власне складеного коду, фактично 0.
Але наші віртуальні машини не обмежуються лише компіляцією JIT. Вони використовують заздалегідь методи компіляції, кешування, гарячу заміну та адаптивну оптимізацію. Тож давайте модифікуємо нашу заяву, що продуктивність - це те, що бачить користувач, і обмежимо її часом, необхідним для виконання програми (припустимо, ми склали AOT). Ми можемо ефективно зробити виконаний код еквівалентним нативному компілятору (а може, і краще?). Велика претензія на VM полягає в тому, що вони, можливо, зможуть створити більш якісний код, ніж власний компілятор, оскільки він має доступ до додаткової інформації - про запущений процес, наприклад про те, як часто може виконуватися певна функція. Потім VM може застосувати адаптаційні оптимізації до найважливішого коду за допомогою гарячої заміни.
Однак у цьому аргументі є проблема - він передбачає, що оптимізація, орієнтована на профіль, тощо - це щось унікальне для віртуальних машин, що не відповідає дійсності. Ми також можемо застосувати його до нашої компіляції - склавши нашу програму з увімкненим профілюванням, записуючи інформацію, а потім перекомпілюйте програму з цим профілем. Напевно, також варто зазначити, що гаряча заміна коду - це не те, що може робити лише компілятор JIT, ми можемо це зробити за власним кодом - хоча рішення на базі JIT для цього є більш доступними та набагато простішими для розробника. Отже, велике питання: Чи може VM запропонувати нам певну інформацію, яку не може зробити нативна компіляція, що може підвищити ефективність нашого коду?
Я сам цього не бачу. Ми також можемо застосувати більшість методів типового VM до рідного коду - хоча процес є більш задіяним. Аналогічно, ми можемо застосувати будь-які оптимізації нативного компілятора назад до VM, який використовує компіляцію AOT або адаптивну оптимізацію. Реальність полягає в тому, що різниця між кодом, котрий працює заздалегідь, і тим, що він працює в VM, не така вже й велика, як ми вважали. Вони в кінцевому підсумку призводять до того ж результату, але вони діють інший підхід, щоб дістатися туди. VM використовує ітеративний підхід для створення оптимізованого коду, де нативний компілятор очікує його з самого початку (і може бути вдосконалений за допомогою ітеративного підходу).
Програміст на C ++ може стверджувати, що йому потрібні оптимізації під час керування, і він не повинен чекати, коли VM з'ясує, як їх зробити, якщо вони взагалі є. Це, мабуть, справедливий момент у нашій сучасній технології, оскільки поточний рівень оптимізацій у наших віртуальних машинах поступається тому, що можуть запропонувати локальні компілятори - але це не завжди може бути так, якщо рішення AOT у наших віртуальних машинах покращуються тощо.