Насколько дорог RTTI?

152

Я понимаю, что использование RTTI приводит к потере ресурса, но насколько он велик? Везде, где я смотрел, просто говорится, что «RTTI стоит дорого», но ни один из них на самом деле не дает никаких эталонов или количественных данных, касающихся памяти, времени процессора или скорости.

Итак, насколько дорог RTTI? Я мог бы использовать его во встроенной системе, где у меня есть только 4 МБ ОЗУ, поэтому каждый бит считается.

Редактировать: Согласно ответу С. Лотта , было бы лучше, если бы я включил то, что я на самом деле делаю. Я использую класс для передачи данных различной длины, которые могут выполнять различные действия , поэтому было бы трудно сделать это, используя только виртуальные функции. Кажется, что использование нескольких dynamic_casts может решить эту проблему, позволяя различным производным классам проходить через разные уровни, но при этом они могут действовать совершенно по-разному.

dynamic_castНасколько я понимаю, использует RTTI, поэтому мне было интересно, насколько это целесообразно использовать в ограниченной системе.

Кристиан Ромо
источник
1
Исходя из ваших правок - очень часто, когда я выполняю несколько динамических приведений, я понимаю, что использование шаблона Visitor снова исправляет ситуацию. Может ли это работать на вас?
philsquared
4
Я скажу это так - я только начал использовать dynamic_castв C ++, и теперь, 9 из 10 раз, когда я «ломаю» программу с помощью отладчика, она ломается внутри внутренней функции динамического приведения. Это чертовски медленно.
user541686 9.09.12
3
RTTI = "информация о типе времени выполнения", кстати.
Нумен

Ответы:

115

Независимо от компилятора, вы всегда можете сэкономить на времени выполнения, если можете себе это позволить

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

вместо того

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

Первое включает в себя только одно сравнение std::type_info; последнее обязательно включает обход дерева наследования и сравнение.

В прошлом ... как все говорят, использование ресурсов зависит от конкретной реализации.

Я согласен со всеми остальными комментариями, что автор должен избегать RTTI по ​​причинам дизайна. Тем не менее, есть есть веские причины использовать RTTI ( в основном из - за повышения :: любой). Имея это в виду, полезно знать фактическое использование ресурсов в распространенных реализациях.

Недавно я провел кучу исследований RTTI в GCC.

tl; dr: RTTI в GCC использует незначительное место и typeid(a) == typeid(b)очень быстро работает на многих платформах (Linux, BSD и, возможно, встроенные платформы, но не mingw32). Если вы знаете, что вы всегда будете на благословенной платформе, RTTI очень близка к бесплатной.

Песчаные детали:

GCC предпочитает использовать определенный «независимый от производителя» C ++ ABI [1] и всегда использует этот ABI для целей Linux и BSD [2]. Для платформ, которые поддерживают этот ABI, а также слабую связь, typeid()возвращает непротиворечивый и уникальный объект для каждого типа, даже через границы динамической связи. Вы можете проверить &typeid(a) == &typeid(b)или просто положиться на тот факт, что портативный тест на typeid(a) == typeid(b)самом деле просто сравнивает указатель внутри.

В предпочтительном ABI GCC класс vtable всегда содержит указатель на структуру RTTI для каждого типа, хотя он может не использоваться. Таким образом, typeid()сам вызов должен стоить столько же, сколько любой другой поиск в vtable (так же, как вызов виртуальной функции-члена), а поддержка RTTI не должна использовать дополнительное пространство для каждого объекта.

Из того, что я могу разглядеть, структуры RTTI, используемые GCC (это все подклассы std::type_info), содержат только несколько байтов для каждого типа, кроме имени. Мне не ясно, присутствуют ли имена в выходном коде даже с -fno-rtti. В любом случае, изменение размера скомпилированного двоичного файла должно отражать изменение в использовании памяти во время выполнения.

Быстрый эксперимент (с использованием GCC 4.4.3 в 64-битной Ubuntu 10.04) показывает, что -fno-rttiфактически увеличивает размер двоичного файла простой тестовой программы на несколько сотен байт. Это происходит последовательно между комбинациями -gи -O3. Я не уверен, почему размер увеличится; одна возможность состоит в том, что код STL GCC ведет себя иначе без RTTI (так как исключения не будут работать).

