Joy: What is going on?
Sadness: We’re abstracting! There are four stages. This is the first. Non-objective fragmentation!
Bing Bong: Alright, do not panic. What is important is that we all stay together. [suddenly his abstract arm falls off]
Joy: Oh! [Sadness and Joy start falling apart too]
Sadness: We’re in the second stage. We’re deconstructing! [as Bing Bong falls to pieces]
Bing Bong: I can’t feel my legs! [picks one leg up] Oh, there they are.
© мультфильм Inside Out


Все любят писать красивый код. Чтобы абстракции, лямбды, SOLID, DRY, DI и т.д. и т.п. В этой статье я хочу исследовать, во сколько обходится это всё с точки зрения производительности и почему.

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

Дисклеймер: Эта статья ни в коем случае не должна рассматриваться как призыв писать плохой код. Лучше всего, если вы заранее настроитесь сказать после прочтение «Прикольно! Теперь я знаю, как оно там внутри. Но, конечно же, не буду это использовать». :)

Задача:

  1. Дан текстовый файл.
  2. Разобьём его по строкам.
  3. Обрежем пробелы слева и справа
  4. Отбросим все пустые строки.
  5. Все не единичные пробелы заменим единичными («A B C»->«A B C»).
  6. Строки, в которых более 10 слов, по словам перевернём задом наперёд («An Bn Cn»->«Cn Bn An»).
  7. Посчитаем, сколько раз встречается каждая строка.
  8. Выведем все строки, которые встречаются более N раз.

В качестве входного файла по традиции возьмём php-src/Zend/zend_vm_execute.h на ~70 тысяч строк.

В качестве среды исполнения возьмём PHP 7.3.6.
На скомпилированные опкоды посмотрим тут https://3v4l.org.

Замеры будем производить следующим образом:

// объявление функций и классов
$start = microtime(true);

ob_start();
for ($i = 0; $i < 10; $i++) {
    // тут наш код
}
ob_clean();

echo "Time: " . (microtime(true) - $start) / 10;

Подход первый, наивный


Напишем простой императивный код:

$array = explode("\n", file_get_contents('/Users/rjhdby/CLionProjects/php-src/Zend/zend_vm_execute.h'));
$cache = [];

foreach ($array as $row) {
    if (empty($row)) continue;
    $words = preg_split("/\s+/", trim($row));
    if (count($words) > 10) {
        $words = array_reverse($words);
    }
    $row = implode(" ", $words);
    if (isset($cache[$row])) {
        $cache[$row]++;
    } else {
        $cache[$row] = 1;
    }
}

foreach ($cache as $key => $value) {
    if ($value > 1000) {
        echo "$key : $value" . PHP_EOL;
    }
}

Время выполнения ~0.148с.

Тут всё просто и разговаривать особо не о чем.

Подход второй, процедурный


Отрефакторим наш код и вынесем элементарные действия в функции.
Постараемся придерживаться принципа единственной ответственности.

Портянка под спойлером.
function getContentFromFile(string $fileName): array
{
    return explode("\n", file_get_contents($fileName));
}

function reverseWordsIfNeeded(array &$input)
{
    if (count($input) > 10) {
        $input = array_reverse($input);
    }
}

function prepareString(string $input): string
{
    $words = preg_split("/\s+/", trim($input));
    reverseWordsIfNeeded($words);
    return implode(" ", $words);
}

function printIfSuitable(array $input, int $threshold)
{
    foreach ($input as $key => $value) {
        if ($value > $threshold) {
            echo "$key : $value" . PHP_EOL;
        }
    }
}

function addToCache(array &$cache, string $line)
{
    if (isset($cache[$line])) {
        $cache[$line]++;
    } else {
        $cache[$line] = 1;
    }
}

function processContent(array $input): array
{
    $cache = [];
    foreach ($input as $row) {
        if (empty($row)) continue;
        addToCache($cache, prepareString($row));
    }
    return $cache;
}

