По-перше, більшість 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, і для впровадження кожного байтового коду потрібно менше інструкцій машинного коду, байт-код повинен виграти (я спрощую, але, думаю, загальна ідея повинна все-таки бути очевидним).