вставить лекцию о преждевременном обсуждении "корня зла"
Тем не менее, вот некоторые привычки, которые я приобрел, чтобы избежать ненужной эффективности, а в некоторых случаях сделать мой код более простым и более правильным.
Это не обсуждение общих принципов, а о некоторых вещах, о которых следует помнить, чтобы избежать внесения ненужных недостатков в код.
Знай своего биг-о
Это, вероятно, следует объединить с длительным обсуждением выше. Весьма здравый смысл, что цикл внутри цикла, где внутренний цикл повторяет вычисления, будет медленнее. Например:
for (i = 0; i < strlen(str); i++) {
...
}
Это займет ужасное количество времени, если строка действительно длинная, потому что длина пересчитывается на каждой итерации цикла. Обратите внимание, что GCC фактически оптимизирует этот случай, потому что strlen()
помечен как чистая функция.
При сортировке миллиона 32-битных целых чисел пузырьковая сортировка была бы неправильным способом . В общем, сортировка может быть выполнена за O (n * log n) (или лучше, в случае радикальной сортировки), поэтому, если вы не знаете, что ваши данные будут небольшими, ищите алгоритм, который по крайней мере O (n * войти n).
Аналогично, при работе с базами данных следует помнить об индексах. Если у вас SELECT * FROM people WHERE age = 20
и у вас нет индекса по людям (возрасту), для этого потребуется последовательное сканирование 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;
}