Berapa mahal RTTI?

152

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_castgunakan RTTI, jadi saya bertanya-tanya bagaimana layaknya untuk digunakan pada sistem terbatas.

Cristián Romo
sumber
1
Mengikuti dari suntingan Anda - sangat sering ketika saya menemukan diri saya melakukan beberapa pemeran dinamis saya menyadari bahwa menggunakan pola Pengunjung meluruskan hal-hal lagi. Bisakah itu bekerja untuk Anda?
philsquared
4
Saya akan begini - saya baru mulai menggunakan dynamic_castdi C ++, dan sekarang, 9 dari 10 kali ketika saya "istirahat" program dengan debugger, itu rusak di dalam fungsi internal dinamis-cor. Sangat lambat.
user541686
3
RTTI = "informasi jenis run-time", omong-omong.
Noumenon

Jawaban:

115

Terlepas dari kompiler, Anda selalu dapat menghemat saat runtime jika Anda mampu melakukannya

if (typeid(a) == typeid(b)) {
  B* ba = static_cast<B*>(&a);
  etc;
}

dari pada

B* ba = dynamic_cast<B*>(&a);
if (ba) {
  etc;
}

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 portabel typeid(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-rttisebenarnya meningkatkan ukuran biner dari program pengujian sederhana beberapa ratus byte. Ini terjadi secara konsisten di kombinasi -gdan -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_infostabilitas 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 perilaku operator==untuk std::type_infodalam STL GCC, mulai dari GCC 3.0. Saya memang menemukan bahwa mingw32-gcc mematuhi Windows C ++ ABI, di mana std::type_infoobjek tidak unik untuk jenis di seluruh DLL; typeid(a) == typeid(b)panggilan di strcmpbawah penutup. Saya berspekulasi bahwa pada target tertanam program tunggal seperti AVR, di mana tidak ada kode untuk ditautkan, std::type_infoobjek selalu stabil.

sbrudenell
sumber
6
Pengecualian bekerja tanpa RTTI. (Anda diizinkan untuk melempar intdan tidak ada
tabel di dalamnya
3
@Dupuplikator: Namun, ketika saya mematikan RTTI di kompiler saya, mereka bekerja dengan baik. Maaf mengecewakanmu.
Billy ONeal
5
Mekanisme penanganan pengecualian harus dapat bekerja dengan semua jenis yang memenuhi beberapa persyaratan dasar. Anda bebas menyarankan cara menangani pelemparan dan penangkapan pengecualian dari tipe arbitrer melintasi batas modul tanpa RTTI. Harap pertimbangkan bahwa casting atas dan bawah diperlukan.
Deduplicator
15
typeid (a) == typeid (b) TIDAK sama dengan B * ba = dynamic_cast <B *> (& a). Cobalah pada objek dengan multiple inheritance sebagai level acak pada pohon kelas turunan dan Anda akan menemukan typeid () == typeid () tidak akan menghasilkan positif. dynamic_cast adalah satu-satunya cara untuk mencari pohon warisan secara nyata. Berhentilah memikirkan potensi penghematan dengan menonaktifkan RTTI dan gunakan saja. Jika Anda kelebihan kapasitas maka optimalkan kode Anda mengasapi. Cobalah untuk menghindari penggunaan dynamic_cast di dalam loop internal atau kode penting kinerja lainnya dan Anda akan baik-baik saja.
mysticcoder
3
@ mcoder Itu sebabnya artikel itu secara eksplisit menyatakan itu the 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.
Aidiakapi
48

Mungkin angka-angka ini akan membantu.

Saya sedang melakukan tes cepat menggunakan ini:

  • GCC Clock () + Profiler XCode.
  • 100.000.000 iterasi loop.
  • 2 x 2,66 GHz Dual-Core Intel Xeon.
  • Kelas yang dimaksud berasal dari kelas dasar tunggal.
  • typeid (). name () mengembalikan "N12fastdelegate13FastDelegate1IivEE"

5 Kasus diuji:

1) dynamic_cast< FireType* >( mDelegate )
2) typeid( *iDelegate ) == typeid( *mDelegate )
3) typeid( *iDelegate ).name() == typeid( *mDelegate ).name()
4) &typeid( *iDelegate ) == &typeid( *mDelegate )
5) { 
       fastdelegate::FastDelegateBase *iDelegate;
       iDelegate = new fastdelegate::FastDelegate1< t1 >;
       typeid( *iDelegate ) == typeid( *mDelegate )
   }

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):

