Стоит ли микрооптимизация времени?

Я разработчик PHP и всегда думал, что микрооптимизация не стоит времени. Если вам действительно нужна эта дополнительная производительность, вы должны либо написать свое программное обеспечение, чтобы оно было архитектурно быстрее, либо написать расширение C ++ для обработки медленных задач (или, что еще лучше, скомпилировать код с помощью HipHop). Тем не менее, сегодня помощник сказал мне, что есть большая разница в

is_array($array)

и

$array === (array) $array

, и я был похож на «да, это действительно бессмысленное сравнение», но он не согласился бы со мной .. и он является лучшим разработчиком в нашей компании и отвечает за веб-сайт, который выполняет около 50 миллионов запросов SQL в день, например. Итак, я задаюсь вопросом: может ли он ошибаться или микрооптимизация действительно стоит времени и когда?

37
задан Luuklag 13 November 2018 в 19:19
поделиться

7 ответов

Микрооптимизация того стоит , когда у вас есть доказательства того, что вы оптимизируете узкое место .

Обычно это того не стоит - напишите максимально читаемый код и используйте реалистичные тесты производительности для проверки производительности. Если и когда вы обнаружите, что у вас есть узкое место, микрооптимизируйте только этот фрагмент кода (измеряя по мере продвижения). Иногда небольшая микрооптимизация может иметь огромное значение.

Но не микрооптимизируйте весь код ... его будет намного сложнее поддерживать, и вы, вполне возможно, обнаружите, что либо пропустили реальное узкое место, или что ваши микрооптимизации вредят производительности, а не помогают.

81
ответ дан 27 November 2019 в 03:58
поделиться

Что ж, для тривиально маленького массива $ array === (array) $ array значительно быстрее, чем is_array ($ array) . Более чем в 7 раз быстрее. Но каждый вызов имеет порядок 1,0 x 10 ^ -6 секунд ( 0,000001 секунды ). Так что, если вы не называете это буквально тысячи раз, оно того не стоит. И если вы вызываете его тысячи раз, я бы посоветовал вам что-то сделать не так ...

Разница возникает, когда вы имеете дело с большим массивом. Поскольку $ array === (array) $ array требует, чтобы новая переменная была скопирована, необходимо выполнить внутреннюю итерацию массива для сравнения, это, вероятно, будет ЗНАЧИТЕЛЬНО медленнее для большого массива. Например, в массиве из 100 целочисленных элементов is_array ($ array) находится в пределах погрешности ( <2% ) is_array () с небольшой массив (занимает 0,0909 секунд для 10 000 итераций). Но $ array = (array) $ array работает очень медленно. Всего для 100 элементов он уже вдвое медленнее, чем is_array () (входящий в 0.203 секунд). Для 1000 элементов is_array остался прежним, но сравнение приведений увеличилось до 2,0699 секунд ...

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

Это компромисс. Если массив достаточно мал, итерация будет более эффективной. Но по мере увеличения размера массива он будет становиться все медленнее (и, следовательно, вызов функции станет быстрее).

Другой способ взглянуть на это

Другой способ взглянуть на это - изучить алгоритмическую сложность каждого приведения.

Давайте сначала посмотрим на is_array () . Исходный код в основном показывает, что это операция O (1) . Это означает, что это операция с постоянным временем. Но нам также нужно посмотреть на вызов функции. В PHP вызовы функций с одним параметром массива имеют вид O (1) или O (n) в зависимости от того, нужно ли запускать копирование при записи. Если вы вызываете is_array ($ array) , когда $ array является ссылкой на переменную, будет запущено копирование при записи и произойдет полная копия переменной.

Таким образом, is_array () - лучший вариант O (1) и худший вариант O (n) .Но пока вы не используете ссылки, это всегда O (1) ...

Версия с приведением, с другой стороны, выполняет две операции. Он выполняет приведение, а затем проверяет равенство. Итак, давайте рассмотрим каждого в отдельности. Обработчик оператора приведения сначала заставляет копию входной переменной. Неважно, ссылка это или нет. Поэтому простое использование оператора приведения (array) заставляет итерацию O (n) по массиву приводить его (через вызов copy_ctor).

