По-перше, більшість JVM включають компілятор, тому "інтерпретований байт-код" насправді є досить рідкісним (принаймні, в бенчмарковому коді - це не так вже й рідко в реальному житті, де ваш код, як правило, більше кількох тривіальних циклів, які повторюються надзвичайно часто ).
По-друге, досить велика кількість задіяних орієнтирів здається досить упередженою (чи то наміром, чи некомпетентністю, я не можу сказати). Так, наприклад, років тому я переглянув частину вихідного коду, пов’язаного з одного із посилань, які ви опублікували. Він мав такий код:
init0 = (int*)calloc(max_x,sizeof(int));
init1 = (int*)calloc(max_x,sizeof(int));
init2 = (int*)calloc(max_x,sizeof(int));
for (x=0; x<max_x; x++) {
init2[x] = 0;
init1[x] = 0;
init0[x] = 0;
}
Оскільки calloc
забезпечує пам'ять, яка вже занулена, використовувати for
цикл до нуля, це, очевидно, марно. Після цього (якщо пам'ять слугує), заповнюючи пам'ять іншими даними в будь-якому випадку (і жодна залежність від того, щоб вона була нульовою), тому все нулювання все одно було абсолютно непотрібним. Заміна вищевказаного коду на просту malloc
(як і будь-яка розумна людина, як би колись починала з) покращила швидкість версії C ++, достатня для перемоги над версією Java (з досить широким відривом, якщо використовується пам'ять).
Розгляньте (для іншого прикладу) methcall
тест, використаний у записі блогу у вашому останньому посиланні. Незважаючи на назву (і як це може виглядати), версія C ++ взагалі не дуже сильно оцінює накладні виклики методів. Частина коду, яка виявляється критичною, знаходиться в класі Toggle:
class Toggle {
public:
Toggle(bool start_state) : state(start_state) { }
virtual ~Toggle() { }
bool value() {
return(state);
}
virtual Toggle& activate() {
state = !state;
return(*this);
}
bool state;
};
Критична частина виявляється найвищою state = !state;
. Розглянемо, що відбувається, коли ми змінимо код, щоб кодувати стан як int
замість bool
:
class Toggle {
enum names{ bfalse = -1, btrue = 1};
const static names values[2];
int state;
public:
Toggle(bool start_state) : state(values[start_state])
{ }
virtual ~Toggle() { }
bool value() { return state==btrue; }
virtual Toggle& activate() {
state = -state;
return(*this);
}
};
Ця незначна зміна покращує загальну швидкість приблизно на 5: 1 . Навіть незважаючи на те, що тест призначений для вимірювання часу виклику методу, насправді більшість того, що він вимірював, був час для перетворення між int
і bool
. Я, безумовно, погоджуюся, що неефективність, яку показує оригінал, прикро - але, враховуючи, як рідко це здається в реальному коді, і легкість, з якою це можна виправити, коли / якщо він виникає, мені важко замислитися це як багато що означає.
У випадку, якщо хтось вирішить повторно запустити базові показники, я також повинен додати, що існує майже однаково тривіальна модифікація версії Java, яка виробляє (або принаймні за один раз виробляється - я не повторно запускав тести з недавній JVM для підтвердження, що вони все ще роблять) досить істотне поліпшення і версії Java. Версія Java має NthToggle :: activate (), який виглядає приблизно так:
public Toggle activate() {
this.counter += 1;
if (this.counter >= this.count_max) {
this.state = !this.state;
this.counter = 0;
}
return(this);
}
Зміна цього на виклик базової функції замість маніпулювання this.state
безпосередньо дає досить значне підвищення швидкості (хоча й недостатньо, щоб не відставати від модифікованої версії C ++).
Отже, що ми закінчуємо - помилкове припущення про інтерпретовані байтові коди порівняно з деякими найгіршими тестами (які я коли-небудь бачив). Це не дає значущого результату.
Мій власний досвід полягає в тому, що з однаково досвідченими програмістами, які приділяють однакову увагу оптимізації, C ++ буде бити Java частіше, ніж ні - але (принаймні між цими двома), мова рідко зробить стільки ж різниці, як програмісти та дизайн. Посилання, що цитуються, розповідають більше про (не) компетентність / (не) чесність їх авторів, ніж про мови, які вони хочуть орієнтувати.
[Редагувати: Як мається на увазі в одному місці вище, але ніколи не зазначалося так прямо, як я, мабуть, маю, результати, які я цитую, - це ті результати, які я отримав, коли тестував це ~ 5 років тому, використовуючи C ++ та Java-реалізації, які були поточними на той час . Я не повторював тести з поточними реалізаціями. Однак огляд вказує на те, що код не було виправлено, тому все, що було б змінено, - це можливість компілятора приховати проблеми в коді.]
Якщо ми будемо ігнорувати приклади Java, проте, це дійсно можливо інтерпретувати код , щоб працювати швидше , ніж скомпільований код (хоча важко і дещо незвично).
Звичайний спосіб цього відбувається, що інтерпретується код набагато компактніше, ніж машинний код, або він працює на центральному процесорі, який має більший кеш даних, ніж кеш-код.
У такому випадку невеликий інтерпретатор (наприклад, внутрішній інтерпретатор реалізації Forth) може мати можливість повністю вміститися в кеш-коді, а програма, яку він інтерпретує, повністю вписується в кеш даних. Кеш, як правило, швидший, ніж основна пам'ять, на коефіцієнт щонайменше 10, а часто і набагато більше (коефіцієнт 100 вже не особливо рідкісний).
Отже, якщо кеш швидше, ніж основна пам'ять, на коефіцієнт N, і для впровадження кожного байтового коду потрібно менше інструкцій машинного коду, байт-код повинен виграти (я спрощую, але, думаю, загальна ідея повинна все-таки бути очевидним).