Pertama, sebagian besar JVM termasuk kompiler, jadi "interpretasi bytecode" sebenarnya cukup langka (setidaknya dalam kode benchmark - itu tidak cukup langka dalam kehidupan nyata, di mana kode Anda biasanya lebih dari beberapa loop sepele yang diulang sangat sering ).
Kedua, cukup banyak tolok ukur yang terlibat tampaknya cukup bias (apakah dengan niat atau tidak kompeten, saya tidak bisa mengatakannya). Sebagai contoh, bertahun-tahun yang lalu saya melihat beberapa kode sumber yang ditautkan dari salah satu tautan yang Anda posting. Itu kode seperti ini:
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;
}
Karena calloc
menyediakan memori yang sudah memusatkan perhatian, menggunakan for
loop ke nol lagi jelas tidak berguna. Ini diikuti (jika memori berfungsi) dengan mengisi memori dengan data lain (dan tidak ada ketergantungan pada itu yang memusatkan perhatian), jadi semua penekanan itu sama sekali tidak perlu. Mengganti kode di atas dengan yang sederhana malloc
(seperti orang waras yang akan digunakan untuk memulainya) meningkatkan kecepatan versi C ++ cukup untuk mengalahkan versi Java (dengan selisih yang cukup lebar, jika memori berfungsi).
Pertimbangkan (untuk contoh lain) methcall
patokan yang digunakan dalam entri blog di tautan terakhir Anda. Terlepas dari namanya (dan bagaimana hal-hal bahkan terlihat), versi C ++ ini tidak benar - benar mengukur banyak tentang overhead panggilan metode sama sekali. Bagian dari kode yang ternyata kritis adalah di kelas 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;
};
Bagian penting ternyata adalah state = !state;
. Pertimbangkan apa yang terjadi ketika kami mengubah kode untuk menyandikan status sebagai int
alih - alih 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);
}
};
Perubahan kecil ini meningkatkan kecepatan keseluruhan sekitar 5: 1 margin . Meskipun tolok ukur dimaksudkan untuk mengukur waktu pemanggilan metode, pada kenyataannya sebagian besar yang diukur adalah waktu untuk mengkonversi antara int
dan bool
. Saya pasti setuju bahwa ketidakefisienan yang ditunjukkan oleh dokumen asli sangat disayangkan - tetapi mengingat betapa jarang hal itu muncul dalam kode nyata, dan kemudahan untuk memperbaikinya ketika / jika muncul, saya mengalami kesulitan untuk berpikir itu sangat berarti.
Jika ada yang memutuskan untuk menjalankan kembali tolok ukur yang terlibat, saya juga harus menambahkan bahwa ada modifikasi yang hampir sama sepele untuk versi Java yang menghasilkan (atau setidaknya pada satu waktu diproduksi - saya belum menjalankan kembali tes dengan JVM baru-baru ini untuk mengkonfirmasi mereka masih melakukan) peningkatan yang cukup besar dalam versi Java juga. Versi Java memiliki NthToggle :: activate () yang terlihat seperti ini:
public Toggle activate() {
this.counter += 1;
if (this.counter >= this.count_max) {
this.state = !this.state;
this.counter = 0;
}
return(this);
}
Mengubah ini untuk memanggil fungsi dasar alih-alih memanipulasi this.state
secara langsung memberikan peningkatan kecepatan yang cukup besar (meskipun tidak cukup untuk mengikuti versi C ++ yang dimodifikasi).
Jadi, yang akhirnya kita dapatkan adalah asumsi yang salah tentang kode byte yang ditafsirkan vs beberapa tolok ukur terburuk (yang pernah saya lihat). Tidak ada yang memberikan hasil yang berarti.
Pengalaman saya sendiri adalah bahwa dengan programmer yang sama-sama berpengalaman memberikan perhatian yang sama untuk mengoptimalkan, C ++ akan mengalahkan Java lebih sering daripada tidak - tetapi (setidaknya di antara keduanya), bahasa jarang akan membuat perbedaan sebanyak programmer dan desain. Tolok ukur yang dikutip memberi tahu kami lebih banyak tentang kompetensi (dalam) / kejujuran penulis mereka daripada yang mereka lakukan tentang bahasa yang mereka maksud untuk dijadikan patokan.
[Sunting: Seperti yang tersirat di satu tempat di atas tetapi tidak pernah dinyatakan secara langsung seperti yang seharusnya saya miliki, hasil yang saya kutip adalah yang saya dapatkan ketika saya menguji ini ~ 5 tahun yang lalu, menggunakan implementasi C ++ dan Java yang saat ini ada pada saat itu . Saya belum menjalankan kembali tes dengan implementasi saat ini. Pandangan sekilas, menunjukkan bahwa kode belum diperbaiki, jadi semua yang akan berubah adalah kemampuan kompiler untuk menutupi masalah dalam kode.]
Jika kita mengabaikan contoh Java, bagaimanapun, adalah sebenarnya mungkin untuk kode ditafsirkan untuk berjalan lebih cepat daripada kode dikompilasi (meskipun sulit dan agak tidak biasa).
Cara biasa ini terjadi adalah bahwa kode yang ditafsirkan jauh lebih kompak daripada kode mesin, atau itu berjalan pada CPU yang memiliki cache data yang lebih besar daripada kode cache.
Dalam kasus seperti itu, juru bahasa kecil (misalnya, juru bahasa dalam implementasi Keempat) mungkin dapat masuk sepenuhnya dalam cache kode, dan program yang ditafsirkan sepenuhnya cocok dalam cache data. Cache biasanya lebih cepat dari memori utama dengan faktor setidaknya 10, dan seringkali jauh lebih (faktor 100 tidak terlalu jarang lagi).
Jadi, jika cache lebih cepat dari memori utama dengan faktor N, dan dibutuhkan lebih sedikit dari instruksi kode mesin N untuk mengimplementasikan setiap kode byte, kode byte harus menang (saya menyederhanakan, tapi saya pikir ide umum masih harus menjadi jelas).