Несколько уровней базовых классов замедляют класс / структуру в c ++? - PullRequest
14 голосов
/ 19 сентября 2008

Замедляет ли наличие нескольких уровней базовых классов? A выводит B выводит C выводит D выводит F выводит G, ...

Замедляет ли множественное наследование класс?

Ответы [ 12 ]

25 голосов
/ 19 сентября 2008

Не виртуальные вызовы функций не имеют абсолютно никакого снижения производительности во время выполнения, в соответствии с мантрой c ++, что вы не должны платить за то, что вы не используете. При вызове виртуальной функции вы обычно платите за дополнительный поиск указателя, независимо от того, сколько у вас уровней наследования или количества базовых классов. Конечно, все это определяется реализацией.

Редактировать: Как уже отмечалось, в некоторых сценариях множественного наследования перед выполнением вызова требуется корректировка указателя 'this'. Раймонд Чен описывает , как это работает для COM-объектов. По сути, вызов виртуальной функции для объекта, который наследуется от нескольких баз, может потребовать дополнительного вычитания и инструкции jmp поверх дополнительного поиска указателя, необходимого для виртуального вызова.

6 голосов
/ 19 сентября 2008

[Глубокая иерархия наследования] значительно увеличивает нагрузку на обслуживание, добавляя ненужную сложность, заставляя пользователей изучать интерфейсы многих классов, даже когда все, что они хотят сделать, - это использовать определенный производный класс. Он также может влиять на использование памяти и производительность программы, добавляя ненужные таблицы и косвенные ссылки к классам, которые в них действительно не нуждаются. Если вы часто создаете глубокие иерархии наследования, вам следует пересмотреть свой стиль проектирования, чтобы увидеть, подхватили ли вы эту вредную привычку. Глубокие иерархии редко нужны и почти никогда не хороши. И если вы не верите этому, но думаете, что «ОО просто не является ОО без большого количества наследования», то хороший контрпример для рассмотрения - [C ++] сама стандартная библиотека. - Херб Саттер

4 голосов
/ 19 сентября 2008
  • Замедляется ли множественное наследование класс?

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

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

.
(static_cast<Base*>( this) == this)

Не обязательно верно в зависимости от макета объекта.

Обратите внимание, что все это очень , очень зависит от реализации.

См. Главу 4.2 «Внутри объектной модели C ++» Липпмана - Виртуальные функции-члены / Виртуальные функции в MI

2 голосов
/ 19 сентября 2008

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

2 голосов
/ 19 сентября 2008

Сами виртуальные вызовы занимают больше времени, чем обычные вызовы, потому что они должны искать адрес фактической функции для вызова из vtable

Кроме того, оптимизация компилятора, такая как встраивание, может быть сложной из-за требования поиска. Ситуации, когда встраивание само по себе невозможно, могут привести к довольно высоким издержкам из-за операций выталкивания стека и операций push и jump

Вот правильное исследование, в котором говорится, что накладные расходы могут достигать 50% http://www.cs.ucsb.edu/~urs/oocsb/papers/oopsla96.pdf

Вот еще один ресурс, который рассматривает побочный эффект от наличия большой библиотеки виртуальных классов http://keycorner.org/pub/text/doc/kde-slow.txt

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

Что касается вашего конкретного вопроса, касающегося большого количества базовых классов, то обычно макет памяти объекта класса будет содержать vtbl ptrs для всех других составляющих классов внутри него.

Проверьте эту страницу для примера макета vtable - http://www.codesourcery.com/public/cxx-abi/cxx-vtable-ex.html

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

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

2 голосов
/ 19 сентября 2008

Нет разницы в скорости между виртуальными вызовами на разных уровнях, так как все они расплющены в vtable (указывая на большинство производных версий переопределенных методов). Таким образом, вызов ((A *) inst) -> Method (), когда inst является экземпляром B , приводит к таким же издержкам, как когда inst является экземпляром D .

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

1 голос
/ 19 сентября 2008

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

1 голос
/ 19 сентября 2008

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

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

0 голосов
/ 19 сентября 2008

Как указывает Кори Росс , vtable известен во время компиляции для любого производного от листа класса, и поэтому стоимость виртуального вызова действительно должна быть одинаковой независимо от структуры иерархии.

Однако этого нельзя сказать о dynamic_cast. Если вы подумаете о том, как реализовать dynamic_cast, базовый подход будет состоять из O (n) поиска в вашей иерархии!

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

sturct A { int i; };
struct B { int j; };

struct C : public A, public B { int k ; };

// Let's assume that the layout of C is:  { [ int i ] [ int j ] [int k ] }

void foo (C * c) {
  A * a = c;                // Probably has zero cost
  B * b = c;                // Compiler needed to add sizeof(A) to 'c'
  c = static_cast<B*> (b);  // Compiler needed to take sizeof(A)' from 'b'
}
0 голосов
/ 19 сентября 2008

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

Однако, если вы вызываете функции базового класса, которые вызывают функции базового класса и т. Д., Это может сильно повлиять на производительность.

По сути, это сильно зависит от того, как вы структурировали свое дерево наследования.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...