1)  1,840,000 Ticks (~2  Seconds) - dynamic_cast
2)    870,000 Ticks (~1  Second)  - typeid()
3)    890,000 Ticks (~1  Second)  - typeid().name()
4)    615,000 Ticks (~1  Second)  - &typeid()
5) 14,261,000 Ticks (~23 Seconds) - typeid() with extra variable allocations.

Jadi kesimpulannya adalah:

  • Untuk kasus cor sederhana tanpa optimasi typeid()lebih dari dua kali lebih cepat daripada dyncamic_cast.
  • Pada mesin modern, perbedaan antara keduanya adalah sekitar 1 nanodetik (sepersejuta milidetik).

Dengan Optimasi (-Os)

1)  1,356,000 Ticks - dynamic_cast
2)     76,000 Ticks - typeid()
3)     76,000 Ticks - typeid().name()
4)     75,000 Ticks - &typeid()
5)     75,000 Ticks - typeid() with extra variable allocations.

Jadi kesimpulannya adalah:

  • Untuk kasus cor sederhana dengan optimasi, typeid()hampir x20 lebih cepat daripada dyncamic_cast.

Grafik

masukkan deskripsi gambar di sini

Kode

Seperti yang diminta dalam komentar, kode di bawah ini (agak berantakan, tetapi berfungsi). 'FastDelegate.h' tersedia dari sini .

#include <iostream>
#include "FastDelegate.h"
#include "cycle.h"
#include "time.h"

// Undefine for typeid checks
#define CAST

class ZoomManager
{
public:
    template < class Observer, class t1 >
    void Subscribe( void *aObj, void (Observer::*func )( t1 a1 ) )
    {
        mDelegate = new fastdelegate::FastDelegate1< t1 >;
        
        std::cout << "Subscribe\n";
        Fire( true );
    }
    
    template< class t1 >
    void Fire( t1 a1 )
    {
        fastdelegate::FastDelegateBase *iDelegate;
        iDelegate = new fastdelegate::FastDelegate1< t1 >;
        
        int t = 0;
        ticks start = getticks();
        
        clock_t iStart, iEnd;
        
        iStart = clock();
        
        typedef fastdelegate::FastDelegate1< t1 > FireType;
        
        for ( int i = 0; i < 100000000; i++ ) {
        
#ifdef CAST
                if ( dynamic_cast< FireType* >( mDelegate ) )
#else
                // Change this line for comparisons .name() and & comparisons
                if ( typeid( *iDelegate ) == typeid( *mDelegate ) )
#endif
                {
                    t++;
                } else {
                    t--;
                }
        }
        
        iEnd = clock();
        printf("Clock ticks: %i,\n", iEnd - iStart );
        
        std::cout << typeid( *mDelegate ).name()<<"\n";
        
        ticks end = getticks();
        double e = elapsed(start, end);
        std::cout << "Elasped: " << e;
    }
    
    template< class t1, class t2 >
    void Fire( t1 a1, t2 a2 )
    {
        std::cout << "Fire\n";
    }
    
    fastdelegate::FastDelegateBase *mDelegate;
};

class Scaler
{
public:
    Scaler( ZoomManager *aZoomManager ) :
        mZoomManager( aZoomManager ) { }
    
    void Sub()
    {
        mZoomManager->Subscribe( this, &Scaler::OnSizeChanged );
    }
    
    void OnSizeChanged( int X  )
    {
        std::cout << "Yey!\n";        
    }
private:
    ZoomManager *mZoomManager;
};

