Данная статья является вольным переводом статьи Optimizing C++/Code optimization/Faster operations. Оригинал найти можно по ссылке. Первая часть лежит здесь.
Часть 2
Префиксный или постфиксный оператор
Префиксный оператор предпочтительнее постфиксного. При работе с примитивными типами префиксные и постфиксные арифметические операции, вероятно, будут иметь одинаковую производительность. Однако с объектами, операторы постфикса могут заставить объект создать собственную копию, чтобы сохранить свое начальное состояние (которое должно быть возвращено в результате операции), а также вызвать побочный эффект операции. Рассмотрим следующий пример:
class IntegerIncreaser
{
int m_Value;
public:
/* Postfix operator. */
IntegerIncreaser operator++ (int) {
IntegerIncreaser tmp (*this);
++m_Value;
return tmp;
};
/* Prefix operator. */
IntegerIncreaser operator++ () {
++m_Value;
return *this;
};
};
Поскольку операторы постфикса обязаны возвращать неизмененную версию значения, которое увеличивается (или уменьшается) — независимо от того, используется ли результат на самом деле — скорее всего, он сделает копию. Итераторы STL (например) более эффективны при изменении с помощью префиксных операторов.
Встроенные функции
Если вы не используете параметры компилятора для оптимизации всей программы, чтобы компилятор мог встраивать любую функцию, то есть смысл перенести некоторые функции в заголовочные файлы как inline
функции, то есть объявить их встроенными.
Если верить руководству (например компилятора gcc "5.34 An Inline Function is As Fast As a Macro"), то inline
функция выполняется (так же быстро как макрос) быстрее чем обычная из-за устранения служебных вызовов, но стоит учитывать, что не все функции будут работать быстрее, а некоторые функции, объявленные как inline
способны замедлить работу всей программы.
Целочисленное деление на постоянную
Когда вы делите целое число (которое является положительным или равным нулю) на константу, преобразуйте целое число в unsigned.
Например, если s — целое число со знаком, u — целое число без знака, а C — выражение с постоянным целым числом (положительное или отрицательное), операция s / C медленнее, чем u / C, а s% C медленнее, чем u% C. Это проявляется наиболее явно, когда С — степень двойки, но, все же, при делении знак стоит учитываться.
Кстати, преобразование из signed
в unsigned
ничего не будет нам стоить, поскольку это только другая интерпретация одних и тех же битов. Следовательно, если s — целое число со знаком, которое будет использоваться в дальнейшем, как положительное или ноль, вы можете ускорить его деление, используя следующие выражения: (unsigned
) s / C и (unsigned
) s% C.
Использование нескольких массивов вместо полей структуры
Вместо обработки одного массива совокупных объектов параллельно обрабатывайте два или более массива одинаковой длины. Например, вместо следующего кода:
const int n = 10000;
struct { double a, b, c; } s[n];
for (int i = 0; i < n; ++i) {
s[i].a = s[i].b + s[i].c;
}
следующий код может быть быстрее:
const int n = 10000;
double a[n], b[n], c[n];
for (int i = 0; i < n; ++i) {
a[i] = b[i] + c[i];
}
Используя эту перегруппировку, «a», «b» и «c» могут обрабатываться командами обработки массива, которые значительно быстрее, чем скалярные инструкции. Эта оптимизация может иметь нулевые или неблагоприятные результаты для некоторых архитектур.
Еще лучше перемежать массивы:
const int n = 10000;
double interleaved[n * 3];
for (int i = 0; i < n; ++i) {
const size_t idx = i * 3;
interleaved[idx] = interleaved[idx + 1] + interleaved[idx + 2];
}
PS: Учтите, что каждый случай нужно тестировать, а не оптимизировать преждевременно.