Затем он преобразует новую копию в массив. Это O (1) для массивов и примитивов, но O (n) для объектов.

Затем выполняется идентичный оператор. Обработчик - это просто прокси для is_identical_function () . Теперь is_identical произведет короткое замыкание, если $ array не является массивом. Следовательно, он имеет лучший случай из O (1) . Но если $ array является массивом, он может снова закоротить, если хеш-таблицы идентичны (что означает, что обе переменные являются копиями друг друга для копирования при записи). Таким образом, этот случай тоже O (1) . Но помните, что мы принудительно создали копию выше, поэтому мы не можем этого сделать, если это массив. Итак, это O (n) благодаря zend_hash_compare ...

Итак, конечный результат - это таблица для наихудшего времени выполнения:

+----------+-------+-----------+-----------+---------------+
|          | array | array+ref | non-array | non-array+ref |
+----------+-------+-----------+-----------+---------------+
| is_array |  O(1) |    O(n)   |    O(1)   |     O(n)      |
+----------+-------+-----------+-----------+---------------+
| (array)  |  O(n) |    O(n)   |    O(n)   |     O(n)      |
+----------+-------+-----------+-----------+---------------+

Обратите внимание, что похоже, что они масштабируются то же самое для ссылок. Они этого не делают. Оба они масштабируются линейно для ссылочных переменных. Но постоянный коэффициент меняется.Например, в указанном массиве размера 5 is_array выполнит 5 выделений памяти и 5 копий памяти, а затем 1 проверку типа. Версия с приведением, с другой стороны, выполнит 5 выделений памяти, 5 копий памяти, за которыми последуют 2 проверки типа,за которыми следуют 5 проверок типа и 5 проверок равенства ( memcmp () и т.п.). Итак, n = 5 дает 11 операций для is_array , но 22 операции для === (array) ...

Теперь is_array () имеет накладные расходы O (1) на выталкивание стека (из-за вызова функции), но это будет доминировать во время выполнения только для чрезвычайно малых значений n (мы видели в тесте всего 10 элементов массива было достаточно, чтобы полностью устранить все различия).

Итог

Я бы посоветовал пойти на удобочитаемость. Я считаю, что is_array ($ array) гораздо более читабелен, чем $ array === (array) $ array . Так вы получите лучшее из обоих миров.

Скрипт, который я использовал для теста:

$elements = 1000;
$iterations = 10000;

$array = array();
for ($i = 0; $i < $elements; $i++) $array[] = $i;

$s = microtime(true);
for ($i = 0; $i < $iterations; $i++) is_array($array);
$e = microtime(true);
echo "is_array completed in " . ($e - $s) ." Seconds\n";

$s = microtime(true);
for ($i = 0; $i < $iterations; $i++) $array === (array) $array;
$e = microtime(true);
echo "Cast completed in " . ($e - $s) ." Seconds\n";

Edit: Для записи, эти результаты были с 5.3.2 в Linux ...

Edit2: Исправлена ​​причина, по которой массив работает медленнее (это связано с повторным сравнением, а не с памятью). См. compare_function для кода итерации ...

138
ответ дан 27 November 2019 в 03:58
поделиться