printIfSuitable(
    processContent(
        getContentFromFile('/Users/rjhdby/CLionProjects/php-src/Zend/zend_vm_execute.h')
    ),
    1000
);


Время выполнения ~0.275с… WTF!? Разница почти в 2 раза!

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

Код:

$a = 1;
$b = 2;
$c = $a + $b;

Компилируется в:

line     #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
   2     0  E >   ASSIGN                                                   !0, 1
   3     1        ASSIGN                                                   !1, 2
   4     2        ADD                                              ~5      !0, !1
         3        ASSIGN                                                   !2, ~5

Давайте вынесем сложение в функцию:

function sum($a, $b){
    return $a + $b;
}

$a = 1;
$b = 1;

$c = sum($a, $b);

Такой код скомпилируется в два набора опкодов: один для корневого пространства имён, а второй для функции.

Корень:

line     #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
   2     0  E >   ASSIGN                                                   !0, 1
   3     1        ASSIGN                                                   !1, 1
   5     2        NOP
   9     3        INIT_FCALL                                               'sum'
         4        SEND_VAR                                                 !0
         5        SEND_VAR                                                 !1
         6        DO_FCALL                                      0  $5
         7        ASSIGN                                                   !2, $5

Функция:

line     #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
   5     0  E >   RECV                                             !0
         1        RECV                                             !1
   6     2        ADD                                              ~2      !0, !1
         3      > RETURN                                                   ~2

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

А если копнуть немного глубже, то тут у нас ещё и переключение контекста выполнения.

Грубая прикидка по нашему отрефакторенному коду даёт такие цифры (помним про 70 000 итераций).
Количество «дополнительных» исполненных опкодов: ~17 000 000.
Количество переключений контекста: ~280 000.

Подход третий, классический


Особо не мудрствуя, обернём все эти функции классом.

Простыня под спойлером
class ProcessFile
{
    private $content;
    private $cache = [];

    function __construct(string $fileName) {
        $this->content = explode("\n", file_get_contents($fileName));
    }

    private function reverseWordsIfNeeded(array &$input) {
        if (count($input) > 10) {
            $input = array_reverse($input);
        }
    }

    private function prepareString(string $input): string {
        $words = preg_split("/\s+/", trim($input));
        $this->reverseWordsIfNeeded($words);
        return implode(" ", $words);
    }

    function printIfSuitable(int $threshold) {
        foreach ($this->cache as $key => $value) {
            if ($value > $threshold) {
                echo "$key : $value" . PHP_EOL;
            }
        }
    }

    private function addToCache(string $line) {
        if (isset($this->cache[$line])) {
            $this->cache[$line]++;
        } else {
            $this->cache[$line] = 1;
        }
    }

    function processContent() {
        foreach ($this->content as $row) {
            if (empty($row)) continue;
            $this->addToCache( $this->prepareString($row));
        }
    }
}

$processFile = new ProcessFile('/Users/rjhdby/CLionProjects/php-src/Zend/zend_vm_execute.h');
$processFile->processContent();
$processFile->printIfSuitable(1000);


Время выполнения: 0.297. Стало хуже. Не сильно, но заметно. Неужели создание объекта (10 раз в нашем случае) такое затратное? Нууу… Не только в этом дело.

Давайте посмотрим, как виртуальная машина работает с классом.

class Adder{
    private $a;
    private $b;

    function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }
    function sum(){
        return $this->a + $this->b;
    }
}

$a = 1;
$b = 1;
$adder = new Adder($a, $b);
$c = $adder->sum();

Тут будет три набора опкодов, что логично: корень и два метода.

Корень:

line     #* E I O op                 fetch          ext  return  operands
---------------------------------------------------------------------------
   2     0  E >   NOP
  16     1        ASSIGN                                         !0, 1
  17     2        ASSIGN                                         !1, 1
  18     3        NEW                                    $7      :15
         4        SEND_VAR_EX                                    !0
         5        SEND_VAR_EX                                    !1
         6        DO_FCALL                            0
         7        ASSIGN                                         !2, $7
  19     8        INIT_METHOD_CALL                               !2, 'sum'
         9        DO_FCALL                            0  $10
        10        ASSIGN                                         !3, $10

Конструктор:

line     #* E I O op                 fetch          ext  return  operands
---------------------------------------------------------------------------
   6     0  E >   RECV                                   !0
         1        RECV                                   !1
   7     2        ASSIGN_OBJ                                     'a'
         3        OP_DATA                                        !0
   8     4        ASSIGN_OBJ                                     'b'
         5        OP_DATA                                        !1
   9     6      > RETURN                                         null

Метод sum:

line     #* E I O op                 fetch          ext  return  operands
---------------------------------------------------------------------------
  11     0  E >   FETCH_OBJ_R                            ~0      'a'
         1        FETCH_OBJ_R                            ~1      'b'
         2        ADD                                    ~2      ~0, ~1
         3      > RETURN                                         ~2

Ключевое слово new фактически преобразуется в вызов функции (строки 3-6).
Она создаёт экземпляр класса и вызывает на нем конструктор с переданными параметрами.

В коде же методов нам будет интересна работа с полями класса. Обратите внимание, что если с обычными переменными при присвоении используется один простой опкод ASSIGN, то для полей класса всё несколько иначе.

Присвоение — 2 опкода

   7     2        ASSIGN_OBJ                                     'a'
         3        OP_DATA                                        !0

Чтение — 1 опкод

         1        FETCH_OBJ_R                            ~1      'b'

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

Опкод Количество строк обработчика (С-код)
ASSIGN_OBJ 149
OP_DATA 30
FETCH_OBJ_R 112
ASSIGN 26

Понятно, что такое сравнение очень далеко от корректного, но всё же даёт некоторое представление. Чуть дальше произведу замеры.

А теперь посмотрим, насколько затратно создание экземпляра объекта. Давайте замерим на одном миллионе итераций:

class ValueObject{
    private $a;
    function __construct($a) {
        $this->a = $a;
    }
}

$start = microtime(true);

for($i = 0; $i < 1000000; $i++){
    // $a = $i;
    // $a = new ValueObject($i);
}

echo "Time: " . (microtime(true) - $start);

Присвоение переменной: 0.092.
Инстанциация объекта: 0.889.

Как-то вот так. Не совсем бесплатно, особенно если много раз.

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

class ValueObject{
    private $b;

    function try($a) {
        // Обмен через свойство
        // $this->b = $a;
        // $c = $this->b;

        // Обмен через присвоение
        // $b = $a;
        // $c = $b;

        return $c;
    }
}

$a = new ValueObject();

$start = microtime(true);

for($i = 0; $i < 1000000; $i++){
    $b = $a->try($i);
}

echo "Simple. Time: " . (microtime(true) - $start);


Обмен через присвоение: 0.830.
Обмен через свойство: 0.862.

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

Банальные выводы


  1. В следующий раз, когда вы захотите инстанциировать миллион объектов, задумайтесь, так ли оно вам необходимо. Может, просто массив, а?
  2. Писать спагетти-код ради экономии одной миллисекунды — ну такое. Выхлоп копеечный, а коллеги потом и побить могут.
  3. А вот ради экономии 500 миллисекунд, может быть, иногда и имеет смысл. Главное, не перегибать палку и помнить, что эти 500 миллисекунд, скорее всего, будут сэкономлены только небольшим участком очень горячего кода, и не превращать весь проект в юдоль скорби.

P.S. Про лямбды в следующий раз. Там интересно. :)

Комментарии (46)


  1. Eldhenn
    23.09.2019 10:56
    +1

    «Экономия» полусекунды спагетти-кодом обернётся проблемами при поддержке и расширении. Если, конечно вы не пишете код «write once and better never run».


    1. gecube
      23.09.2019 11:06
      -1

      Не любой код в будущем будет доработан. Вполне возможно, что когда понадобится код доработать — выйдет очередной новомодный фреймворк и задача будет переписать все с нуля.
      Естественно, что your mileage may vary. И писать говнокод не стоит.


      1. smarthomeblog
        23.09.2019 11:52

        ИМХО код без использования классов и функций нельзя прямо так назвать говнокодом. Все зависит от контекста :) Писать большой проект одним скриптом явно не стоит. Равно как и бездумно пихать везде ООП тоже.


        1. gecube
          23.09.2019 11:54

          Я не говорил, что любой скрипт — говнокод.
          И, да, полностью согласен, что нужно инструменты применять к месту.


    1. rjhdby Автор
      23.09.2019 11:11
      +1

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


    1. edogs
      23.09.2019 12:43

      «Экономия» полусекунды спагетти-кодом обернётся проблемами при поддержке и расширении. Если, конечно вы не пишете код «write once and better never run».
      Есть еще вариант. Автогенерация/авторазворачивание кода в более простой. Упрощенно — когда 2 вариант из статьи автоматом конвертится в первый, получается нечто вроде макросов в сях. Убивается два зайца (нет говнокода и нет потерь скорости), но есть нюансы конечно.


    1. AmdY
      23.09.2019 12:47

      Поддержка и расширение — это очень абстрактные вещи и большинство статистики собиралось в 80-90 годы, с тех пор появились мощные IDE, выросли возможности языков и появились хорошие по вопросу библиотеки, решающие кучу инфраструктурных проблем.
      Вот у меня был проект сети магазинов, спагети код на функциях, который поддерживался и развивался силами 1.5 разработчиков. Затем его переписали на новый модный фреймворк, с эвентами, команд басами, попытками в ДДД, естественно всё реализовали как микросервисы. На момент моего ухода, команда уже перевалила за 20 человек.


      1. trueMoRoZ
        23.09.2019 14:47

        «перевалила за 20 человек» — это хорошо или плохо? количество разработчиков изменилось только из-за переезда на модный фреймворк или таки были ещё какие-то факторы?


        1. AmdY
          23.09.2019 15:25

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


  1. Akdmeh
    23.09.2019 11:53

    Могу посоветовать писать с использованием ООП (который проще поддерживать команде и тестировать при грамотном подходе), но затем проверять «тяжелый код» с помощью xhprof.
    Как-то я оптимизировал запросы к базе данных (так как считал, что это замедляет работу тяжелого скрипта), но не получал значительного ускорения работы скрипта. Оказалось, что самое большое замедление давало именно создание объекта ActiveRecord и затем миллионы вызовов __get к виртуальным полям. Да, пришлось переписать конкретно код с объектов на массивы (с потерей возможности работы с сущностями как объектами со своими свойствами), т.е., грубо говоря, вместо $obj->getParentName() писать Something::getParentName($obj), но за счет этого получил троекратное ускорение работы алгоритма.
    Итого, все эти предположения — хороши, но всегда нужно смотреть фактическое употребление памяти и процессорного времени с помощью отладчиков в конкретных алгоритмах и получать приемлемую скорость.


    1. michael_vostrikov
      23.09.2019 19:18

      А почему не $obj->parentName? Публичные поля в объектах все-таки удобнее static-вызовов. Вы же получается все равно заменили функционал ActiveRecord на свой, можно было поля через $this->$name инициализировать по массиву из БД.


  1. homm
    23.09.2019 13:57

    В качестве входного файла по традиции возьмём php-src/Zend/zend_vm_execute.h на ~70 тысяч строк.

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


    1. rjhdby Автор
      23.09.2019 14:16
      +1

  1. namikiri
    23.09.2019 15:00

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


  1. youROCK
    23.09.2019 20:53

    Скажу честно, первый вариант кода ещё и легче читается (по крайней мере для меня) :). Когда логики не очень много, очень легко читать, когда она вся собрана в одном месте. У этого подхода есть много недостатков, в том числе сложность тестирования такого кода, но это можно частично преодолеть тем, чтобы вынести в функции (скорее классы) те участки, которые хочется уметь подменять.

    Другой вопрос, когда весь проект написан в таком стиле — в этом случае поддержка превращается в ад. Поэтому, ИМХО, совет должен быть немного в другом — не стоит переусложнять код и вносить туда кучу абстракций перед тем, как код действительно стал хотя бы чуточку сложным. Особенно если вы пишете на PHP


    1. rjhdby Автор
      23.09.2019 21:56
      +1

      Другой вопрос, когда весь проект написан в таком стиле

      Как то раз один уважаемый мною писатель выложил на гитхаб движёк своего стендалона. Взыграло во мне чувство бессмысленного и беспощадного альтруизма помочь в его развитии. Форкнул, загрузил, открыл, и волосы у меня начали шевелиться в неожиданных местах — такой адище, что ни в сказке сказать, ни пером описать. Но, как говорится, взялся за гуж… В общем переписал скрипт работы с базой в лучших практиках. Не так, чтоб прям фанатично, но хотя бы по человечески. Дня два ушло только на то, чтобы разобраться, как оно вообще работает и какие граничные условия какими костылями подперты. В ответку прилетело "Это что за фигня? Классы какие-то! Нафиг, нафиг — моему скрипту уже дцать лет, я там каждый костыль знаю и мне так удобно".


      Не знаю, к чему об этом рассказал. Навеяло. :)


      1. dmitryrublev
        23.09.2019 23:12
        +1

        Знаю такую CMS, где коду лет 10-15 уже, по ощущениям (и редким комментариям) всё писано фрилансерами всего мира. Раз в пару лет заглядываю на форум разработчика, и вижу одинокие посты:
        — «А давайте нормально перепишем?..»
        — «Иди в *опу, пипл лицензии всёравно покупает всех всё устраивает, самый умный штоле?»


  1. Hett
    23.09.2019 21:59

    Что там на счёт opcace, вашем тесте он включен?


    1. rjhdby Автор
      23.09.2019 22:02

      Opcache имели в виду? А зачем он там? Время компиляции то нам тут не так чтоб интересно — оно практически никак не сыграет ни для одного из вариантов.


      1. Hett
        24.09.2019 14:35

        Opcache конечно, отправил сообщение и только потом увидел ошибку.
        А PHP 8 с JIT уже доступен для тестов? Интересно как там дела обстоят.



        1. rjhdby Автор
          25.09.2019 19:52

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


      1. Hett
        24.09.2019 14:49

        Я думал, что он какую-то оптимизацию опкода проводит, но видимо нет. Проверил ваши примеры, разницы во времени с включенным/выключенным opcache нет.


  1. FanAs
    23.09.2019 22:48

    Спасибо за статью. Действительно, в «горячих» фрагментах можно и иногда нужно экономить миллисекунды — в наших проектах это позволяло выиграть до 15% производительности кэш, однако возникает проблема — как оформить императивный код таким образом, чтобы он был понятен при обслуживании? Возможно у вас есть какие-то мысли?


    1. rjhdby Автор
      24.09.2019 00:11

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


  1. bolk
    23.09.2019 23:04

    explode("\n", file_get_contents($fileName));

    это просто

    file($fileName, FILE_IGNORE_NEW_LINES);

    А тут вы отбрасываете не только пустую строку, но и строку «0»:

    if (empty($row)) continue;


    1. rjhdby Автор
      24.09.2019 00:09

      Не с казал бы, что это принципиально в рамках заданной темы


      1. zorn_v
        24.09.2019 03:36

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


        1. rjhdby Автор
          24.09.2019 08:29

          Вы напомнили мне моего старого преподавателя по начертательной геометрии. Когда у него не было претензий к содержательной части работы — он начинал придираться к тому, что буква А в легенде не по ГОСТу.


    1. zorn_v
      24.09.2019 03:28

      А `file($fileName, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES)` еще до кучи и пустые строки отбросит ;)


      1. NickyX3
        24.09.2019 07:37

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

        public static function readTheRealyBigFile($path) {
        	$handle = fopen($path, "r");	
        	while(!feof($handle)) {
        		yield trim(fgets($handle));
        	}	
        	fclose($handle);
        }
        


        1. zorn_v
          24.09.2019 08:29

          Все так, но не понятно при чем тут php7
          Генераторы появились в 5.5


          1. NickyX3
            24.09.2019 08:32

            Ключевое слово времена. А у нас 2019 год кончается и 7 актуальнее, да ведь?


            1. zorn_v
              24.09.2019 08:36

              Т.е. во «времена» пхп 5.5 память была дешевле что ли? Или может не было файлов в тысячи строк? Не понял посыл.


              1. VolCh
                24.09.2019 08:39

                "Во времена" PHP 5 далеко не везде он был, а во времена PHP7 можно рассчитывать, что 5 уж точно будет :(


                1. NickyX3
                  24.09.2019 08:51

                  Кстати да. Мы вот с 5.3 переехали на 5.6 и далее на 7, а 5.5 вообще пропустили


              1. NickyX3
                24.09.2019 08:50

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


                1. zorn_v
                  24.09.2019 09:26

                  > Во-вторых речь то о том, что именно генератор поможет

                  Так и я об этом же. Не пхп7 же как таковой )


                  1. NickyX3
                    24.09.2019 10:40

                    Ну так времена php7 это одно утверждение.
                    А генераторы это второе.
                    Малосвязанные, ага


                1. rjhdby Автор
                  24.09.2019 10:13

                  Статья у нас про экономию и оптимизацию же

                  Статья у меня не про "как", а про "почему". Да и, если на то пошло, не про оптимизацию совсем.


                  Раз зашла речь про преимущества потоковой обработки файлов, то нельзя не отметить, что хоть она и экономит на памяти, но вот что касается стоимости в перфомансе — там все довольно печально.


                  1. NickyX3
                    24.09.2019 10:41

                    экономит на памяти, но вот что касается стоимости в перфомансе

                    Есть расклад по опкодам и цыферки? Было бы интересно


                    1. rjhdby Автор
                      24.09.2019 11:01

                      По циферкам, при замене explode("\n", file_get_contents(... на генератор, на моем железе, получилось в два раза медленнее. Тут дело не столько в опкодах, сколько в том, что bulk operation, возможно за исключением некоторых случаев, которые с ходу не придумываются, всегда быстрее, чем one by one. По природе своей.


                      UPD Потоковая обработка хороша в случаях, когда существует фильтрация входных данных и/или условие остановки обработки.


  1. Sabubu
    24.09.2019 03:51

    Сравнивал время загрузки записей из БД в массив через PDO и в объект через Доктрину. Через Доктрину в 10 раз медленнее. Использую Доктрину за исключением случаев, когда надо обработать большое число записей и получить на выходе небольшой объем информации (вроде пары чисел).


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


  1. iproger
    24.09.2019 04:54
    -1

    Ваши слова да разработчикам Magento 2 в уши. Я когда первый раз столкнулся с ней не сразу понял с чем имею дело, потом понемногу приходило понимание масштаба «абстракции».
    Есть предположение что в ней классов и строчек кода больше чем в каком-нибудь ядре Линукса.
    Без кэша загрузка страницы может занимать до 15 секунд.


  1. dzsysop
    24.09.2019 06:54

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


  1. VolCh
    24.09.2019 08:26
    +2

    Одна из причин использования ООП не по делу в PHP — отсутствие автозагрузуи для функций. Это так, заметка.