[1] Известный как Itanium C ++ ABI, документированный по адресу http://www.codesourcery.com/public/cxx-abi/abi.html . Имена ужасно сбивают с толку: имя относится к оригинальной архитектуре разработки, хотя спецификация ABI работает на многих архитектурах, включая i686 / x86_64. Комментарии во внутреннем источнике GCC и коде STL ссылаются на Itanium как «новый» ABI в отличие от «старого», который они использовали ранее. Хуже того, «новый» / Itanium ABI относится ко всем версиям, доступным через -fabi-version; «старый» ABI предшествовал этой версии. GCC принял Itanium / versioned / "новый" ABI в версии 3.0; «старый» ABI использовался в 2.95 и ранее, если я правильно читаю их журналы изменений.

[2] Я не смог найти ни одного ресурса, перечисляющего std::type_infoстабильность объекта в зависимости от платформы. Для компиляторов , я имел доступ к, я использовал следующее: echo "#include <typeinfo>" | gcc -E -dM -x c++ -c - | grep GXX_MERGED_TYPEINFO_NAMES. Этот макрос управляет поведением operator==for std::type_infoв STL GCC, начиная с GCC 3.0. Я обнаружил, что mingw32-gcc подчиняется ABI Windows C ++, где std::type_infoобъекты не являются уникальными для типа в разных DLL; typeid(a) == typeid(b)звонки strcmpпод одеялом. Я предполагаю, что в однопрограммных встраиваемых целях, таких как AVR, где нет кода для ссылки, std::type_infoобъекты всегда стабильны.

sbrudenell
источник
6
Исключения работают без RTTI. (Вы можете бросить, intи в этом нет vtable :))
Billy ONeal
3
@Deduplicator: И все же, когда я выключаю RTTI в моем компиляторе, они работают просто отлично. Извините, что разочаровал вас.
Билли ОНил
5
Механизм обработки исключений должен быть в состоянии работать с любым типом, полностью удовлетворяющим некоторым основным требованиям. Вы можете свободно предлагать, как обрабатывать выбрасывание и перехват исключений произвольного типа через границы модуля без RTTI. Пожалуйста, примите во внимание, что требуется повышение и понижение.
Дедупликатор
15
typeid (a) == typeid (b) НЕ совпадает с B * ba = dynamic_cast <B *> (& a). Попробуйте это на объектах с множественным наследованием в качестве случайного уровня в производном дереве классов, и вы найдете, что typeid () == typeid () не даст положительного результата. dynamic_cast - единственный способ поиска дерева наследования для реального. Перестаньте думать о потенциальной экономии, отключив RTTI, и просто используйте ее. Если вы перегружены, оптимизируйте код. Старайтесь избегать использования dynamic_cast во внутренних циклах или любого другого кода, критичного для производительности, и все будет в порядке.
Мистикодер
3
@mcoder Вот почему в статье прямо говорится об этом the latter necessarily involves traversing an inheritance tree plus comparisons. @CoryB Вы можете «позволить себе» сделать это, когда вам не нужно поддерживать приведение из всего дерева наследования. Например, если вы хотите найти все элементы типа X в коллекции, но не те, которые являются производными от X, то вам следует использовать первое. Если вам нужно также найти все производные экземпляры, вам придется использовать последний.
Aidiakapi
48

Возможно, эти цифры помогут.

Я делал быстрый тест, используя это:

  • GCC Clock () + XCode Profiler.
  • 100 000 000 повторений цикла.
  • Двухъядерный процессор Intel Xeon 2 x 2,66 ГГц.
  • Данный класс является производным от одного базового класса.
  • typeid (). name () возвращает "N12fastdelegate13FastDelegate1IivEE"

Было проверено 5 случаев:

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 - это просто мой настоящий код, так как мне нужно было создать объект такого типа, прежде чем проверять, похож ли он на тот, который у меня уже есть.

Без оптимизации

Для которых результаты были (я усреднил несколько прогонов):

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.

Таким образом, вывод будет:

  • Для простых приведений без оптимизации typeid()это более чем в два раза быстрее dyncamic_cast.
  • На современной машине разница между ними составляет около 1 наносекунды (миллионная доля миллисекунды).

С оптимизацией (-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.

Таким образом, вывод будет:

  • Для простых случаев приведения с оптимизацией typeid()почти в 20 раз быстрее, чем dyncamic_cast.

Диаграмма

введите описание изображения здесь

Код

Как и просили в комментариях, код ниже (немного грязно, но работает). FastDelegate.h доступен здесь .

#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
источник
1
Конечно, динамическое приведение является более общим - оно работает, если элемент более производный. Например , class a {}; class b : public a {}; class c : public b {};когда цель является экземпляром cбудет работать нормально при тестировании класса bс dynamic_cast, но не с typeidраствором. Тем не менее, все еще разумно, +1
Билли ONeal
34
Этот бенчмарк полностью поддельный с оптимизациями : проверка typeid инвариантна к циклу и перемещается из цикла. Это совсем не интересно, это базовый бенчмаркинг нет-нет.
Восстановите Монику
3
@ Куба: Тогда эталон фальшивый. Это не повод для сравнения с оптимизацией; это причина для написания лучших тестов.
Билли ONEAL
3
еще раз, это провал. «Для простых случаев приведения с оптимизацией typeid () почти в 20 раз быстрее, чем dyncamic_cast». они не делают то же самое. Существует причина, по которой dynamic_cast работает медленнее.
Мистикодер
1
@KubaOber: всего +1. это так классика. и по количеству циклов должно быть очевидно, что это произошло.
v.oddou
38

Это зависит от масштаба вещей. По большей части это всего лишь пара проверок и несколько разыменований указателей. В большинстве реализаций в верхней части каждого объекта, имеющего виртуальные функции, есть указатель на виртуальную таблицу, в которой содержится список указателей на все реализации виртуальной функции в этом классе. Я предполагаю, что большинство реализаций будет использовать это либо для хранения другого указателя на структуру type_info для класса.

Например, в псевдо-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);       
}

В общем, реальный аргумент против RTTI - это необязательность необходимости изменять код везде всякий раз, когда вы добавляете новый производный класс. Вместо повсеместного переключения операторов делайте их на виртуальные функции. Это перемещает весь код, который отличается между классами, в сами классы, так что новому производному просто нужно переопределить все виртуальные функции, чтобы стать полностью функционирующим классом. Если вам когда-либо приходилось искать большую базу кода каждый раз, когда кто-то проверяет тип класса и делает что-то другое, вы быстро научитесь избегать такого стиля программирования.

Если ваш компилятор позволяет полностью отключить RTTI, конечная результирующая экономия размера кода может быть значительной при таком небольшом объеме ОЗУ. Компилятор должен сгенерировать структуру type_info для каждого отдельного класса с виртуальной функцией. Если вы отключите RTTI, все эти структуры не обязательно должны быть включены в исполняемый образ.

Затмение
источник
4
+1 за фактическое объяснение, почему использование RTTI считается плохим дизайнерским решением, что раньше мне было не совсем понятно.
aguazales
6
Этот ответ является низким уровнем понимания возможностей C ++. «В общем» и «В большинстве реализаций», используемые в широком смысле, означают, что вы не думаете о том, как правильно использовать возможности языков. Виртуальные функции и повторная реализация RTTI не является ответом. RTTI - это ответ. Иногда вы просто хотите узнать, относится ли объект к определенному типу. Вот почему это там! Таким образом, вы теряете несколько КБ ОЗУ для некоторых структур type_info. Ну и дела ...
Мистикодер
16

Ну, профилировщик никогда не лжет.

Так как у меня довольно стабильная иерархия из 18-20 типов, которая не сильно меняется, я подумал, что если бы использование простого члена enum могло бы сработать и избежать якобы «высокой» стоимости RTTI. Я скептически относился к тому, что RTTI на самом деле дороже, чем просто ifзаявление, которое он вводит. Мальчик, о мальчик, это так.

Оказывается, RTTI стоит дорого, намного дороже, чем эквивалентное ifутверждение или простая switchпеременная в примитиве C ++. Так ответ С. Лотт является не совсем корректно, то есть дополнительные расходы на RTTI, и это не из - за просто имея ifзаявление в миксе. Это связано с тем, что RTTI очень дорогой.

Этот тест был проведен на компиляторе Apple LLVM 5.0 с включенной стандартной оптимизацией (настройки режима выпуска по умолчанию).

Итак, у меня есть 2 функции, каждая из которых определяет конкретный тип объекта через 1) RTTI или 2) простой переключатель. Это 50 000 000 раз. Без дальнейших церемоний, я представляю вам относительное время выполнения для 50 000 000 прогонов.

введите описание изображения здесь

Это правда, что dynamicCastsзаняло 94% времени выполнения. Пока regularSwitchблок взял только 3,3% .

Короче говоря: если вы можете позволить себе энергию для подключения enumтипа d, как я сделал ниже, я, вероятно, рекомендую это, если вам нужно сделать RTTI и производительность имеет первостепенное значение. Требуется установить элемент только один раз (убедитесь, что он получен через все конструкторы ), и никогда не пишите его позже.

Тем не менее, выполнение этого не должно испортить ваши практики ООП ... оно предназначено только для использования, когда информация о типе просто недоступна, и вы оказались в окружении использования 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
источник
13