int main(int argc, const char * argv[])
{
    ZoomManager *iZoomManager = new ZoomManager();
    
    Scaler iScaler( iZoomManager );
    iScaler.Sub();
        
    delete iZoomManager;

    return 0;
}
Izhaki
sumber
1
Tentu saja, para pemain dinamis lebih umum - itu berfungsi jika item lebih diturunkan. Misalnya class a {}; class b : public a {}; class c : public b {};ketika target adalah instance dari cakan berfungsi dengan baik ketika menguji untuk kelas bdengan dynamic_cast, tetapi tidak dengan typeidsolusi. Masih masuk akal, +1
Billy ONeal
34
Benchmark ini sepenuhnya palsu dengan optimasi : cek typeid adalah invarian loop dan dipindahkan dari loop. Ini tidak menarik sama sekali, ini adalah pembandingan standar tidak-tidak.
Pasang kembali Monica
3
@Kuba: Maka patokannya palsu. Itu bukan alasan untuk melakukan pembandingan dengan menonaktifkan optimasi; itulah alasan untuk menulis tolok ukur yang lebih baik.
Billy ONeal
3
lagi-lagi ini gagal. "Untuk kasus cor sederhana dengan optimasi, typeid () hampir x20 lebih cepat daripada dyncamic_cast." mereka TIDAK melakukan hal yang sama. Ada alasan dynamic_cast lebih lambat.
mysticcoder
1
@KubaOber: total +1. ini sangat klasik. dan harus jelas dari tampilan nomor siklus bahwa ini terjadi.
v.oddou
38

Itu 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 ++:

struct Base
{
    virtual ~Base() {}
};

struct Derived
{
    virtual ~Derived() {}
};


int main()
{
    Base *d = new Derived();
    const char *name = typeid(*d).name(); // C++ way

    // faked up way (this won't actually work, but gives an idea of what might be happening in some implementations).
    const vtable *vt = reinterpret_cast<vtable *>(d);
    type_info *ti = vt->typeinfo;
    const char *name = ProcessRawName(ti->name);       
}

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.

Gerhana
sumber
4
1 untuk benar-benar menjelaskan mengapa menggunakan RTTI dianggap sebagai keputusan desain yang buruk, itu tidak cukup jelas bagi saya sebelumnya.
aguazales
6
Jawaban ini adalah pemahaman tingkat rendah dari kekuatan C ++. "Secara umum" dan "Dalam sebagian besar implementasi" yang digunakan secara bebas berarti Anda tidak memikirkan cara menggunakan fitur bahasa dengan baik. Fungsi virtual dan implementasi ulang RTTI bukanlah jawabannya. RTTI adalah jawabannya. Terkadang Anda hanya ingin tahu apakah suatu objek adalah tipe tertentu. Itu sebabnya ada di sana! Jadi, Anda kehilangan beberapa KB RAM untuk beberapa struct type_info. Astaga ...
mysticcoder
16

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 ifpernyataan yang diperkenalkannya. Ya ampun, ya.

Ternyata RTTI adalah mahal, jauh lebih mahal daripada setara ifpernyataan atau sederhana switchpada variabel primitif di C ++. Jadi jawaban S. Lott ini tidak sepenuhnya benar, ada adalah biaya tambahan untuk RTTI, dan itu bukan karena hanya memiliki ifpernyataan 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.

masukkan deskripsi gambar di sini

Itu benar, dynamicCastsbutuh 94% dari runtime. Sementara regularSwitchblok 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.

#include <stdio.h>
#include <vector>
using namespace std;

enum AnimalClassTypeTag
{
  TypeAnimal=1,
  TypeCat=1<<2,TypeBigCat=1<<3,TypeDog=1<<4
} ;

struct Animal
{
  int typeTag ;// really AnimalClassTypeTag, but it will complain at the |= if
               // at the |='s if not int
  Animal() {
    typeTag=TypeAnimal; // start just base Animal.
    // subclass ctors will |= in other types
  }
  virtual ~Animal(){}//make it polymorphic too
} ;

struct Cat : public Animal
{
  Cat(){
    typeTag|=TypeCat; //bitwise OR in the type
  }
} ;

