Я говорю о том, как мы пишем простые рутины, чтобы улучшить производительность, не делая код более трудным для чтения... например, вот типичный для нас прием:
for(int i = 0; i < collection.length(); i++ ){
// stuff here
}
Но, я обычно делаю это, когда foreach
не применимо:
for(int i = 0, j = collection.length(); i < j; i++ ){
// stuff here
}
Я думаю, что это лучший подход, поскольку он будет вызывать метод length
только один раз... моя девушка говорит, что это криптовалюта. Есть ли еще какой-нибудь простой трюк, который вы используете в своих разработках?
Тем не менее, вот некоторые привычки, которые я приобрел, чтобы избежать ненужной эффективности, а в некоторых случаях сделать свой код более простым и правильным.
Это не обсуждение общих принципов, а некоторые вещи, о которых следует помнить, чтобы избежать введения ненужной неэффективности в код.
Возможно, это следует объединить с длинным обсуждением выше. Вполне логично, что цикл внутри цикла, где внутренний цикл повторяет вычисления, будет медленнее. Например:
for (i = 0; i < strlen(str); i++) {
...
}
Это займет ужасающее количество времени, если строка действительно длинная, потому что длина пересчитывается на каждой итерации цикла. Обратите внимание, что GCC фактически оптимизирует этот случай, потому что strlen()
помечен как чистая функция.
При сортировке миллиона 32-битных целых чисел пузырьковая сортировка будет неправильным способом. В целом, сортировка может быть выполнена за время O(n * log n) (или лучше, в случае радиксной сортировки), поэтому, если вы не знаете, что ваши данные будут небольшими, ищите алгоритм, который по крайней мере O(n * log n).
Аналогичным образом, работая с базами данных, помните об индексах. Если у вас SELECT * FROM people WHERE age = 20
нет индекса на people(age), то потребуется последовательное сканирование O(n), а не гораздо более быстрое сканирование O(log n) по индексу.
При программировании на языке C следует помнить, что некоторые арифметические операции дороже других. Для целых чисел иерархия выглядит следующим образом (сначала наименее затратные):
+ - ~ & | ^
<< >>
*
/
Конечно, компилятор обычно оптимизирует такие вещи, как n / 2
до n >> 1
автоматически, если вы ориентируетесь на основной компьютер, но если вы ориентируетесь на встраиваемое устройство, вы можете не получить такой роскоши.
Кроме того, % 2
и & 1
имеют разную семантику. Деление и модуль обычно округляются в сторону нуля, но это определяется реализацией. Старые добрые >>
и &
всегда округляются в сторону отрицательной бесконечности, что (на мой взгляд) имеет гораздо больше смысла. Например, на моем компьютере:
printf("%d\n", -1 % 2); // -1 (maybe)
printf("%d\n", -1 & 1); // 1
Следовательно, используйте то, что имеет смысл. Не думайте, что вы хороший мальчик, используя % 2
, когда вы изначально собирались написать & 1
.
Избегайте тяжелых операций с плавающей точкой, таких как pow()
и log()
, в коде, который в них не нуждается, особенно при работе с целыми числами. Возьмем, к примеру, чтение числа:
int parseInt(const char *str)
{
const char *p;
int digits;
int number;
int position;
// Count the number of digits
for (p = str; isdigit(*p); p++)
{}
digits = p - str;
// Sum the digits, multiplying them by their respective power of 10.
number = 0;
position = digits - 1;
for (p = str; isdigit(*p); p++, position--)
number += (*p - '0') * pow(10, position);
return number;
}
Такое использование pow()
(и преобразования int
<->double
, необходимые для его использования) не только довольно дорого, но и создает возможность потери точности (кстати, в приведенном выше коде проблем с точностью нет). Вот почему я морщусь, когда вижу, как этот тип функции используется в нематематическом контексте.
Также обратите внимание, что "умный" алгоритм ниже, который умножает на 10 на каждой итерации, на самом деле более лаконичен, чем код выше:
int parseInt(const char *str)
{
const char *p;
int number;
number = 0;
for (p = str; isdigit(*p); p++) {
number *= 10;
number += *p - '0';
}
return number;
}