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

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

is_array($array)

и

$array === (array) $array

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

Ответ 1

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

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

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

Ответ 2

Ну, для тривиально малого массива $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) 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) для стека (из-за вызова функции), но это будет доминировать только во время выполнения для чрезвычайно малых значений n (мы видели в вышеприведенном тесте всего 10 элементов массива было достаточно, чтобы полностью устранить все различия).

Нижняя линия

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

script Я использовал для эталона:

$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 для кода итерации...

Ответ 3

Оптимизирована ли микро-оптимизация?

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

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

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

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

Ответ 4

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

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

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

Ответ 5

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

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

  • это не мешает мне печатать.
  • Цель кода по-прежнему очевидна.

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


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

Ответ 6

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

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

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

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

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

Ответ 7

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

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

is_array($v): 10 секунд

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

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

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

Ответ 8

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

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

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

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

Ответ 9

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

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

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

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

Ответ 10

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

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

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

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

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

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

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