Я понимаю, что использование RTTI приводит к потере ресурса, но насколько он велик? Везде, где я смотрел, просто говорится, что «RTTI стоит дорого», но ни один из них на самом деле не дает никаких эталонов или количественных данных, касающихся памяти, времени процессора или скорости.
Итак, насколько дорог RTTI? Я мог бы использовать его во встроенной системе, где у меня есть только 4 МБ ОЗУ, поэтому каждый бит считается.
Редактировать: Согласно ответу С. Лотта , было бы лучше, если бы я включил то, что я на самом деле делаю. Я использую класс для передачи данных различной длины, которые могут выполнять различные действия , поэтому было бы трудно сделать это, используя только виртуальные функции. Кажется, что использование нескольких dynamic_cast
s может решить эту проблему, позволяя различным производным классам проходить через разные уровни, но при этом они могут действовать совершенно по-разному.
dynamic_cast
Насколько я понимаю, использует RTTI, поэтому мне было интересно, насколько это целесообразно использовать в ограниченной системе.
источник
dynamic_cast
в C ++, и теперь, 9 из 10 раз, когда я «ломаю» программу с помощью отладчика, она ломается внутри внутренней функции динамического приведения. Это чертовски медленно.Ответы:
Независимо от компилятора, вы всегда можете сэкономить на времени выполнения, если можете себе это позволить
вместо того
Первое включает в себя только одно сравнение
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==
forstd::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
объекты всегда стабильны.источник
int
и в этом нет vtable :))the latter necessarily involves traversing an inheritance tree plus comparisons
. @CoryB Вы можете «позволить себе» сделать это, когда вам не нужно поддерживать приведение из всего дерева наследования. Например, если вы хотите найти все элементы типа X в коллекции, но не те, которые являются производными от X, то вам следует использовать первое. Если вам нужно также найти все производные экземпляры, вам придется использовать последний.Возможно, эти цифры помогут.
Я делал быстрый тест, используя это:
Было проверено 5 случаев:
5 - это просто мой настоящий код, так как мне нужно было создать объект такого типа, прежде чем проверять, похож ли он на тот, который у меня уже есть.
Без оптимизации
Для которых результаты были (я усреднил несколько прогонов):
Таким образом, вывод будет:
typeid()
это более чем в два раза быстрееdyncamic_cast
.С оптимизацией (-Os)
Таким образом, вывод будет:
typeid()
почти в 20 раз быстрее, чемdyncamic_cast
.Диаграмма
Код
Как и просили в комментариях, код ниже (немного грязно, но работает). FastDelegate.h доступен здесь .
источник
class a {}; class b : public a {}; class c : public b {};
когда цель является экземпляромc
будет работать нормально при тестировании классаb
сdynamic_cast
, но не сtypeid
раствором. Тем не менее, все еще разумно, +1Это зависит от масштаба вещей. По большей части это всего лишь пара проверок и несколько разыменований указателей. В большинстве реализаций в верхней части каждого объекта, имеющего виртуальные функции, есть указатель на виртуальную таблицу, в которой содержится список указателей на все реализации виртуальной функции в этом классе. Я предполагаю, что большинство реализаций будет использовать это либо для хранения другого указателя на структуру type_info для класса.
Например, в псевдо-C ++:
В общем, реальный аргумент против RTTI - это необязательность необходимости изменять код везде всякий раз, когда вы добавляете новый производный класс. Вместо повсеместного переключения операторов делайте их на виртуальные функции. Это перемещает весь код, который отличается между классами, в сами классы, так что новому производному просто нужно переопределить все виртуальные функции, чтобы стать полностью функционирующим классом. Если вам когда-либо приходилось искать большую базу кода каждый раз, когда кто-то проверяет тип класса и делает что-то другое, вы быстро научитесь избегать такого стиля программирования.
Если ваш компилятор позволяет полностью отключить RTTI, конечная результирующая экономия размера кода может быть значительной при таком небольшом объеме ОЗУ. Компилятор должен сгенерировать структуру type_info для каждого отдельного класса с виртуальной функцией. Если вы отключите RTTI, все эти структуры не обязательно должны быть включены в исполняемый образ.
источник
Ну, профилировщик никогда не лжет.
Так как у меня довольно стабильная иерархия из 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.
источник
Стандартный способ:
Стандартный RTTI стоит дорого, потому что он основан на сравнении базовых строк, поэтому скорость RTTI может варьироваться в зависимости от длины имени класса.
Причина, по которой используются сравнения строк, состоит в том, чтобы заставить его работать согласованно через границы библиотеки / DLL. Если вы строите свое приложение статически и / или используете определенные компиляторы, вы можете использовать:
Который не гарантированно работает (никогда не даст ложных срабатываний, но может давать ложные отрицания), но может быть до 15 раз быстрее. Это зависит от реализации typeid () для работы определенным образом, и все, что вы делаете, это сравнивает внутренний указатель на символ. Это также иногда эквивалентно:
Однако вы можете безопасно использовать гибрид, который будет очень быстрым, если типы совпадают, и будет наихудшим для непревзойденных типов:
Чтобы понять, нужно ли вам оптимизировать это, вам нужно посмотреть, сколько времени вы тратите на получение нового пакета, по сравнению со временем, которое требуется для обработки пакета. В большинстве случаев сравнение строк, вероятно, не будет слишком большим. (в зависимости от вашего класса или пространства имен :: длина имени класса)
Самый безопасный способ оптимизировать это - реализовать свой собственный typeid как int (или enum Type: int) как часть вашего базового класса и использовать его для определения типа класса, а затем просто использовать static_cast <> или reinterpret_cast < >
Для меня разница примерно в 15 раз на неоптимизированной MS VS 2005 C ++ SP1.
источник
typeid::operator
работы с . GCC на поддерживаемой платформе, например, уже использует сравненияchar *
s, без нас это принудительно - gcc.gnu.org/onlinedocs/gcc-4.6.3/libstdc++/api/… . Конечно, ваш путь заставляет MSVC вести себя намного лучше, чем по умолчанию на вашей платформе, так что, слава богу, и я не знаю, каковы «некоторые цели», которые используют указатели изначально ... но я хочу сказать, что поведение MSVC никак не связано «Стандарт».Для простой проверки RTTI может быть таким же дешевым, как сравнение указателей. Для проверки наследования это может быть так же дорого, как и
strcmp
для каждого типа в дереве наследования, если выdynamic_cast
перебираете сверху вниз в одной реализации.Вы также можете уменьшить накладные расходы, не используя
dynamic_cast
и вместо этого явно проверяя тип с помощью & typeid (...) == & typeid (type). Хотя это не обязательно работает для .dll или другого динамически загружаемого кода, это может быть довольно быстро для вещей, которые статически связаны.Хотя в этот момент это все равно что использовать оператор switch, так что все готово.
источник
Всегда лучше измерить вещи. В следующем коде под g ++ использование идентификации типа, закодированного вручную, кажется в три раза быстрее, чем RTTI. Я уверен, что более реалистичная реализация вручную с использованием строк вместо символов будет медленнее, сближая время.
источник
Некоторое время назад я измерил временные затраты на RTTI в конкретных случаях MSVC и GCC для 3 ГГц PowerPC. В тестах, которые я запускал (довольно большое приложение C ++ с глубоким деревом классов), каждое
dynamic_cast<>
стоило от 0,8 мкс до 2 мкс, в зависимости от того, попадало оно или нет.источник
Это полностью зависит от компилятора, который вы используете. Я понимаю, что некоторые используют сравнения строк, а другие используют реальные алгоритмы.
Ваша единственная надежда - написать пример программы и посмотреть, что делает ваш компилятор (или хотя бы определить, сколько времени потребуется для выполнения миллиона
dynamic_casts
или миллионаtypeid
с).источник
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
Макет памяти
Как компилятор узнает, как оптимизировать это во время компиляции?
Во время компиляции компилятор знает текущую иерархию объектов, поэтому он отказывается компилировать иерархию другого типа dynamic_casting. Тогда он просто должен обработать глубину иерархии и добавить инвертированное количество тестов, чтобы соответствовать такой глубине.
Например, это не компилируется:
источник
RTTI может быть «дорогим», потому что вы добавляете оператор if каждый раз, когда проводите сравнение RTTI. В глубоко вложенных итерациях это может быть дорогостоящим. В чем-то, что никогда не выполняется в цикле, это по существу бесплатно.
Выбор состоит в том, чтобы использовать правильный полиморфный дизайн, исключая оператор if. В глубоко вложенных циклах это важно для производительности. В противном случае, это не имеет большого значения.
RTTI также дорог, потому что он может скрыть иерархию подклассов (если она вообще есть). Это может иметь побочный эффект удаления «объектно-ориентированного» из «объектно-ориентированного программирования».
источник
if
утверждение, которое вы вводите, когда проверяете информацию о типе среды выполнения таким образом.