Saya mengerti bahwa ada sumber daya yang memukul dari menggunakan RTTI, tetapi seberapa besar itu? Di mana-mana saya telah melihat hanya mengatakan bahwa "RTTI itu mahal," tetapi tidak satupun dari mereka yang benar-benar memberikan tolok ukur atau data kuantitatif yang membutuhkan memori, waktu prosesor, atau kecepatan.
Jadi, seberapa mahalkah RTTI? Saya mungkin menggunakannya pada sistem tertanam di mana saya hanya memiliki 4MB RAM, jadi setiap bit diperhitungkan.
Sunting: Sesuai jawaban S. Lott , akan lebih baik jika saya memasukkan apa yang sebenarnya saya lakukan. Saya menggunakan kelas untuk mengirimkan data dengan panjang yang berbeda dan yang dapat melakukan tindakan yang berbeda , sehingga akan sulit untuk melakukan ini hanya dengan menggunakan fungsi virtual. Tampaknya menggunakan beberapadynamic_cast
s dapat mengatasi masalah ini dengan membiarkan kelas turunan yang berbeda melewati tingkat yang berbeda namun masih memungkinkan mereka untuk bertindak sepenuhnya berbeda.
Dari pemahaman saya, dynamic_cast
gunakan RTTI, jadi saya bertanya-tanya bagaimana layaknya untuk digunakan pada sistem terbatas.
sumber
dynamic_cast
di C ++, dan sekarang, 9 dari 10 kali ketika saya "istirahat" program dengan debugger, itu rusak di dalam fungsi internal dinamis-cor. Sangat lambat.Jawaban:
Terlepas dari kompiler, Anda selalu dapat menghemat saat runtime jika Anda mampu melakukannya
dari pada
Yang pertama hanya melibatkan satu perbandingan
std::type_info
; yang terakhir harus melibatkan melintasi pohon warisan ditambah perbandingan.Melewati itu ... seperti yang dikatakan semua orang, penggunaan sumber daya adalah implementasi khusus.
Saya setuju dengan komentar orang lain bahwa pengirim harus menghindari RTTI karena alasan desain. Namun, ada yang alasan yang baik untuk menggunakan RTTI (terutama karena meningkatkan :: ada). Itu dalam pikiran, itu berguna untuk mengetahui penggunaan sumber daya yang sebenarnya dalam implementasi umum.
Baru-baru ini saya melakukan banyak penelitian tentang RTTI di GCC.
tl; dr: RTTI di GCC menggunakan ruang yang dapat diabaikan dan
typeid(a) == typeid(b)
sangat cepat, di banyak platform (Linux, BSD, dan mungkin platform yang disematkan, tetapi tidak mingw32). Jika Anda tahu Anda akan selalu berada di platform yang diberkati, RTTI sangat dekat dengan gratis.Detail berpasir:
GCC lebih suka menggunakan ABI "vendor-neutral" C ++ ABI [1], dan selalu menggunakan ABI ini untuk target Linux dan BSD [2]. Untuk platform yang mendukung ABI ini dan juga hubungan lemah,
typeid()
mengembalikan objek yang konsisten dan unik untuk setiap jenis, bahkan melintasi batas tautan dinamis. Anda dapat menguji&typeid(a) == &typeid(b)
, atau hanya mengandalkan fakta bahwa tes portabeltypeid(a) == typeid(b)
sebenarnya hanya membandingkan pointer secara internal.Dalam ABI pilihan GCC, kelas vtable selalu memegang pointer ke struktur RTTI per-tipe, meskipun mungkin tidak digunakan. Jadi
typeid()
panggilan itu sendiri seharusnya hanya biaya sebanyak pencarian vtable lainnya (sama dengan memanggil fungsi anggota virtual), dan dukungan RTTI tidak boleh menggunakan ruang tambahan untuk setiap objek.Dari apa yang saya dapat melihat, struktur RTTI yang digunakan oleh GCC (ini semua adalah subkelas
std::type_info
) hanya menampung beberapa byte untuk setiap jenis, selain dari namanya. Tidak jelas bagi saya apakah nama-nama itu ada dalam kode keluaran-fno-rtti
. Either way, perubahan ukuran biner yang dikompilasi harus mencerminkan perubahan dalam penggunaan memori runtime.Eksperimen cepat (menggunakan GCC 4.4.3 pada Ubuntu 10.04 64-bit) menunjukkan bahwa
-fno-rtti
sebenarnya meningkatkan ukuran biner dari program pengujian sederhana beberapa ratus byte. Ini terjadi secara konsisten di kombinasi-g
dan-O3
. Saya tidak yakin mengapa ukurannya akan meningkat; satu kemungkinan adalah bahwa kode STL GCC berperilaku berbeda tanpa RTTI (karena pengecualian tidak akan berfungsi).[1] Dikenal sebagai Itanium C ++ ABI, yang didokumentasikan di http://www.codesourcery.com/public/cxx-abi/abi.html . Nama-nama itu sangat membingungkan: namanya mengacu pada arsitektur pengembangan asli, meskipun spesifikasi ABI bekerja pada banyak arsitektur termasuk i686 / x86_64. Komentar dalam sumber internal GCC dan kode STL menyebut Itanium sebagai ABI "baru" berbeda dengan "lama" yang mereka gunakan sebelumnya. Lebih buruk lagi, ABI "baru" / Itanium merujuk ke semua versi yang tersedia melalui
-fabi-version
; ABI "lama" mendahului versi ini. GCC mengadopsi ABI Itanium / versi / "baru" dalam versi 3.0; ABI "lama" digunakan pada 2.95 dan sebelumnya, jika saya membaca changelog mereka dengan benar.[2] Saya tidak bisa menemukan
std::type_info
stabilitas daftar sumber daya objek berdasarkan platform. Untuk kompiler saya memiliki akses ke, saya menggunakan berikut:echo "#include <typeinfo>" | gcc -E -dM -x c++ -c - | grep GXX_MERGED_TYPEINFO_NAMES
. Makro ini mengontrol perilakuoperator==
untukstd::type_info
dalam STL GCC, mulai dari GCC 3.0. Saya memang menemukan bahwa mingw32-gcc mematuhi Windows C ++ ABI, di manastd::type_info
objek tidak unik untuk jenis di seluruh DLL;typeid(a) == typeid(b)
panggilan distrcmp
bawah penutup. Saya berspekulasi bahwa pada target tertanam program tunggal seperti AVR, di mana tidak ada kode untuk ditautkan,std::type_info
objek selalu stabil.sumber
int
dan tidak adathe latter necessarily involves traversing an inheritance tree plus comparisons
. @CoryB Anda dapat "mampu" melakukannya ketika Anda tidak perlu mendukung casting dari seluruh pohon warisan. Misalnya jika Anda ingin menemukan semua item tipe X dalam koleksi, tetapi bukan item yang berasal dari X, maka yang harus Anda gunakan adalah yang pertama. Jika Anda juga perlu menemukan semua turunan turunan, Anda harus menggunakan yang terakhir.Mungkin angka-angka ini akan membantu.
Saya sedang melakukan tes cepat menggunakan ini:
5 Kasus diuji:
5 hanyalah kode aktual saya, karena saya perlu membuat objek jenis itu sebelum memeriksa apakah mirip dengan yang sudah saya miliki.
Tanpa Optimasi
Yang hasilnya (saya telah rata-rata beberapa kali berjalan):
Jadi kesimpulannya adalah:
typeid()
lebih dari dua kali lebih cepat daripadadyncamic_cast
.Dengan Optimasi (-Os)
Jadi kesimpulannya adalah:
typeid()
hampir x20 lebih cepat daripadadyncamic_cast
.Grafik
Kode
Seperti yang diminta dalam komentar, kode di bawah ini (agak berantakan, tetapi berfungsi). 'FastDelegate.h' tersedia dari sini .
sumber
class a {}; class b : public a {}; class c : public b {};
ketika target adalah instance daric
akan berfungsi dengan baik ketika menguji untuk kelasb
dengandynamic_cast
, tetapi tidak dengantypeid
solusi. Masih masuk akal, +1Itu tergantung pada skala hal. Sebagian besar hanya beberapa cek dan beberapa pointer pointer. Di sebagian besar implementasi, di bagian atas setiap objek yang memiliki fungsi virtual, ada pointer ke vtable yang menyimpan daftar pointer ke semua implementasi fungsi virtual di kelas itu. Saya kira sebagian besar implementasi akan menggunakan ini untuk menyimpan pointer lain ke struktur type_info untuk kelas.
Misalnya di pseudo-c ++:
Secara umum argumen nyata terhadap RTTI adalah ketidakmampuan harus mengubah kode di mana-mana setiap kali Anda menambahkan kelas turunan baru. Alih-alih beralih pernyataan di mana-mana, faktorkan itu menjadi fungsi virtual. Ini memindahkan semua kode yang berbeda antara kelas ke dalam kelas itu sendiri, sehingga derivasi baru hanya perlu mengesampingkan semua fungsi virtual untuk menjadi kelas yang berfungsi penuh. Jika Anda pernah harus mencari basis kode yang besar untuk setiap kali seseorang memeriksa jenis kelas dan melakukan sesuatu yang berbeda, Anda akan segera belajar untuk menjauh dari gaya pemrograman itu.
Jika kompiler Anda memungkinkan Anda benar-benar mematikan RTTI, penghematan ukuran kode akhir yang dihasilkan bisa jadi signifikan, dengan ruang RAM yang kecil. Kompilator perlu membuat struktur type_info untuk setiap kelas tunggal dengan fungsi virtual. Jika Anda mematikan RTTI, semua struktur ini tidak perlu dimasukkan dalam gambar yang dapat dieksekusi.
sumber
Nah, profiler tidak pernah bohong.
Karena saya memiliki hierarki yang cukup stabil yaitu 18-20 jenis yang tidak banyak berubah, saya bertanya-tanya apakah hanya menggunakan anggota enum'd sederhana akan melakukan trik dan menghindari biaya RTTI yang konon "tinggi". Saya ragu apakah RTTI sebenarnya lebih mahal dari sekadar
if
pernyataan yang diperkenalkannya. Ya ampun, ya.Ternyata RTTI adalah mahal, jauh lebih mahal daripada setara
if
pernyataan atau sederhanaswitch
pada variabel primitif di C ++. Jadi jawaban S. Lott ini tidak sepenuhnya benar, ada adalah biaya tambahan untuk RTTI, dan itu bukan karena hanya memilikiif
pernyataan dalam campuran. Itu karena RTTI sangat mahal.Tes ini dilakukan pada kompiler Apple LLVM 5.0, dengan optimisasi stok dihidupkan (pengaturan mode rilis default).
Jadi, saya memiliki di bawah 2 fungsi, yang masing-masing menggambarkan tipe konkret objek baik melalui 1) RTTI atau 2) saklar sederhana. Itu melakukannya 50.000.000 kali. Tanpa basa-basi lagi, saya sajikan kepada Anda runtimes relatif untuk 50.000.000 berjalan.
Itu benar,
dynamicCasts
butuh 94% dari runtime. SementararegularSwitch
blok hanya butuh 3,3% .Singkat cerita: Jika Anda mampu membeli energi untuk menghubungkan tipe
enum
'd' seperti yang saya lakukan di bawah ini, saya mungkin akan merekomendasikannya, jika Anda perlu melakukan RTTI dan kinerja sangat penting. Hanya perlu menetapkan anggota sekali (pastikan untuk mendapatkannya melalui semua konstruktor ), dan pastikan untuk tidak pernah menulisnya sesudahnya.Yang mengatakan, melakukan ini seharusnya tidak mengacaukan praktik OOP Anda .. itu hanya dimaksudkan untuk digunakan ketika informasi jenis tidak tersedia dan Anda menemukan diri Anda terpojok untuk menggunakan RTTI.
sumber
Cara standar:
RTTI standar mahal karena bergantung pada melakukan perbandingan string yang mendasarinya dan dengan demikian kecepatan RTTI dapat bervariasi tergantung pada panjang nama kelas.
Alasan mengapa perbandingan string digunakan adalah untuk membuatnya bekerja secara konsisten melintasi batas-batas perpustakaan / DLL. Jika Anda membuat aplikasi secara statis dan / atau Anda menggunakan kompiler tertentu maka Anda mungkin dapat menggunakan:
Yang tidak dijamin berfungsi (tidak akan pernah memberikan false positive, tetapi bisa memberikan false negative) tetapi bisa mencapai 15 kali lebih cepat. Ini bergantung pada implementasi typeid () untuk bekerja dengan cara tertentu dan semua yang Anda lakukan adalah membandingkan pointer char internal. Ini terkadang juga setara dengan:
Namun Anda dapat menggunakan hibrida dengan aman yang akan sangat cepat jika jenisnya cocok, dan akan menjadi kasus terburuk untuk jenis yang tidak cocok:
Untuk memahami apakah Anda perlu mengoptimalkan ini, Anda perlu melihat berapa banyak waktu Anda dihabiskan untuk mendapatkan paket baru, dibandingkan dengan waktu yang dibutuhkan untuk memproses paket. Dalam kebanyakan kasus, perbandingan string mungkin bukan overhead yang besar. (tergantung pada kelas Anda atau namespace :: panjang nama kelas)
Cara teraman untuk mengoptimalkan ini adalah dengan mengimplementasikan typeid Anda sendiri sebagai int (atau enum Type: int) sebagai bagian dari kelas Basis Anda dan menggunakannya untuk menentukan jenis kelas, dan kemudian cukup gunakan static_cast <> atau reinterpret_cast < >
Bagi saya perbedaannya kira-kira 15 kali pada MS VS 2005 C ++ SP1 yang tidak dioptimalkan.
sumber
typeid::operator
bekerja . GCC pada platform yang didukung, misalnya, sudah menggunakan perbandinganchar *
s, tanpa kami memaksakannya - gcc.gnu.org/onlinedocs/gcc-4.6.3/libstdc++/api/… . Tentu, cara Anda membuat MSVC berperilaku jauh lebih baik daripada default pada platform Anda, jadi kudos, dan saya tidak tahu apa "beberapa target" yang menggunakan pointer secara asli adalah ... tapi poin saya adalah perilaku MSVC tidak dengan cara apa pun "Standar".Untuk pemeriksaan sederhana, RTTI bisa semurah perbandingan pointer. Untuk pemeriksaan warisan, ini bisa semahal
strcmp
untuk setiap jenis dalam pohon warisan jika Anda menggunakandynamic_cast
dari atas ke bawah dalam satu implementasi di luar sana.Anda juga dapat mengurangi overhead dengan tidak menggunakan
dynamic_cast
dan sebagai gantinya memeriksa jenis secara eksplisit melalui & typeid (...) == & typeid (type). Meskipun itu tidak selalu berfungsi untuk .dlls atau kode lain yang dimuat secara dinamis, itu bisa sangat cepat untuk hal-hal yang terhubung secara statis.Meskipun pada saat itu rasanya seperti menggunakan pernyataan switch, jadi begitulah.
sumber
Itu selalu yang terbaik untuk mengukur hal-hal. Dalam kode berikut, di bawah g ++, penggunaan identifikasi tipe kode tangan tampaknya sekitar tiga kali lebih cepat dari RTTI. Saya yakin bahwa implementasi kode tangan yang lebih realistis menggunakan string daripada karakter akan lebih lambat, membawa waktu dekat.
sumber
Beberapa waktu lalu saya mengukur biaya waktu untuk RTTI dalam kasus spesifik MSVC dan GCC untuk PowerPC 3 GHz. Dalam tes saya berlari (aplikasi C ++ yang cukup besar dengan pohon kelas yang dalam), masing-masing
dynamic_cast<>
biaya antara 0,8μs dan 2μs, tergantung pada apakah itu mengenai atau tidak terjawab.sumber
Itu sepenuhnya tergantung pada kompiler yang Anda gunakan. Saya mengerti bahwa beberapa menggunakan perbandingan string, dan yang lainnya menggunakan algoritma nyata.
Satu-satunya harapan Anda adalah menulis contoh program dan melihat apa yang dilakukan kompiler Anda (atau setidaknya menentukan berapa banyak waktu yang diperlukan untuk mengeksekusi sejuta
dynamic_casts
atau sejutatypeid
s).sumber
RTTI bisa murah dan tidak perlu strcmp. Compiler membatasi tes untuk melakukan hierarki yang sebenarnya, dalam urutan terbalik. Jadi jika Anda memiliki kelas C yang merupakan anak dari kelas B yang merupakan anak dari kelas A, dynamic_cast dari A * ptr ke C * ptr menyiratkan hanya satu perbandingan pointer dan bukan dua (BTW, hanya pointer tabel vptr adalah dibandingkan). Tes ini seperti "if (vptr_of_obj == vptr_of_C) return (C *) obj"
Contoh lain, jika kita mencoba dynamic_cast dari A * ke B *. Dalam hal ini, kompiler akan memeriksa kedua kasus (obj menjadi C, dan obj menjadi B) secara bergantian. Ini juga dapat disederhanakan menjadi satu pengujian (sebagian besar kali), karena tabel fungsi virtual dibuat sebagai agregasi, sehingga pengujian dilanjutkan ke "if (offset_of (vptr_of_obj, B) == vptr_of_B)" dengan
offset_of = return sizeof (vptr_table)> = sizeof (vptr_of_B)? vptr_of_new_methods_in_B: 0
Tata letak memori
Bagaimana kompiler mengetahui untuk mengoptimalkan ini pada waktu kompilasi?
Pada waktu kompilasi, kompiler mengetahui hierarki objek saat ini, sehingga ia menolak untuk mengkompilasi berbagai tipe hierarki dynamic_casting. Maka itu hanya harus menangani kedalaman hirarki, dan menambahkan jumlah tes terbalik untuk mencocokkan kedalaman tersebut.
Misalnya, ini tidak mengkompilasi:
sumber
RTTI bisa menjadi "mahal" karena Anda telah menambahkan pernyataan jika setiap kali Anda melakukan perbandingan RTTI. Dalam iterasi yang sangat bersarang, ini bisa mahal. Dalam sesuatu yang tidak pernah dieksekusi dalam satu lingkaran itu pada dasarnya gratis.
Pilihannya adalah menggunakan desain polimorfik yang tepat, menghilangkan pernyataan if. Dalam loop yang sangat bersarang, ini sangat penting untuk kinerja. Kalau tidak, itu tidak masalah.
RTTI juga mahal karena dapat mengaburkan hirarki subkelas (jika ada). Itu dapat memiliki efek samping dari menghapus "berorientasi objek" dari "pemrograman berorientasi objek".
sumber
if
pernyataan yang Anda perkenalkan saat Anda memeriksa informasi jenis runtime dengan cara ini.