Советы по производительности C ++ и практические правила кто-нибудь? - PullRequest
7 голосов
/ 24 ноября 2008

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

Ответы [ 26 ]

16 голосов
/ 24 ноября 2008

На ум приходит известная цитата:

«Мы должны забыть о малой эффективности, скажем, в 97% случаев: преждевременная оптимизация - корень всего зла». (Кнут, Дональд. Структурированное программирование с переходом к заявлениям, ACM Journal Computing Surveys, том 6, № 4, декабрь 1974. с.268.)

Но, может быть, вам все равно не следует передавать большие структуры данных по значению ...: -)

Редактировать: И, возможно, также избегать O (N ^ 2) или более сложных алгоритмов ...

12 голосов
/ 24 ноября 2008

Совет № 1 по производительности - для раннего и частого профилирования вашего кода. Существует множество общих советов «не делай этого», но действительно сложно гарантировать, что это повлияет на производительность вашего приложения. Зачем? Каждое приложение отличается. Легко сказать, что передача вектора по значению - это плохо, если у вас много элементов, но использует ли ваша программа даже вектор (вы, вероятно, должны ...)?

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

EDIT:

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

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

В типичном проекте я обнаружил, что у меня есть код, отвечающий этим критериям 30% -40% пути проекта (100% в руках клиентов). Я слабо классифицирую это время как раннее.

11 голосов
/ 25 ноября 2008

Кто-то упомянул указатели на функции (и почему вы должны использовать if). Что ж, еще лучше: используйте вместо этого функторы, они встроены и обычно имеют ноль накладных расходов . Функтор - это структура (или класс, но обычно первый), который перегружает оператор () и экземпляры которого могут использоваться как обычная функция:

template <typename T>
struct add {
    operator T ()(T const& a, T const& b) const { return a + b; }
};

int result = add<int>()(1, 2);

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

EDIT Явная квалификация типа <int> необходима в приведенном выше коде. Вывод типа работает только для вызовов функций, но не для создания экземпляров. Однако его часто можно опустить, используя вспомогательную функцию make. Это делается в STL за pair с:

template <typename T1, typename T2>
pair<T1, T2> make_pair(T1 const& first, T2 const& second) {
    return pair<T1, T2>(first, second);
}

// Implied types:
pair<int, float> pif = make_pair(1, 1.0f);

Кто-то упомянул в комментариях, что функторы иногда называют «функционоидами». Да иш - но не совсем. На самом деле, «функтор» - это (несколько странное) сокращение от «функциональный объект». Функциональный объект концептуально похож, но реализуется с помощью виртуальных функций (хотя иногда они используются как синонимы). Например, функционал может выглядеть так (вместе с необходимым определением интерфейса):

template <typename T, typename R>
struct UnaryFunctionoid {
    virtual R invoke(T const& value) const = 0;
};

struct IsEvenFunction : UnaryFunctionoid<int, bool> {
    bool invoke(int const& value) const { return value % 2 == 0; }
};

// call it, somewhat clumsily:
UnaryFunctionoid const& f = IsEvenFunction();
f.invoke(4); // true

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

В C ++ FAQ есть больше, чтобы сказать на эту тему.

11 голосов
/ 24 ноября 2008
  • По возможности используйте if или switch вместо вызовов через указатели функций. Уточнение: void doit(int m) { switch(m) { case 1: f1(); break; case 2: f2(); break; } } вместо void doit(void(*m)()) { m(); } может включать звонки.
  • Когда это возможно и не причиняет вреда, предпочитайте CRTP виртуальным функциям
  • По возможности избегайте строк C и используйте класс String. Это будет быстрее, чаще всего. (постоянная длительность "мера", добавление амортизированного постоянного времени, ...)
  • Всегда передает определяемые пользователем типизированные значения (кроме случаев, когда это не имеет смысла. Например, итераторы) путем ссылки на const (T const &) вместо копирования значения.
  • Для пользовательских типов всегда предпочитайте ++t вместо t++
  • Используйте const рано, часто. Наиболее важно улучшить читаемость.
  • Постарайтесь свести new к минимуму. Всегда предпочитайте автоматические переменные (в стеке), если это возможно
  • Вместо того, чтобы заполнять массивы самостоятельно, предпочитайте инициализацию с пустым списком инициализаторов, например T t[N] = { };, если вы хотите нули.
  • Используйте список инициализатора конструктора как можно чаще, особенно при инициализации определенных пользователем типизированных элементов.
  • Использовать функторы (типы с operator() перегружены). Они встроены лучше, чем вызовы через указатели функций.
  • Не используйте такие классы, как std::vector или std::string, если количество фиксированного размера не растет. Используйте boost::array<T, Size> или голый массив и используйте его правильно.

И действительно, я почти забыл это:

Преждевременная оптимизация - корень всего зла

8 голосов
/ 24 ноября 2008

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

Кроме того, алгоритмическая оптимизация обычно оказывает большее влияние, чем микро. Использование A-звезды вместо поиска пути грубой силы будет быстрее, точно так же, как круги Брезенхэма лучше, чем использование sin / cos. Конечно, есть исключения, но они очень (очень) редки (<0,1%). Если у вас хороший дизайн, изменение алгоритма изменит только один модуль в вашем коде. Легко. </p>

7 голосов
/ 24 ноября 2008

Использовать существующий проверенный код, который использовался и использовался повторно. (Пример: STL, boost против скручивания собственных контейнеров и алгоритмов)

Обновление из-за комментариев: ПРАВИЛЬНО используйте существующий проверенный код, который использовался и использовался повторно.

4 голосов
/ 25 ноября 2008

Еще один момент: Самый быстрый код - это код, который не существует. Это означает, что чем более надёжным и функциональным должен быть ваш проект, тем медленнее он будет. Итог: пропускайте пух всякий раз, когда это возможно, и при этом убедитесь, что вы все еще соответствуете требованиям.

4 голосов
/ 24 ноября 2008

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

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

3 голосов
/ 25 ноября 2008

Два из лучших советов для C ++:

Покупка Effective C ++, Скотт Мейерс.

Тогда приобретите Скотта Мейерса More Effective C ++.

2 голосов
/ 24 ноября 2008

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

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