Как говорится, микрооптимизация обычно стоит потраченного времени только в самых маленьких, наиболее критичных для производительности горячих точках вашего кода, только после того, как вы докажете , что именно там и есть узкое место. Однако я хотел бы немного конкретизировать это, чтобы указать на некоторые исключения и области недопонимания.

  1. Это не означает, что производительность вообще не следует рассматривать заранее. Я определяю микрооптимизацию как оптимизацию, основанную на низкоуровневых деталях компилятора / интерпретатора, оборудования и т. Д. По определению, микрооптимизация не влияет на сложность big-O. Макро -оптимизации следует рассматривать заранее, особенно когда они имеют большое влияние на высокоуровневый дизайн. Например, можно с уверенностью сказать, что если у вас большая, часто используемая структура данных, линейный поиск O (N) ее не сократит. Даже вещи, которые являются лишь постоянными условиями, но имеют большие и очевидные накладные расходы, возможно, стоит учесть заранее. Два больших примера - это чрезмерное выделение памяти / копирование данных и двойное вычисление одного и того же, тогда как вы можете вычислить его один раз и сохранить / повторно использовать результат.

  2. Если вы делаете что-то, что делалось раньше в несколько ином контексте, могут быть некоторые узкие места, которые настолько хорошо известны, что их разумно рассмотреть заранее. Например, недавно я работал над реализацией алгоритма БПФ (быстрое преобразование Фурье) для стандартной библиотеки D.Поскольку раньше так много БПФ было написано на других языках, хорошо известно, что самым большим узким местом является производительность кеша, поэтому я сразу же вошел в проект, думая о том, как это оптимизировать.

4
ответ дан 27 November 2019 в 03:58
поделиться

Что ж, я собираюсь предположить, что is_array ($ array) является предпочтительным способом, а $ array === (array) $ array предположительно более быстрым способом (что поднимает вопрос, почему is_array не реализован с использованием этого сравнения, но я отвлекся).

Я почти никогда не вернусь в свой код и вставлю микрооптимизацию * , но я часто буду вставлять их в код по мере написания, при условии:

  • это не замедляет мой набор текста.
  • цель кода все еще ясна.

Эта конкретная оптимизация терпит неудачу по обоим пунктам.


* Хорошо, на самом деле я знаю, но это больше связано с тем, что у меня есть прикосновение ОКР, а не с хорошими практиками разработки.

3
ответ дан 27 November 2019 в 03:58
поделиться

В общем, вам не следует писать оптимизацию, которая делает ваш код более уродливым или трудным для понимания; в моей книге это определенно попадает в эту категорию.

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

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

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

Разумеется, рассеивание подобных микрооптимизаций по существующему, работающему, протестированному коду - ужасное дело, оно обязательно приведет к регрессу и почти наверняка не принесет заметной разницы.

3
ответ дан 27 November 2019 в 03:58
поделиться

Что ж, нужно учитывать не только скорость. Когда вы читаете эту «более быструю» альтернативу, вы сразу думаете: «О, это проверка, является ли переменная массивом», или вы думаете «... wtf»?

Потому что на самом деле - при рассмотрении этого метода , как часто это называется? Каково точное преимущество в скорости? Складывается ли это, когда массив больше или меньше? Без тестов нельзя делать оптимизацию.

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

Кроме того, не пугайтесь опыта этого парня, как говорили другие, и думайте сами.

1
ответ дан 27 November 2019 в 03:58
поделиться

Стоит ли микрооптимизация потраченного времени?

Нет, если только это не так.

Другими словами, априори , ответ будет «нет», но после вы знаете, что конкретная строка кода потребляет здоровый процент времени часов, тогда и только тогда стоит ли оптимизировать.

Другими словами, сначала профилируйте, потому что иначе у вас не будет этих знаний. Это метод, на который я полагаюсь , независимо от языка или ОС.

Добавлено: когда многие программисты обсуждают производительность, начиная с экспертов, они обычно говорят о том, «где» программа проводит свое время.Есть скрытая двусмысленность в том «где», которое уводит их от вещей, которые могли бы сэкономить больше всего времени, а именно от мест вызова функций. В конце концов, «вызов Main» в верхней части приложения - это «место», в котором программа почти никогда не находится, но отвечает за 100% времени. Теперь вы не собираетесь избавляться от «call Main», но почти всегда есть другие вызовы, от которых вы можете избавиться. Пока программа открывает или закрывает файл, или форматирует некоторые данные в строку текста, или ожидает подключения к сокету, или «создает новое» - загружает фрагмент памяти или передает уведомление по всей большой структуре данных, это тратя много времени на вызовы функций, но разве это «где»? В любом случае, эти вызовы быстро обнаруживаются с помощью образцов стека.

11
ответ дан 27 November 2019 в 03:58
поделиться
Другие вопросы по тегам:

Похожие вопросы: