Стоит ли микрооптимизация времени? - PullRequest
36 голосов
/ 12 августа 2010

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

is_array($array)

и

$array === (array) $array

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

Ответы [ 10 ]

136 голосов
/ 12 августа 2010

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

Разница возникает, когда вы работаете с большим массивом.Так как $array === (array) $array требует, чтобы новая переменная была скопирована, требуется, чтобы массив был внутренне повторен для сравнения, вероятно, он будет ЗНАЧИТЕЛЬНО медленнее для большого массива.Например, в массиве с 100 целочисленными элементами is_array($array) находится в пределах погрешности (< 2%) в is_array() с небольшим массивом (входящий в 0.0909 секунд для 10000 итераций).Но $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 проверка типа. Версия Cast, с другой стороны, будет выполнять 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";

Редактировать: Для записи, эти результаты были с 5.3.2 на Linux ...

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

80 голосов
/ 12 августа 2010

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

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

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

11 голосов
/ 24 августа 2010

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

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

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

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

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

4 голосов
/ 13 августа 2010

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

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

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

3 голосов
/ 08 ноября 2012

У нас было одно место, где оптимизация была действительно полезной.

Вот некоторые сравнения:

is_array($v): 10 сек

$v === (array)$v: 3,3 с

($v.'') === 'Array': 2,6 с

Последний делает приведение к строке, массив всегда приводится к строке со значением 'массив'. Эта проверка будет неправильной, если $ v является строкой со значением 'Array' (в нашем случае это никогда не происходит).

3 голосов
/ 13 августа 2010

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

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

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

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

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

3 голосов
/ 12 августа 2010

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

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

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

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


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

1 голос
/ 23 апреля 2013

Микрооптимизация не стоит.Читаемость кода гораздо важнее, чем микрооптимизация.

Отличная статья о бесполезной микрооптимизации Фабьена Потенциера (создателя Symfony framework)

print vs echo, какой из них быстрее?

Print использует еще один код операции, потому что он действительно что-то возвращает.Мы можем сделать вывод, что эхо быстрее, чем печать.Но один код операции ничего не стоит, на самом деле ничего.Даже если в сценарии есть сотни вызовов для печати.Я попробовал на свежую установку WordPress.Сценарий останавливается до того, как он заканчивается «Ошибка шины» на моем ноутбуке, но количество кодов операций уже превысило 2,3 миллиона.Достаточно сказано.

1 голос
/ 12 августа 2010

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

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

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

0 голосов
/ 04 декабря 2017

ИМХО микро-оптимизации сегодня даже более актуальны, чем алгоритмические оптимизации , если вы работаете в области, критически важной для производительности. Это может быть большим , если , потому что многие люди на самом деле не работают в областях, критичных к производительности, даже для программного обеспечения, критически важного для производительности, поскольку они могут просто делать высокоуровневые вызовы в стороннюю библиотеку, которая выполняет критически важная работа. Например, в наши дни многие люди, пытающиеся написать программное обеспечение для изображений или видео, могут писать не критичный к производительности код, выражающий их желание на уровне изображений, без необходимости вручную вручную проходить несколько миллионов пикселей со скоростью более 100 кадров в секунду. Библиотека делает это для них.

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

Если вы сегодня используете какой-либо достаточно высокоуровневый язык, в том числе C ++, у вас уже есть достаточно эффективные универсальные структуры данных и алгоритмы. Нет никакого оправдания, если только вы не начинающий студент CS, просто намочив свои ноги и заново изобретая самые примитивные колеса для применения квадратичной сортировки сложности к массивным входным размерам или поискам с линейным временем, которые могут быть выполнены в постоянном времени с соответствующими данными структур.

Таким образом, как только вы пройдете этот начальный уровень, приложения, критичные к производительности, по-прежнему будут иметь сильно отличающиеся характеристики производительности. Зачем? Почему одно программное обеспечение для обработки видео имеет в три раза большую частоту кадров и больше интерактивных предварительных просмотров видео, чем другое, если разработчики не делают ничего чрезвычайно глупого алгоритмически? Почему один сервер, который делает очень похожую вещь, мог бы обрабатывать десять раз запросы с одним и тем же оборудованием? Почему это программное обеспечение загружает сцену за 5 секунд, в то время как другая загружает те же данные за 5 минут? Почему эта красивая игра имеет гладкую и равномерную частоту кадров, в то время как другая уродливая, более примитивная, с графикой и освещением, и заикается здесь и там, занимая вдвое больше памяти?

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

Таким образом, если вы хотите писать конкурентоспособное эффективное программное обеспечение сегодня, гораздо чаще, чем когда-либо, это сводится к таким вещам, как многопоточность, SIMD, GPU, GPGPU, улучшая локальность ссылок с лучшими шаблонами доступа к памяти (циклическое разбиение на блоки, SoA расщепление горячих / холодных полей и т. д., возможно, даже оптимизация для прогнозирования ветвлений в экстремальных случаях и т. д., не так уж много алгоритмических прорывов, если только вы не занимаетесь чрезвычайно неизведанной территорией, где до этого не сталкивались программисты.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...