Насколько важна функция вызова функции PHP?

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

// Change this:
} catch (Exception $e) {
  print "It seems that error " . $e->getCode() . " occured";
  log("Error: " . $e->getCode());
}

// To this:
} catch (Exception $e) {
  $code = $e->getCode();
  print "It seems that error " . $code . " occured";
  log("Error: " . $code);
}

Второй пример

// Change this:
$customer->setProducts($products);

// To this:
if (!empty($products)) {
  $customer->setProducts($products);
}

В первом примере я нахожу, что при назначении $e->getCode() to $code объявлений возникают небольшие когнитивные накладные расходы; "Что такое" код "? Ах, это код из исключения". В то время как второй пример добавляет циклическую сложность. В обоих примерах я нахожу, что оптимизация достигается за счет удобочитаемости и ремонтопригодности.

Значит ли это увеличение производительности или это микро-оптимизация?

Я должен отметить, что мы застряли с PHP 5.2 прямо сейчас.

Я провел очень грубые тесты на стенде и обнаружил, что производительность вызова функции составляет порядка 10% до 70% в зависимости от характера моего стендового теста. Я соглашусь, что это важно. Но до того, как этот блок блокировки попал, произошел вызов базы данных и конечной точки HTTP. Прежде чем $products был установлен в $customer, был сложный вид, который произошел с массивом $products. В конце концов эта оптимизация оправдывает затраты на упрощение чтения и обслуживания кода? Или, хотя эти примеры являются упрощениями, кто-нибудь может найти 2-й пример так же легко или проще для чтения чем первый (я являюсь wiener)?

Может ли кто-нибудь привести какие-либо хорошие статьи или исследования об этом?

Изменить:

Пример тестового теста:

<?php
class Foo {
        private $list;
        public function setList($list) {
                $this->list = $list;
        }
}

$foo1 = new Foo();

for($i = 0; $i < 1000000; $i++) {
        $a = array();
        if (!empty($a))
                $foo1->setList($a);
}
?>

Запустите этот файл с помощью команды time. На одной конкретной машине она занимает в среднем 0,60 секунды после нескольких прогонов. Комментирование if (!empty($a)) заставляет его выполнять в среднем 3,00 секунды для запуска.

Разъяснение. Это примеры. Первый пример демонстрирует ужасную обработку исключений и возможное нарушение DRY за счет простого примера, не относящегося к домену.

Ответ 1

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

Возможно, вы захотите рассмотреть другие PHP-реализации.

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

Ответ 2

Накладные расходы на функцию PHP составляют ровно 15.5355%.

:) Просто помешивая горшок.

Серьезно, вот пара отличных ссылок на эту тему:

Возможно ли иметь слишком много функций в приложении PHP?

функции против повторяющегося кода

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

Как отмечалось в другом месте, важным соображением при принятии решения о том, следует ли вызывать функцию или поместить код "in-line", является то, сколько раз функция будет вызываться из определенного блока кода. Чем больше раз будет вызываться функция, тем больше стоит считать выполнение работы в строке.

Результаты (раз в секундах)

Метод функции вызова | Метод In-Line | Разница | Процент разных

1,000 итераций (4 пробега)

0.0039088726043701 | 0.0031478404998779 | 0.00076103210449219 | 19,4694

0.0038208961486816 | 0.0025999546051025 | 0.0012209415435791 | 31,9543

0.0030159950256348 | 0.0029480457305908 | 6.7949295043945E-5 | 2,2530

0.0031449794769287 | 0.0031390190124512 | 5.9604644775391E-6 | 0,1895

1,000,000 итераций (4 прогона)

3.1843111515045 | 2.6896121501923 | 0,49469900131226 | 15,5355

3.131945848465 | 2.7114839553833 | 0.42046189308167 | 13,4249

3.0256152153015 | 2.7648048400879 | 0,26081037521362 | 8,6201

3.1251409053802 | 2.7397727966309 | 0.38536810874939 | 12,3312

function postgres_friendly_number($dirtyString) {

    $cleanString = str_ireplace("(", "-", $dirtyString);
    $badChars = array("$", ",", ")");
    $cleanString = str_ireplace($badChars, "", $cleanString);

    return $cleanString;

}


//main
$badNumberString = '-$590,832.61';

$iterations = 1000000;

$startTime = microtime(true);
for ($i = 1; $i <= $iterations; $i++) {
    $goodNumberString = postgres_friendly_number($badNumberString);
}
$endTime = microtime(true);
$firstTime = ($endTime - $startTime); 

$startTime = microtime(true);
for ($i = 1; $i <= $iterations; $i++) {
    $goodNumberString = str_ireplace("(", "-", $badNumberString);
    $badChars = array("$", ",", ")");
    $goodNumberString = str_ireplace($badChars, "", $goodNumberString);
}
$endTime = microtime(true); 
$secondTime = ($endTime - $startTime); 

$timeDifference = $firstTime - $secondTime;
$percentDifference = (( $timeDifference / $firstTime ) * 100);

Ответ 3

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

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

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

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

Исследования по объектно-ориентированному PHP выполнялись на основе использования геттеров/сеттеров. Удаление всех геттеров/сеттеров сокращает время выполнения примерно на 50%. И это просто связано с накладными вызовами функций.

Ответ 4

Я вас путаю терминологию. Как правило, вызов вызова функции означает накладные расходы, связанные с вызовом возврата из функции. Вместо обработки inline. Это не общая стоимость вызова функции. Его просто затраты на подготовку аргументов и возврат значений и выполнение ветки.

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

Итак, в нижней строке: вызов функции один раз и сохранение и повторное использование результата! Не вызывайте одну и ту же функцию несколько раз с теми же аргументами. (без уважительной причины)