Metode virtual biasanya diimplementasikan melalui apa yang disebut tabel metode virtual (singkatnya vtable), di mana pointer fungsi disimpan. Ini menambahkan tipuan ke panggilan aktual (harus mengambil alamat fungsi untuk memanggil dari vtable, lalu memanggilnya - bukan hanya memanggilnya di depan). Tentu saja, ini membutuhkan waktu dan beberapa kode lagi.
Namun, itu belum tentu menjadi penyebab utama kelambatan. Masalah sebenarnya adalah bahwa kompiler (umumnya / biasanya) tidak dapat mengetahui fungsi mana yang akan dipanggil. Jadi itu tidak dapat menyejajarkan atau melakukan optimasi lainnya. Ini saja mungkin menambah selusin instruksi yang tidak berguna (menyiapkan register, memanggil, kemudian mengembalikan status setelahnya), dan mungkin menghambat optimasi lain yang tampaknya tidak terkait. Selain itu, jika Anda bercabang gila dengan memanggil banyak implementasi berbeda, Anda mengalami hit yang sama dengan yang Anda menderita percabangan seperti gila dengan cara lain: Cache dan prediktor cabang tidak akan membantu Anda, cabang-cabangnya akan memakan waktu lebih lama dari yang dapat diprediksi dengan sempurna cabang.
Besar tetapi : Hits kinerja ini biasanya terlalu kecil untuk diperhitungkan. Mereka patut dipertimbangkan jika Anda ingin membuat kode berkinerja tinggi dan mempertimbangkan untuk menambahkan fungsi virtual yang akan dipanggil pada frekuensi yang mengkhawatirkan. Namun, juga perlu diingat bahwa mengganti panggilan fungsi virtual dengan cara lain bercabang ( if .. else
, switch
, fungsi pointer, dll) tidak akan memecahkan masalah mendasar - mungkin sangat baik menjadi lebih lambat. Masalahnya (jika ada sama sekali) bukan fungsi virtual tetapi (tidak perlu) tipuan.
Sunting: Perbedaan dalam instruksi panggilan dijelaskan dalam jawaban lain. Pada dasarnya, kode untuk panggilan statis ("normal") adalah:
- Salin beberapa register pada stack, untuk memungkinkan fungsi yang dipanggil menggunakan register tersebut.
- Salin argumen ke lokasi yang telah ditentukan, sehingga fungsi yang dipanggil dapat menemukannya terlepas dari mana namanya.
- Dorong alamat pengirim.
- Cabang / lompat ke kode fungsi, yang merupakan alamat waktu kompilasi dan karenanya dikodekan dalam biner oleh kompiler / penghubung.
- Dapatkan nilai pengembalian dari lokasi yang telah ditentukan dan pulihkan register yang ingin kita gunakan.
Panggilan virtual melakukan hal yang persis sama, kecuali bahwa alamat fungsi tidak diketahui pada waktu kompilasi. Sebagai gantinya, beberapa instruksi ...
- Dapatkan pointer vtable, yang menunjuk ke array pointer fungsi (function address), satu untuk setiap fungsi virtual, dari objek.
- Dapatkan alamat fungsi yang benar dari vtable ke register (indeks di mana alamat fungsi yang benar disimpan ditentukan pada waktu kompilasi).
- Lompat ke alamat dalam register itu, daripada melompat ke alamat yang dikodekan dengan keras.
Adapun cabang: Cabang adalah apa pun yang melompat ke instruksi lain, bukan hanya membiarkan instruksi berikutnya dijalankan. Ini termasuk if
,, switch
bagian dari berbagai loop, pemanggilan fungsi, dll. Dan kadang-kadang kompiler mengimplementasikan hal-hal yang tampaknya tidak bercabang dengan cara yang benar-benar membutuhkan cabang di bawah tenda. Lihat Mengapa memproses array yang diurutkan lebih cepat daripada array yang tidak disortir? untuk alasan ini mungkin lambat, apa yang CPU lakukan untuk mengatasi perlambatan ini, dan bagaimana ini bukan obat untuk semua.