struct BigCat : public Cat
{
  BigCat(){
    typeTag|=TypeBigCat;
  }
} ;

struct Dog : public Animal
{
  Dog(){
    typeTag|=TypeDog;
  }
} ;

typedef unsigned long long ULONGLONG;

void dynamicCasts(vector<Animal*> &zoo, ULONGLONG tests)
{
  ULONGLONG animals=0,cats=0,bigcats=0,dogs=0;
  for( ULONGLONG i = 0 ; i < tests ; i++ )
  {
    for( Animal* an : zoo )
    {
      if( dynamic_cast<Dog*>( an ) )
        dogs++;
      else if( dynamic_cast<BigCat*>( an ) )
        bigcats++;
      else if( dynamic_cast<Cat*>( an ) )
        cats++;
      else //if( dynamic_cast<Animal*>( an ) )
        animals++;
    }
  }

  printf( "%lld animals, %lld cats, %lld bigcats, %lld dogs\n", animals,cats,bigcats,dogs ) ;

}

//*NOTE: I changed from switch to if/else if chain
void regularSwitch(vector<Animal*> &zoo, ULONGLONG tests)
{
  ULONGLONG animals=0,cats=0,bigcats=0,dogs=0;
  for( ULONGLONG i = 0 ; i < tests ; i++ )
  {
    for( Animal* an : zoo )
    {
      if( an->typeTag & TypeDog )
        dogs++;
      else if( an->typeTag & TypeBigCat )
        bigcats++;
      else if( an->typeTag & TypeCat )
        cats++;
      else
        animals++;
    }
  }
  printf( "%lld animals, %lld cats, %lld bigcats, %lld dogs\n", animals,cats,bigcats,dogs ) ;  

}

int main(int argc, const char * argv[])
{
  vector<Animal*> zoo ;

  zoo.push_back( new Animal ) ;
  zoo.push_back( new Cat ) ;
  zoo.push_back( new BigCat ) ;
  zoo.push_back( new Dog ) ;

  ULONGLONG tests=50000000;

  dynamicCasts( zoo, tests ) ;
  regularSwitch( zoo, tests ) ;
}
bobobobo
sumber
13

Cara standar:

cout << (typeid(Base) == typeid(Derived)) << endl;

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:

cout << (typeid(Base).name() == typeid(Derived).name()) << endl;

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:

cout << (&typeid(Base) == &typeid(Derived)) << endl;

Namun Anda dapat menggunakan hibrida dengan aman yang akan sangat cepat jika jenisnya cocok, dan akan menjadi kasus terburuk untuk jenis yang tidak cocok:

cout << ( typeid(Base).name() == typeid(Derived).name() || 
          typeid(Base) == typeid(Derived) ) << endl;

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.

Marius
sumber
2
"Standar RTTI mahal karena bergantung pada melakukan perbandingan string yang mendasarinya" - tidak, tidak ada "Standar" tentang ini; hanya bagaimana implementasi Anda typeid::operatorbekerja . GCC pada platform yang didukung, misalnya, sudah menggunakan perbandingan char *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".
underscore_d
7

Untuk pemeriksaan sederhana, RTTI bisa semurah perbandingan pointer. Untuk pemeriksaan warisan, ini bisa semahal strcmpuntuk setiap jenis dalam pohon warisan jika Anda menggunakan dynamic_castdari atas ke bawah dalam satu implementasi di luar sana.

Anda juga dapat mengurangi overhead dengan tidak menggunakan dynamic_castdan 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.

