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

Мы столкнулись с этой проблемой при попытке добавить индекс UNIQUE в поле VARCHAR (255), используя utf8mb4. Хотя проблема уже здесь хорошо описана, я хотел бы добавить несколько практических советов, как мы это выяснили и решили.

При использовании utf8mb4 символы считаются 4 байтами, тогда как под utf8 они могут как 3 байта. Базы данных InnoDB имеют ограничение на то, что индексы могут содержать только 767 байт. Поэтому при использовании utf8 вы можете сохранить 255 символов (767/3 = 255), но используя utf8mb4, вы можете хранить только 191 символ (767/4 = 191).

Вы абсолютно можете добавить регулярные индексы для полей VARCHAR(255) с использованием utf8mb4, но случается, что размер индекса усекается с 191 символом автоматически - например unique_key здесь:

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

Итак, почему MySQL автоматически обрезает индекс для регулярных индексов, но бросает явную ошибку при попытке сделать это для уникальных индексов? Ну, для того, чтобы MySQL смог выяснить, существует ли уже существующее или обновляемое значение, оно должно фактически индексировать все значение, а не только его часть.

В конце дня, если вы хотите иметь уникальный индекс в поле, все содержимое поля должно вписываться в индекс. Для utf8mb4 это означает сокращение длины полей VARCHAR до 191 символа или меньше. Если вам не нужна utf8mb4 для этой таблицы или поля, вы можете вернуть ее обратно в utf8 и сохранить свои длины длины.

142
задан Community 23 May 2017 в 12:34
поделиться

7 ответов

Так, насколько дорогой RTTI?

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

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

2
ответ дан Max Lybbert 23 May 2017 в 12:34
поделиться

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

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

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

6
ответ дан MSN 23 May 2017 в 12:34
поделиться

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

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

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

-4
ответ дан S.Lott 23 May 2017 в 12:34
поделиться
  • 1
    - 1 Это перестанет работать каждый раз, когда процесс занимает больше времени, чем ожидалось. Это может произойти по любой из миллиона причин, например, дисковое резервное копирование работает. – Jean-François Corbett 18 January 2012 в 08:11

Это зависит от масштаба вещей. По большей части это - просто пара проверок и некоторых, указатель разыменовывает. В большинстве реализаций, наверху каждого объекта, который имеет виртуальные функции, существует указатель на vtable, которое содержит список указателей на все реализации виртуальной функции на том классе. Я предположил бы, что большинство реализаций будет использовать это для любого хранилища другой указатель на 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, заключительные получающиеся сбережения размера кода могут быть значительными, хотя, с такой маленькой RAM располагают с интервалами. Компилятор должен генерировать type_info структуру для каждого класса с виртуальной функцией. Если Вы выключаете RTTI, все эти структуры не должны быть включены в исполняемое изображение.

37
ответ дан Johannes Schaub - litb 23 May 2017 в 12:34
поделиться

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

#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;
}
6
ответ дан 23 May 2017 в 12:34
поделиться

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

13
ответ дан 23 November 2019 в 22:44
поделиться

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

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

offset_of = return 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);  
1
ответ дан 23 November 2019 в 22:44
поделиться
Другие вопросы по тегам:

Похожие вопросы: