PHP с использованием strpos() перед str_replace()

У меня есть цикл, который принимает большое количество текста на каждой итерации и заменяет определенный заполнитель («токен») другим содержимым, например:

$string = $pageContent;
foreach($categories as $row) {
    $images = $mdlGallery->getByCategory($row['id']);
    if (!empty($images)) {
        $plug = Plugin::get('includes/gallery', array('rows' => $images));
        $string = str_replace($row['token'], $plug, $string);
    }
}

Класс Plugin и его метод get() просто берут нужный файл из определенного каталога и выводят буфер в виде строки.

Может быть большое количество категорий, поэтому мне интересно, не лучше ли сначала проверить входную строку на наличие определенного «токена», прежде чем заполнять все изображения из данной категории с помощью функции strpos() следующим образом:

foreach($categories as $row) {
    if (strpos($string, $row['token']) !== false) {
        $images = $mdlGallery->getByCategory($row['id']);
        if (!empty($images)) {
            $plug = Plugin::get('includes/gallery', array('rows' => $images));
            $string = str_replace($row['token'], $plug, $string);
        }
    }
}

Меня беспокоит производительность - поможет ли это? - считать, что $string может содержать большое количество символов (тип поля TEXT в MySQL)?


person Spencer Mark    schedule 17.07.2012    source источник
comment
Подключаются ли какие-либо методы внутри foreach к БД? Или читать целые файлы в память (file_get_contents)?   -  person Mihai Stancu    schedule 17.07.2012
comment
Как и в случае с тем, что сказал @MihaiStancu. Именно извлечение этих данных будет стоить производительности. Является ли $mdlGallery->getByCategory($row['id']); вызовом базы данных?   -  person Marc Costello    schedule 17.07.2012
comment
да - метод getByCategory() извлекает записи из базы данных, а метод Plugin::get() включает файл в выходной буфер и возвращает результат в виде строки.   -  person Spencer Mark    schedule 17.07.2012
comment
Ну... не одну, а две дорогостоящие операции, неудивительно, что это медленно. Вы должны (по крайней мере) извлекать данные из БД с помощью одного вызова, используя несколько последовательностей подключения->запроса->чтения-закрытия в вызовах БД, не имеет смысла для массовой (иш) операции.   -  person Mihai Stancu    schedule 17.07.2012
comment
Соединение не закрывается после каждого оператора - я использую постоянное соединение (PDO::ATTR_PERSISTENT). Думаю, в этом случае имеет смысл сначала использовать strpos().   -  person Spencer Mark    schedule 17.07.2012
comment
Когда я сказал «закрыть», я имел в виду процесс связи, необходимый для отправки одного запроса по потоку в БД. db_connect -> query_connect -> execute_query -> read_results -> release_results -> close_query_connection -> db_close каждый из этих псевдокодированных шагов происходит за кулисами, а некоторые из них абстрагируются вашими функциями SQL, вы не контролируете процесс связи для каждого запроса, вы просто инициируете его и получаете результат Ресурсы.   -  person Mihai Stancu    schedule 17.07.2012


Ответы (3)


Чтобы решить вашу проблему

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

Метод getByCategory должен повлечь за собой большие потери производительности, поскольку он подразумевает множество последовательностей подключения->запрос->чтение->закрыть связь с базой данных, и каждая из них подразумевает передачу большого объема данных (поля TEXT, которые вы упомянули).

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

Ваша текущая проблема заключается не в простом просмотре кода, а в подходе. Вы использовали типичную технику для небольших наборов данных в качестве подхода к обработке больших наборов данных. Понятие «обернуть foreach поверх простого скрипта» работает, если у вас есть средние наборы данных и вы не чувствуете снижения производительности, если вам не нужен отдельный подход для обработки большого набора данных.

Чтобы ответить на ваш вопрос

Использование strpos означает один раз просмотреть весь стог сена, чтобы проверить, есть ли в нем иголка, а затем снова просмотреть его, чтобы выполнить замену на str_replace.

Если в стоге сена нет иголки, strpos === str_replace (с точки зрения вычислительной сложности), потому что они оба должны пройти всю строку до конца, чтобы убедиться, что там нет иголок.

Использование обеих функций увеличивает вычислительную сложность на 100 % для любого стога сена, не содержащего иголки, и увеличивает вычислительную сложность от 1 % до 100 % для любого стога сена, в котором есть иголка, потому что strpos вернется сразу после первой иголки. найдено, которое можно найти в начале строки, в середине или в конце.

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

person Mihai Stancu    schedule 17.07.2012
comment
Чтобы добавить.. Вам нужно либо запросить все данные, которые могут понадобиться перед циклом, ИЛИ Если в таблице слишком много данных, удержание в памяти, которое вам нужно выяснить именно то, что вам нужно сначала получить, а затем использовать SQL для получения ваших данных - person Marc Costello; 17.07.2012
comment
Спасибо, Михай. getByCategory() извлекает записи изображений, назначенные текущей итерируемой категории — большой текст представляет собой строку $, которая может содержать заполнители, представляющие категорию. Мы перебираем каждую категорию, у которой есть индекс токена, который можно найти в $string. Если он найден, я вызываю метод getByCategory() для получения всех изображений, назначенных этой категории — очевидно, если я использую функцию strops() для первой проверки появления токена — иначе он все равно будет выполнен. - person Spencer Mark; 17.07.2012
comment
Я обновил свой ответ, чтобы объяснить, почему strpos в вашем конкретном случае бесполезен, наоборот, это добавит сложности. - person Mihai Stancu; 17.07.2012

Спасибо, Михай, однако это имеет большой смысл — в этом конкретном сценарии, даже если я сначала получу все записи из базы данных — то есть все изображения со связанными категориями — будет редко, чтобы $string содержала более только один или два «токена» - это означает, что использование strpos() может фактически сэкономить время, если есть много категорий («токенов») для сравнения.

Представьте, что мы не вызываем getByCategory в каждой итерации, потому что мы уже храним все возможные записи в ранее сгенерированном массиве — нам все еще нужно пройти буферизацию вывода внутри метода Plugin::get() и str_replace. () - это означает, что если у нас есть, скажем, 20 категорий, это произойдет 20 раз без обязательного включения «токена» в строку $.

Таким образом, ваше предложение сработает, если предполагается, что в строке $ будет найдено большое количество «токенов» по ​​сравнению с количеством категорий, которые мы просматриваем, но для небольшого количества «токенов» я думаю, что strpos() будет по-прежнему полезно, так как это будет единственное выполнение для каждой категории, а не два следующих, когда strpos() возвращает true - в этом случае это небольшая цена, которую нужно заплатить в форме strpos() по сравнению с ob и str_replace вместе каждый раз в петле - тебе не кажется?

Я очень ценю ваше объяснение, хотя.

person Spencer Mark    schedule 17.07.2012
comment
Представьте себе следующие две операции strpos('c', 'a b d e f g h i') и str_replace('c', 'x', 'a b d e f g h i'), обе они делают одно и то же, перебирают все 15 символов в строке в поисках (и не находят букву «с»). Таким образом, оба внутренне используют цикл for, который повторяется 15 раз и сравнивает равенство. - person Mihai Stancu; 17.07.2012
comment
Теперь представьте следующие две операции над другой строкой strpos('c', 'abcdefghc i') и str_replace('c', 'x', 'abcdefghc i'), strpos запускается и внутренне использует for, который повторяется 5 раз, пока он находит первое вхождение буквы «c», затем позволяет запустить str_replace, который внутренне использует for, который повторяется 15 раз, пока ему не удастся заменить два вхождения буквы «c». - person Mihai Stancu; 17.07.2012
comment
В первом примере вам нужно пройти через строку один раз с strpos, что означает 15 итераций в цикле for. Но когда вы обнаружите, что иглы нет, вам не нужно звонить str_replace. Если бы вместо этого вы запустили str_replace, у вас был бы точно такой же результат, он бы прогнал строку в общей сложности 15 итераций и пришел к выводу, что заменять нечего. - person Mihai Stancu; 17.07.2012
comment
Во втором примере вы запускаете strpos stop, после чего просматриваете строку и находите вхождение в позиции 5 (так что вы использовали 5 итераций до сих пор) и возвращаетесь, затем вы запускаете str_replace, который тоже начинается с начала и проходит через строку за 15 итераций для выполнения замен, всего 5+15 итераций. Если бы вместо этого вы запустили str_replace, у вас было бы только 15 итераций, необходимых для замены. - person Mihai Stancu; 17.07.2012
comment
Так как в первом сценарии 15 шагов == 15 шагов нет ни плюсов, ни минусов в использовании того и другого, во втором сценарии 15 шагов ‹ 20 шагов, поэтому strpos перед str_replace добавляет 5 шагов к сложности. Если бы первое вхождение было в самом конце строки, strpos добавил бы 15 шагов сложности (буквально удвоил бы ее). Поэтому я считаю, что вы не должны использовать strpos, если только вам не нужно делать другие вещи вместе с str_replace только тогда, когда строка содержит токены. Если вы используете strpos для проверки наличия токена, str_replace уже выполняет проверки. - person Mihai Stancu; 17.07.2012
comment
А как насчет буферизации вывода, которая вызывается с каждой итерацией без вызова strpos(), что также увеличивает производительность, не так ли? - person Spencer Mark; 18.07.2012
comment
Неважно, как вы получаете содержимое внешних файлов. важно то, что вы его получаете. Вы могли бы использовать file_get_contents вместо ob_start -> include ob_get_contents -> ob_end, операция, которую вы выполняете, перебирает всю строку, чтобы получить значение и поместить его в переменную. strpos не останавливает буфер в середине потока, поток считывается полностью, а затем анализируется. - person Mihai Stancu; 18.07.2012
comment
До сих пор мы нашли 3 операции, которые полностью читали эту строку. disk-read -> strpos -> str_replace. Единственный способ избежать (частично) чтения с диска - использовать fread в кусках (1 байт == буква) и сравнивать каждую букву, и когда вы обнаружите расхождение, вы прекращаете чтение с диска и пропускаете str_replace в противном случае, но это было бы полезно, если данные из disk-read будут большими, и их полное чтение приведет к потере производительности, поскольку я понял ваш сценарий, файлы, которые вы включаете, содержат только сам токен, никаких других данных. - person Mihai Stancu; 18.07.2012

Я думаю, что лучше тестировать вещи самостоятельно, если вы ищете оптимизацию (особенно для микрооптимизации). Любая реализация имеет более одного варианта (обычно), поэтому лучше сравнить используемый вариант. В соответствии с этим вы можете увидеть результаты тестов здесь: с помощью strpos: http://3v4l.org/pb4hY#v533 без strpos: http://3v4l.org/v35gT

person Repky    schedule 28.04.2014