MSN
sumber
1
Apakah Anda memiliki referensi untuk versi strcmp? Tampaknya sangat tidak efisien dan tidak akurat untuk menggunakan strcmp untuk pemeriksaan tipe.
JaredPar
Dalam implementasi yang buruk yang dapat memiliki beberapa objek type_info per tipe, ia dapat mengimplementasikan bool type_info :: operator == (const type_info & x) const sebagai "! Strcmp (nama (), x.name ())"
Greg Rogers
3
Langkah ke pembongkaran dari dynamic_cast atau typeid (). Operator == untuk MSVC dan Anda akan menekan strcmp di sana. Saya berasumsi itu ada untuk kasus mengerikan di mana Anda membandingkan terhadap jenis yang dikompilasi dalam. Dan itu menggunakan nama hancur, jadi setidaknya itu benar diberi kompiler yang sama.
MSN
1
Anda seharusnya melakukan "typeid (...) == typeid (type)" dan tidak membandingkan alamat
Johannes Schaub - litb
1
Maksud saya adalah bahwa Anda dapat melakukan & typeid (...) == & typeid (bla) sebagai awal dan akan aman. Ini mungkin tidak benar-benar melakukan sesuatu yang berguna karena typeid (...) dapat dihasilkan pada stack, tetapi jika alamatnya sama, maka tipenya sama.
MSN
6

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.

#include <iostream>
using namespace std;

struct Base {
    virtual ~Base() {}
    virtual char Type() const = 0;
};

struct A : public Base {
    char Type() const {
        return 'A';
    }
};

struct B : public Base {;
    char Type() const {
        return 'B';
    }
};

int main() {
    Base * bp = new A;
    int n = 0;
    for ( int i = 0; i < 10000000; i++ ) {
#ifdef RTTI
        if ( A * a = dynamic_cast <A*> ( bp ) ) {
            n++;
        }
#else
        if ( bp->Type() == 'A' ) {
            A * a = static_cast <A*>(bp);
            n++;
        }
#endif
    }
    cout << n << endl;
}

sumber
1
cobalah untuk tidak melakukannya dengan dynamic_cast, tetapi dengan typeid. itu bisa mempercepat kinerja.
Johannes Schaub - litb
1
tetapi menggunakan dynamic_cast lebih realistis, setidaknya melihat kode saya
2
itu melakukan hal yang berbeda: memeriksa juga apakah bp menunjuk ke tipe yang berasal dari A. your == 'A' memeriksa apakah itu menunjuk tepat ke 'A'. saya juga berpikir tes ini agak tidak adil: kompiler dapat dengan mudah melihat bp tidak dapat menunjuk ke sesuatu yang berbeda dari A. tetapi saya pikir itu tidak dioptimalkan di sini.
Johannes Schaub - litb
Lagi pula, saya sudah menguji kode Anda. dan itu memberi saya "0,016" untuk RTTI dan "0,044" untuk panggilan fungsi virtual. (menggunakan -O2)
Johannes Schaub - litb
meskipun mengubahnya untuk menggunakan typeid tidak ada bedanya di sini (masih 0,016s)
Johannes Schaub - litb
4

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.

Crashworks
sumber
2

Jadi, seberapa mahalkah RTTI?

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_castsatau sejuta typeids).

Max Lybbert
sumber
1

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

vptr_of_C = [ vptr_of_A | vptr_of_new_methods_in_B | vptr_of_new_methods_in_C ]

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:

void * something = [...]; 
// Compile time error: Can't convert from something to MyClass, no hierarchy relation
MyClass * c = dynamic_cast<MyClass*>(something);  
X-Ryl669
sumber
-5

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".

S.Lott
sumber
2
Tidak harus - saya akan menggunakannya secara tidak langsung melalui dynamic_cast, dan menjaga hierarki tetap di tempatnya, karena saya perlu downcast karena setiap subtipe perlu memiliki data yang berbeda (ukuran bervariasi) yang harus diterapkan secara berbeda, maka dynamic_cast.
Cristián Romo
1
@ Cristián Romo: Harap perbarui pertanyaan Anda dengan fakta-fakta baru ini. dynamic_cast adalah (kadang-kadang) kejahatan yang diperlukan dalam C ++. Bertanya tentang kinerja RTTI ketika Anda dipaksa untuk melakukannya tidak masuk akal.
S.Lott
@ S.Lott: Diperbarui. Maaf tentang kebingungannya.
Cristián Romo
1
Saya melakukan percobaan tentang ini sekarang - ternyata RTTI secara signifikan lebih mahal daripada ifpernyataan yang Anda perkenalkan saat Anda memeriksa informasi jenis runtime dengan cara ini.
bobobobo