Стандартный способ:

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

Стандартный RTTI стоит дорого, потому что он основан на сравнении базовых строк, поэтому скорость RTTI может варьироваться в зависимости от длины имени класса.

Причина, по которой используются сравнения строк, состоит в том, чтобы заставить его работать согласованно через границы библиотеки / DLL. Если вы строите свое приложение статически и / или используете определенные компиляторы, вы можете использовать:

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

Который не гарантированно работает (никогда не даст ложных срабатываний, но может давать ложные отрицания), но может быть до 15 раз быстрее. Это зависит от реализации typeid () для работы определенным образом, и все, что вы делаете, это сравнивает внутренний указатель на символ. Это также иногда эквивалентно:

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

Однако вы можете безопасно использовать гибрид, который будет очень быстрым, если типы совпадают, и будет наихудшим для непревзойденных типов:

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

Чтобы понять, нужно ли вам оптимизировать это, вам нужно посмотреть, сколько времени вы тратите на получение нового пакета, по сравнению со временем, которое требуется для обработки пакета. В большинстве случаев сравнение строк, вероятно, не будет слишком большим. (в зависимости от вашего класса или пространства имен :: длина имени класса)

Самый безопасный способ оптимизировать это - реализовать свой собственный typeid как int (или enum Type: int) как часть вашего базового класса и использовать его для определения типа класса, а затем просто использовать static_cast <> или reinterpret_cast < >

Для меня разница примерно в 15 раз на неоптимизированной MS VS 2005 C ++ SP1.

Marius
источник
2
«Стандартный RTTI стоит дорого, потому что он основан на сравнении базовой строки» - нет, в этом нет ничего «Стандартного»; это просто , как ваша реализация - х typeid::operatorработы с . GCC на поддерживаемой платформе, например, уже использует сравнения char *s, без нас это принудительно - gcc.gnu.org/onlinedocs/gcc-4.6.3/libstdc++/api/… . Конечно, ваш путь заставляет MSVC вести себя намного лучше, чем по умолчанию на вашей платформе, так что, слава богу, и я не знаю, каковы «некоторые цели», которые используют указатели изначально ... но я хочу сказать, что поведение MSVC никак не связано «Стандарт».
underscore_d
7

Для простой проверки RTTI может быть таким же дешевым, как сравнение указателей. Для проверки наследования это может быть так же дорого, как и strcmpдля каждого типа в дереве наследования, если вы dynamic_castперебираете сверху вниз в одной реализации.

Вы также можете уменьшить накладные расходы, не используя dynamic_castи вместо этого явно проверяя тип с помощью & typeid (...) == & typeid (type). Хотя это не обязательно работает для .dll или другого динамически загружаемого кода, это может быть довольно быстро для вещей, которые статически связаны.

Хотя в этот момент это все равно что использовать оператор switch, так что все готово.

MSN
источник
1
Есть ли у вас ссылки на версию strcmp? Кажется крайне неэффективным и неточным использовать strcmp для проверки типа.
JaredPar
В плохой реализации, которая может иметь несколько объектов type_info для каждого типа, он может реализовать bool type_info :: operator == (const type_info & x) const как "! Strcmp (name (), x.name ())"
Грег Роджерс,
3
Перейдите к разборке dynamic_cast или typeid (). Operator == для MSVC, и вы попадете туда с помощью команды strcmp. Я предполагаю, что это там для ужасного случая, когда вы сравниваете с типом, скомпилированным в другом .dll. И он использует искаженное имя, так что, по крайней мере, он правильный, если использовать тот же компилятор.
MSN
1
вы должны сделать «typeid (...) == typeid (type)» и не сравнивать адрес
Йоханнес Шауб - litb
1
Я хочу сказать, что вы можете сделать & typeid (...) == & typeid (бла) как можно раньше и будете в безопасности. Это может на самом деле не делать ничего полезного, так как typeid (...) может быть сгенерирован в стеке, но если их адреса равны, то их типы равны.
MSN
6

Всегда лучше измерить вещи. В следующем коде под g ++ использование идентификации типа, закодированного вручную, кажется в три раза быстрее, чем RTTI. Я уверен, что более реалистичная реализация вручную с использованием строк вместо символов будет медленнее, сближая время.

#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;
}

