накладные расходы интерфейса - PullRequest
4 голосов
/ 05 ноября 2010

У меня есть простой класс, который выглядит как Boost.Array.Существует два параметра шаблона T и N. Один из недостатков Boost.Array состоит в том, что каждый метод, использующий такой массив, должен быть шаблоном с параметром N (T - ОК).В результате вся программа является шаблоном.Одна из идей - создать интерфейс (абстрактный класс только с чисто виртуальными функциями), который зависит только от T (что-то вроде ArrayInterface).Теперь любой другой класс имеет доступ только к интерфейсу, и поэтому ему нужен только параметр шаблона T (который, в отличие от N, более или менее всегда известен).Недостаток здесь - это издержки виртуального вызова (более упущенная возможность встроенных вызовов), если используется интерфейс.Пока здесь только факты.

template<typename T>
class ArrayInterface {
public:
    virtual ~ArrayInterface() {};
    virtual T Get(int i) = 0;
};

template<typename T, int N>
class Array : ArrayInterface<T> {
public:
    T Get(int i) { ... }
};

template<typename T, int N>
class ArrayWithoutInterface {
public:
    T Get() { ... }
};

Но моя настоящая проблема заключается в другом.Когда я расширяю Boost.Array интерфейсом, прямое создание Boost.Array становится медленным (фактор 4 в одном случае, где это важно).Если я удаляю интерфейс, Boost.Array работает так же быстро, как и раньше.Я понимаю, что если метод вызывается через ArrayInterface, то возникают издержки, это нормально.Но я не понимаю, почему вызов метода становится медленнее, если есть только дополнительный интерфейс только с чисто виртуальными методами, а класс вызывается напрямую.

Array<int, 1000> a;
a.Get(0); // Slow

ArrayWithoutInterface<int, 1000> awi;
awi.Get(0); // Fast

GCC 4.4.3 и Clang 1.1показать то же поведение.

Ответы [ 4 ]

2 голосов
/ 05 ноября 2010

Я бы не согласился с вашим выводом, что «вся программа имеет тенденцию быть шаблоном»: мне кажется, что вы пытаетесь решить не проблему.

Однако неясно, что вы имеете в виду под «расширением Boost.Array с помощью интерфейса»: вы изменяете источник boost::array, представляя свой интерфейс?Если это так, то каждый создаваемый вами экземпляр array должен перетаскивать указатель vtable, независимо от того, используете вы виртуальные методы или нет.Существование виртуальных методов может также сделать компилятор осторожным в использовании агрессивной оптимизации для не виртуальных методов, которые возможны в классе, определенном только заголовком.

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

2 голосов
/ 05 ноября 2010

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

Для простого вызова, такого как Get (который просто разыменовывает ячейку массива, предположительно без проверки границ), это действительно может иметь значение в 4 раза.

Теперь хороший компилятор может увидеть, что добавленная косвенность здесь не нужна, поскольку динамический тип объекта (и, следовательно, цель вызова метода) известен во время компиляции. Я слегка удивлен, что GCC, по-видимому, не оптимизирует это (вы компилировали с -O3?). Опять же, это всего лишь оптимизация.

1 голос
/ 05 ноября 2010

Две причины:

  • Позднее связывание медленное
  • виртуальные методы не могут быть встроены
0 голосов
/ 05 ноября 2010

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

Теперь это обычно не слишком заметно. Если для вызова вызываемого метода требуется 100 мс, даже если поиск виртуальной таблицы занимает 1 мс, это не имеет значения. Но если в случае массива вашему методу потребуется 0,5 мс, чтобы выполнить это снижение производительности на 1 мс, это будет весьма заметно.

Вы ничего не можете с этим поделать, кроме как не расширять Boost.Array или переименовывать свои методы, чтобы они не переопределяли.

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