Микроэкономика стоит того времени?

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

is_array($array) 

а также

 $array === (array) $array 

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

Solutions Collecting From Web of "Микроэкономика стоит того времени?"

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

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

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

Ну, для тривиально малого массива массив $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 в 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($array) копия-на-запись и будет выполнена полная копия переменной.

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

С другой стороны, литая версия выполняет две операции. Он выполняет бросок, а затем выполняет проверку равенства. Поэтому давайте посмотрим на каждого отдельно. Обработчик оператора переноса сначала заставляет копию входной переменной. Неважно, если это ссылка или нет. Поэтому простое использование оператора (array) casting заставляет 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 ops для is_array , но 22 ops для ===(array)

Теперь is_array() имеет накладные расходы O (1) push (из-за вызова функции), но это будет доминировать только во время выполнения для крайне малых значений 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 для итерационного кода …

Микроэкономика стоит того времени?

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  • это не мешает мне печатать.
  • цель кода по-прежнему ясна.

Эта конкретная оптимизация не срабатывает по обоим показателям.


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

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

Здесь некоторое сравнение:

is_array($v) : 10 сек.

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

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

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

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

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

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

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

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

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

print vs echo, который быстрее?

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

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

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

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

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

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

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

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