источник
1
попробуйте не делать это с dynamic_cast, но с typeid. это может ускорить производительность.
Йоханнес Шауб -
1
но использование dynamic_cast более реалистично, по крайней мере, глядя на мой код
2
он делает другое: он также проверяет, указывает ли bp на тип, производный от A. ваш == 'A' проверяет, точно ли он указывает на 'A'. я также думаю, что тест несколько несправедлив: компилятор может легко увидеть, что bp не может указать на что-то отличное от A., но я думаю, что здесь он не оптимизируется.
Йоханнес Шауб -
во всяком случае, я проверил ваш код. и это дает мне «0,016 с» для RTTI и «0,044 с» для вызовов виртуальных функций. (используя -O2)
Йоханнес Шауб -
хотя изменение его для использования typeid здесь не имеет никакого значения (все еще 0,016 с)
Йоханнес Шауб - litb
4

Некоторое время назад я измерил временные затраты на RTTI в конкретных случаях MSVC и GCC для 3 ГГц PowerPC. В тестах, которые я запускал (довольно большое приложение C ++ с глубоким деревом классов), каждое dynamic_cast<>стоило от 0,8 мкс до 2 мкс, в зависимости от того, попадало оно или нет.

Crashworks
источник
2

Итак, насколько дорог RTTI?

Это полностью зависит от компилятора, который вы используете. Я понимаю, что некоторые используют сравнения строк, а другие используют реальные алгоритмы.

Ваша единственная надежда - написать пример программы и посмотреть, что делает ваш компилятор (или хотя бы определить, сколько времени потребуется для выполнения миллиона dynamic_castsили миллиона typeidс).

Макс Либберт
источник
1

RTTI может быть дешевым и не обязательно требует strcmp. Компилятор ограничивает тест выполнением фактической иерархии в обратном порядке. Поэтому, если у вас есть класс C, который является дочерним по отношению к классу B, который является дочерним по отношению к классу A, динамический_каталог от A * ptr до C * ptr подразумевает только одно сравнение указателей, а не два (Кстати, только указатель таблицы vptr в сравнении). Тест подобен "if (vptr_of_obj == vptr_of_C) return (C *) obj"

Другой пример, если мы попытаемся выполнить dynamic_cast от A * до B *. В этом случае компилятор будет проверять оба случая (obj, являющийся C, и obj, являющийся B) по очереди. Это также можно упростить до одного теста (чаще всего), поскольку таблица виртуальных функций представляет собой агрегацию, поэтому тест возобновляется до «if (offset_of (vptr_of_obj, B) == vptr_of_B)» с

offset_of = вернуть sizeof (vptr_table)> = sizeof (vptr_of_B)? vptr_of_new_methods_in_B: 0

Макет памяти

vptr_of_C = [ vptr_of_A | vptr_of_new_methods_in_B | vptr_of_new_methods_in_C ]

Как компилятор узнает, как оптимизировать это во время компиляции?

Во время компиляции компилятор знает текущую иерархию объектов, поэтому он отказывается компилировать иерархию другого типа dynamic_casting. Тогда он просто должен обработать глубину иерархии и добавить инвертированное количество тестов, чтобы соответствовать такой глубине.

Например, это не компилируется:

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

RTTI может быть «дорогим», потому что вы добавляете оператор if каждый раз, когда проводите сравнение RTTI. В глубоко вложенных итерациях это может быть дорогостоящим. В чем-то, что никогда не выполняется в цикле, это по существу бесплатно.

Выбор состоит в том, чтобы использовать правильный полиморфный дизайн, исключая оператор if. В глубоко вложенных циклах это важно для производительности. В противном случае, это не имеет большого значения.

RTTI также дорог, потому что он может скрыть иерархию подклассов (если она вообще есть). Это может иметь побочный эффект удаления «объектно-ориентированного» из «объектно-ориентированного программирования».

С. Лотт
источник
2
Не обязательно - я собирался использовать его косвенно с помощью dynamic_cast и сохранить иерархию на месте, потому что мне нужно уменьшить значение, потому что каждый подтип должен иметь разные (переменные размеры) данные, которые должны применяться по-разному, следовательно, dynamic_cast.
Кристиан Ромо
1
@ Кристиан Ромо: Пожалуйста, обновите ваш вопрос с этими новыми фактами. dynamic_cast - это (иногда) необходимое зло в C ++. Спрашивать о производительности RTTI, когда вы вынуждены это делать, не имеет большого смысла.
S.Lott
@ S.Lott: Обновлено. Извините за путаницу.
Кристиан Ромо
1
я сделал эксперимент по этому поводу только сейчас - оказалось, что RTTI значительно дороже, чем ifутверждение, которое вы вводите, когда проверяете информацию о типе среды выполнения таким образом.
Бобобобо