Всем доброго времени суток. Наверняка все вы знаете, что такое оптимизация приложений и зачем это надо. Сегодня я хочу поговорить про микро оптимизацию, где она нужна в разработке и где её надо использовать.
В первую очередь, что такое оптимизация и в чем её отличие от микро оптимизации
Оптимизация - компоновка кода, чтобы он выполнял ту же работу, что и до оптимизации, но тратя как можно меньше ресурсов и времени.
Микро оптимизация - та же самая оптимизация, только которая практически не имеет смысла, т.е. сохраняет пользователю условно 20 наносекунд, или 1 лишнюю ячейку памяти, которые он даже не заметит.
Чем плоха микро оптимизация?
Микро оптимизация плоха тем, что на нее зачастую уходит много времени и/или интеллектуальных сил, или же приходится жертвовать чистотой кода (т.е. структурой приложения).
Где нужна микро оптимизация?
Несмотря на это, в некоторых случаях микро оптимизация нужна, как пример: метод strlen
в стандартной библиотеке языка Си.
Хороший пример микро оптимизации
Рассмотрим очевидную имплементацию strlen
:
size_t strlen(char* pointer)
{
size_t i = 0;
for (; true; i++)
{
if (pointer[i] == '\0') break;
}
return i;
}
Эта имплементация плоха тем, что она использует ячейку памяти большого размера (size_t
). Это плохо потому что такой метод будет вызываться миллионы раз в секунду и в таком случае лишние условные 20 наносекунд в итерацию имеют большую разницу.
Рассмотрим реалистичную имплементацию такого метода:
size_t
strlen(const char *str)
{
const char *s;
for (s = str; *s; ++s)
;
return (s - str);
}
Этот код взят из кода библиотеки OpenBSD. Этот код использует ячейку памяти размера 8 бит (char*
) вместо 16-битной (size_t
), и на 1-3 инструкции меньше (поправьте, я не особо понимаю ассемблер).
Плохой пример микро оптимизации
Возьмем как пример комментарий пользователя Malfist
, который оставил комментарий в документации PHP о методе is_null
[оригинал]:
Микро оптимизация того не стоит.
Вам придется повторить это 10 миллионов раз, чтобы заметить разницу, чуть больше чем 2 секунды.
$a===NULL; Заняло 1.2424390316 секунд
is_null($a); Заняло 3.70693397522 секунды
Разница = 2.46449494362
Разница/10,000,000 = 0.000000246449494362
Разница между двумя методами составляет меньше чем 250 наносекунд. Оптимизируйте что-то, что имеет значение.
В таком случае микро оптимизация правда не имеет смысла. is_null
делает код более читаемым, что хорошо сказывается на рабочем процессе. С другой стороны, сравнение с помощью $a === NULL
займет на 250 наносекунд меньше, которые никто не заметит.
Заключение
Микро оптимизация имеет смысл только в методах, которые выполняются много раз в секунду. Как пример: 3D рендеринг, низкоуровневые библиотеки языков программирований, программы для микроконтроллеров (как пример, Arduino Atmega328).
С другой стороны, на высоких уровнях микро оптимизация не имеет смысла и впринципе нецелесообразна: очень хорош пример с методом is_null
, описанный выше.
Также на мой взгляд оптимизировать БД ради пары лишних мегабайт тоже стоит отнести к категории микро оптимизации: несколько лишних мегабайт не имеют сильной разницы в такой ситуации.
Комментарии (5)
dalerank
08.06.2023 18:22+5Если вы откроете асм с -03, то скорее всего окажется что компилятор заменил ваш strlen на векторизованный вариант, оптимальный для данного CPU. Не стоит считать себя умнее компилятора, а ещё лучше использовать пикс для поиска таких мест, оптимизация "глазами" лет 20 как малоэффективна
Sun-ami
08.06.2023 18:22+1Этот код использует ячейку памяти размера 8 бит (char*) вместо 16-битной (size_t)
Оптимизация здесь заключается совсем не в изменении размера ячейки памяти, а в удалении индексной переменной. Этим получают сразу несколько выгод:
- Вдвое уменьшается количество регистров процессора, задействованных для хранения локальных переменных. Это в конечном итоге означает меньшее количество обращений к медленной памяти.
- Вместо индексной адресации используется косвенная, что для некоторых процессоров означает меньшее число ассемблерных команд и задействованных регистров, а для других — меньшее число задействованных блоков процессора, освобождение которых позволяет выполнять несколько команд одновременно
MiraclePtr
Во-первых, size_t будет совсем не обязательно 16 бит, а может быть 32 и 64 (в зависимости от архитектуры).
Во-вторых, в чем конкретно проблема здесь с размером ячейки памяти?
Процессору всегда удобнее оперировать с данными по размеру совпадающими с его машинным словом.
Если компилятор положит эту переменную в стек, стек аллоцируется на один и тот же размер вне зависимости какого размера переменную вы используете - разницы вообще никакой. Если компилятор положит ее в регистр процессора - у современных процессоров регистры вообще 32- или 64-битные, опять же, никакой разницы :)
pavel_pimenov
Этот код использует ячейку памяти размера 8 бит (
char*
) вместо 16-битной (size_t
), и на 1-3 инструкции меньше (поправьте, я не особо понимаю ассемблер).char* это указатель и он не равен 8 бит.
про ассемблер - сравнить можно тут на разных компиляторах:
https://gcc.godbolt.org/z/6xq5KE7rs
https://gcc.godbolt.org/z/dnrzx4x1f
MiraclePtr
А, точно. Я взял фразу из текста и не подумав начал по ней рассуждать :) Впрочем, все сказанное мною выше справедливо и для этого случая.
Всё становится даже ещё проще, size_t же как раз совпадает с размером указателя. Так что разницы вообще никакой, "размер ячейки" одинаков.