Какова «цена» использования наследования во встроенной среде с C ++? - PullRequest
3 голосов
/ 09 августа 2011

Я начинаю новый встраиваемый проект с C ++, и мне было интересно, не слишком ли дорого использовать дизайн, ориентированный на интерфейс.Как то так:

typedef int data;

class data_provider {

public:
    virtual data get_data() = 0;
};

class specific_data_provider : public data_provider {
public:
    data get_data() {
    return 7;
    }
};

class my_device {
public:
    data_provider * dp;
    data d;

    my_device (data_provider * adp) {
    dp = adp;
    d = 0;
    }

    void update() {
    d = dp->get_data();
    }
};

int
main() {
    specific_data_provider sdp;
    my_device dev(&sdp);

    dev.update();

    printf("d = %d\n", dev.d);

    return 0;
}

Ответы [ 4 ]

4 голосов
/ 10 августа 2011

Наследование само по себе бесплатно.Например, ниже, B и C одинаковы с точки зрения производительности / памяти:

struct A { int x; };
struct B : A { int y; };
struct C { int x, y; };

Наследование требует затрат только при наличии виртуальных функций.

struct A { virtual ~A(); };
struct B : A { ... };

Здесь практически во всех реализациях и A, и B будут на один размер указателя больше из-за виртуальной функции.

Виртуальные функции также имеют другие недостатки (по сравнению с не виртуальными функциями)

  1. Виртуальные функции требуют, чтобы вы вызывали vtable при вызове.Если этот vtable отсутствует в кеше, вы получите промах L2, который может быть невероятно дорогим для встраиваемых платформ (например, более 600 циклов на игровых консолях текущего поколения).
  2. Даже если вы попадете в кэш L2, если вы переходите к множеству различных реализаций, вы, скорее всего, получите ошибочное прогнозирование переходов для большинства вызовов, что приведет к сбросу конвейера, что опять-таки будет стоить много циклов.практически невозможно встроить (за исключением редких случаев).Если вызываемая функция невелика, это может привести к серьезному снижению производительности по сравнению с встроенной не виртуальной функцией.
  3. Виртуальные вызовы могут способствовать раздуванию кода.Каждый вызов виртуальной функции добавляет несколько байтов инструкций для поиска виртуальной таблицы и много байтов для самой виртуальной таблицы.

Если вы используете множественное наследование, тогда все становится хуже.

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

Мой совет - проектировать для производительности, если это важно для вас.Дизайн, чтобы избежать необходимости виртуальных функций, если это вообще возможно.Проектируйте свои данные вокруг кеша: предпочитайте массивы структурам данных на основе узлов, таким как std::list и std::map.Даже если у вас есть контейнер из нескольких тысяч элементов с частыми вставками в середину, я бы по-прежнему использовал массив для определенных архитектур.Несколько тысяч циклов, которые вы теряете при копировании данных для вставок, вполне могут быть компенсированы местоположением кэша, которого вы достигнете при каждом обходе (помните стоимость одного L2 промаха кэша? Вы можете ожидать много таких, когдаобход связанного списка)

4 голосов
/ 09 августа 2011

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

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

1 голос
/ 09 августа 2011

Действительно зависит от вашего оборудования.Наследование само по себе, вероятно, ничего не стоит.Виртуальные методы требуют некоторого количества памяти для таблицы vTable в каждом классе.Включение обработки исключений, вероятно, будет стоить вам еще больше как в памяти, так и в производительности.Я широко использовал все возможности C ++ на платформе NetBurner с чипами, такими как MOD5272, которые имеют пару мегабайт флэш-памяти и 8 мегабайт оперативной памяти.Кроме того, некоторые вещи могут зависеть от реализации, в цепочке инструментов GCC, которую я использую, когда cout используется вместо printf, вы получаете большой удар памяти (кажется, что связываются в кучу библиотек).Возможно, вас заинтересует сообщение в блоге о стоимости безопасного кода типа , которое я написал.Вам нужно будет провести аналогичные тесты в своей среде, чтобы действительно ответить на ваш вопрос.

0 голосов
/ 09 августа 2011

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

...