Номер телефона

Последнее обновление:

Язык РНР: каким способом будет быстрее получить требуемые сообщения комментариев из файла html?

В этой статье рассмотрим, насколько эффективными по быстродействию являются регулярные выражения и функции класса domDocument. Допустим, у нас есть файл с сообщениями пользователей на сайте. И мы хотим сделать из него выборку только тех сообщений, которые оставил какой-нибудь один пользователь, например, имеющий ник USER_1.

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

Например, у нас есть файл с именем comments.html, содержащий примерно следующее:

 <div>

<div id="comm_0" class="qwer" data-time="1740776850">
<span id="user_0" class="tyuio">USER_0</span><div id="mess_0">укенгшщзхъфывапролд</div>
</div>

<div id="comm_1" class="qwer" data-time="1740776850">
<span id="user_1" class="tyuio">USER_1</span><div id="mess_1">йцукенгшщзхъ</div>
</div>

<div id="comm_2" class="qwer" data-time="1740776850">
<span id="user_2" class="tyuio">USER_2</span><div id="mess_2">зхъфывапролджэяч</div>
</div>

<div id="comm_3" class="qwer" data-time="1740776850">
<span id="user_3" class="tyuio">USER_3</span><div id="mess_3">укенгшщзхъфы</div>
</div>

<div id="comm_4" class="qwer" data-time="1740776850">
<span id="user_4" class="tyuio">USER_4</span><div id="mess_4">кенгшщзхъфывапро</div>
</div>

<div id="comm_5" class="qwer" data-time="1740776850">
<span id="user_5" class="tyuio">USER_5</span><div id="mess_5">зхъфывапролджэя</div>
</div>



</div>

Т.е. этот файл представляет собой совокупность сообщений пользователей, содержащий их ники и тексты сообщений. Также в одном из блоков каждого сообщения есть атрибут data-time, который задает метку времени UNIX. Если необходимо, ее можно прочитать средствами javascript и разместить в том или ином месте сообщения в требуемом формате (например, в виде Дата – Время).

Создание тестового файла с сообщениями

Чтобы создать подобный файл, можно использовать следующий код на языке PHP (назовем его create_file.php):

 <?php // Создаем файл с "сообщениями"

mb_internal_encoding('utf-8');
header('Content-type: text/html; charset=utf-8');

$file_name = 'comments.html';
$num_str = 40000; // Варьируется по ходу экспериментов
$word_str = 'йцукенгшщзхъфывапролджэячсмитьбю1234567890qwertyuiopasdfghjkl;zxcvbnm,.';

$len = strlen($word_str);
$str_Arr = preg_split('//', $word_str);

$fd = fclose(fopen($file_name, 'w'));
$fd = fopen($file_name, 'r+');

$comm = '<div>';
fwrite($fd, $comm. "\n");

for($i=0; $i < $num_str; $i++){
$word = substr($word_str, rand(0, 10)*2, rand(11, 20)*2);

$comm = '<div id="comm_'. $i. '" class="qwer" data-time="'. time(). '">
<span id="user_'.$i.'" class="tyuio">USER_'. ($i%10) .'</span><div id="mess_'. $i .'">'.$word. '</div>
</div>';

fwrite($fd, $comm. "\n");
}

$comm = '</div>';

fwrite($fd, $comm. "\n");
fclose($fd);
echo 'Файл создан.';

Число сообщений задается при помощи переменной $num_str  (на данный момент она равна 40000). Меняя ее, затем обновляя страницу (т.е. перезапуская файл create_file.php), в том же каталоге сервера будет заново создаваться текстовый файл comments.html. Число блоков с сообщениями в нем будет равно значению переменной $num_str.

Тестируем функцию preg_match_all

Не будем останавливаться на ее описании, так как это достаточно хорошо изложено в мануале, т.е. на сайте php.net.

Создадим файл с именем preg_match_all.php и поместим туда следующий код:

 <?php

header('Content-type: text/html; charset=utf-8');
$t0 = microtime (true);
$USER = 'USER_1';
$content = file_get_contents('comments.html');

$str_to_find = '<span id="user_';
$str_to_find1 = '<div id="mess_';

$mess_num = preg_match_all('!'. preg_quote($str_to_find). '[^>]*?>([^<]+)'. '!', $content, $mathes, PREG_PATTERN_ORDER);

$mess_num1 = preg_match_all('!'. preg_quote($str_to_find1). '[^>]*?>([^<]+)'. '!', $content, $mathes1, PREG_PATTERN_ORDER);

$mess_Arr = array_filter($mathes[1], function ($var) use ($USER){
return (strval($var) === $USER);
}) ;

$mess_Arr1 = array();

for($i=0; $i< ($mess_num); $i++){
if(isset($mess_Arr[$i])){
$mess_Arr1[$i] = $mathes1[1][$i];
}
}

echo 'Всего '. $mess_num. ' сообщений, из них '. sizeof($mess_Arr). ' принадлежат пользователю '. $USER. ', всего '. sizeof($mess_Arr1) .'<br/>';

echo '<table border="1"><tr><td>n: '. $i .'</td><td>Общее время:</td><td>'. (1*microtime(true) - 1*$t0). '</td>'.'<td>Максимально используемая память: </td><td>' .memory_get_peak_usage(true) .'</td></tr></table>';

file_put_contents('rezults.txt', implode("\n", $mess_Arr1));

Тестируем класс domDocument

Создаем файл domDocument.php и там пишем:

 <?php

header('Content-type: text/html; charset=utf-8');
$t0 = microtime(true);
$USER = 'USER_1';
$content = file_get_contents('comments.html');

$Encoding = 'utf-8';
define('XMLHead', "<?xml version='1.0' encoding='".$Encoding."'?>");

$domdocument = new domDocument('1.0', $Encoding);
$domdocument->loadXML(XMLHead . "<html><body>" . $content . "</body></html>");

// Получаем все теги с именем div и span
$div = $domdocument->getElementsByTagname("div");
$span = $domdocument->getElementsByTagname("span");

$mess_Arr1 = array();

for($i=0; $i< $span->length; $i++){
if($span->item($i)->textContent === $USER){
$mess_Arr1[] = $span->item($i)->nextSibling->textContent;
}
}

echo '<table border="1"><tr><td>n: '. $i .'</td><td>Общее время:</td><td>'. (1*microtime(true) - 1*$t0). '</td>'.'<td>Максимально используемая память: </td><td>' .memory_get_peak_usage(true) .'</td></tr></table>';

file_put_contents('rezults1.txt', implode("\n", $mess_Arr1));

Если все сделано правильно, оба этих файла будут создавать свои текстовые файлы, содержащие сообщения пользователя с ником USER_1. Имена этих файлов rezults.txt и rezults1.txt соответственно. Их размеры, равно как и содержимое, должны тождественно соответственно совпадать, т.е. быть полностью идентичными. Если такого нет, значит, в одной из приведенных программ РНР была допущена ошибка, следует ее исправить.

Обсуждение результатов (PHP5.3)

Теперь посмотрим, что получается в итоге. Вначале тестирование проводилось с использованием языка РНР5.3 в среде Denwer, операционная система Windows 7. И вот что получилось для разных размеров файлов с сообщениями:


preg_match_all:


n: 10 Общее время: 0.00059294700622559 Максимально используемая память: 524288
n: 100 Общее время: 0.0013430118560791 Максимально используемая память: 524288
n: 500 Общее время: 0.0077590942382812 Максимально используемая память: 786432
n: 1000 Общее время: 0.014673948287964 Максимально используемая память: 1048576
n: 2000 Общее время: 0.029839992523193 Максимально используемая память: 1835008
n: 3000 Общее время: 0.037868976593018 Максимально используемая память: 2621440
n: 4000 Общее время: 0.023346900939941 Максимально используемая память: 3145728
n: 5000 Общее время: 0.028859853744507 Максимально используемая память: 3932160
n: 20000 Общее время: 0.11564588546753 Максимально используемая память: 14680064
n: 40000 Общее время: 0.22864890098572 Максимально используемая память: 29884416

Примечание: при =40000 объем файла с сообщениями составил почти 7 МБ.


domDocument:

n: 10 Общее время: 0.00034093856811523 Максимально используемая память: 524288
n: 100 Общее время: 0.0029091835021973 Максимально используемая память: 524288
n: 500 Общее время: 0.037052869796753 Максимально используемая память: 786432
n: 1000 Общее время: 0.092877864837646 Максимально используемая память: 1048576
n: 2000 Общее время: 0.32314991950989 Максимально используемая память: 2097152
n: 3000 Общее время: 0.72289109230042 Максимально используемая память: 2097152
n: 4000 Общее время: 1.5535569190979 Максимально используемая память: 2883584
n: 5000 Общее время: 2.9380710124969 Максимально используемая память: 3670016

Скорость работы функции preg_match_all и класса domDocument в языке РНР при разных объемах входной строки в PHP5.3
 

Для наглядности, посмотрим график.



Вывод – однозначен. Функция, основанная на регулярных выражениях, работает гораздо быстрее, чем класс domDocument. И если при числе сообщений до 500 различие в скорости еще не столь существенно, то когда число сообщений достигает 2000 и более, использование класса domDocument является уже попросту нецелесообразным. Ибо время работы этого класса просто катастрофически возрастает по мере роста объема файла (числа сообщений).

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

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

Таким образом, при применении PHP5.3 использование класса domDocument целесообразно, разве что, на небольших файлах. А для мало-мальски серьезной работы об этом классе лучше… забыть. Впрочем, возможно, в следующих версиях языка PHP его работа будет как-то оптимизирована.

Обсуждение результатов (PHP8.0)

Интерпретатор РНР запускался из командной строки в виртуальной машине Windows 7. И вот что получилось:


preg_match_all:

n: 100 Общее время: 0 Максимально используемая память: 2097152
n: 500 Общее время: 0.0010430812835693 Максимально используемая память: 2097152
n: 1000 Общее время: 0.0010120868682861 Максимально используемая память: 2097152
n: 1000 Общее время: 0.0009770393371582 Максимально используемая память: 2097152
n: 3000 Общее время: 0.0016880035400391 Максимально используемая память: 4194304
n: 4000 Общее время: 0.0036709308624268 Максимально используемая память: 4194304
n: 5000 Общее время: 0.0041470527648926 Максимально используемая память: 4194304
n: 6000 Общее время: 0.0054330825805664 Максимально используемая память: 6291456
n: 7000 Общее время: 0.0044009685516357 Максимально используемая память: 6291456
n: 8000 Общее время: 0.0064430236816406 Максимально используемая память: 4194304
n: 9000 Общее время: 0.0084199905395508 Максимально используемая память: 4194304
n: 10000 Общее время: 0.0061380863189697 Максимально используемая память: 4194304
n: 20000 Общее время: 0.018905878067017 Максимально используемая память: 8388608
n: 40000 Общее время: 0.042385101318359 Максимально используемая память: 31457280
n: 60000 Общее время: 0.050030946731567 Максимально используемая память: 35651584
n: 80000 Общее время: 0.058995008468628 Максимально используемая память: 52428800
n: 100000 Общее время: 0.1001980304718 Максимально используемая память: 50331648
n: 150000 Общее время: 0.14366793632507 Максимально используемая память: 85983232
n: 200000 Общее время: 0.16236116409302 Максимально используемая память: 100663296
n: 250000 Общее время: 0.21462988853455 Максимально используемая память: 130023424

Примечание: при n = 250000 объем файла с сообщениями составил почти 42,1 МБ.


domDocument:

n: 100 Общее время: 0.001953125 Максимально используемая память: 2097152
n: 500 Общее время: 0.029081106185913 Максимально используемая память: 2097152
n: 1000 Общее время: 0.13582992553711 Максимально используемая память: 2097152
n: 1000 Общее время: 0.11102414131165 Максимально используемая память: 2097152
n: 3000 Общее время: 1.9373848438263 Максимально используемая память: 2097152
n: 4000 Общее время: 4.1183609962463 Максимально используемая память: 2097152
n: 5000 Общее время: 11.798079013824 Максимально используемая память: 4194304

Скорость работы функции preg_match_all и класса domDocument в языке РНР при разных объемах входной строки в PHP8.0
 

Для сопоставления, отложим эти и ранее полученные данные на одном графике.


Как видно, результат более, чем неожиданный. Класс domDocument, по сути, работает крайне медленно даже при небольшом числе сообщений в файле. И для РНР8.0 он работает еще медленнее (примерно в 2 раза) по сравнению с РНР5.3.

А вот код, основанный на функции preg_match_all, что интересно, напротив, в PHP8.0 работает существенно быстрее (аж на порядок), чем в РНР5.3.

Чем это вызвано – сложно сказать, не разбирая реализацию данных функций (на языке С++).

Что касается версии 8 языка РНР, то она, на одном и том же компьютере, зачастую работает на порядок быстрее, чем старая версия 5.

Ясно одно: прежде, чем использовать ту или иную альтернативу в языке РНР, следует протестировать имеющиеся варианты, почитать отзывы о них. Ибо разница в быстродействии получается весьма высокая.

Окончательный вывод

Как бы ни было грустно, но класс domDocument языка PHP, по всей видимости, не обладает достаточным быстродействием для мало-мальски больших объемов данных. Вся его область применения – это небольшие файлы (до нескольких мегабайт) XML/HTML.

ЕДИНСТВЕННОЕ, ради чего его возможно применять, это… некое удобство по сравнению с функциями из серии preg_match_all, не говоря уже о str_replace. А также - вызванная этим удобством более высокая скорость разработки. И, пожалуй, не более того.

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

Видимо, именно поэтому класс domDocument языка PHP является каким-то... недоработанным. Например, есть поиск тегов по идентификатору (id), по имени тега. И, вроде бы, все. Даже поиск по классу, по другим атрибутам, как это реализовано в языке javascript, отсутствует. И это понятно, почему: потому что нет смысла делать такой поиск, ведь он будет тормозить при мало-мальски серьезном использовании. Поэтому создатели языка PHP, вероятно, и выполнили лишь самую основную функциональность для класса domDocument, а потом, по сути, забыли про него. При разработке новых версий РНР уделив внимание наиболее актуальным функциям. И это, как видится, совершенно правильная стратегия.

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

Впрочем, так обстоит дело, как выяснилось на практике, лишь для операций поиска и выборки сообщений. А вот что будет наблюдаться, например, при замене или редактировании сообщений – пока неизвестно. Это надо тоже проводить соответствующие эксперименты. Понятно, однако, что прежде, чем делать замену/редактирование, соответствующее сообщение нужно, по крайней мере, найти. Это наводит на мысль, что и по таким операциям класс domDocument будет проигрывать по скорости функциям типа preg_match_all.

Единственное полезное применение класса domDocument

Впрочем, есть у этого класса одно очень полезное применение. Это - быстрая проверка корректности строки на соответствие правилам кода XML или HTML. Это может быть полезным, например, перед сохранением данных в соответствующем формате. Ибо писать соответствующую проверочную функцию самостоятельно - это, мягко говоря, такое себе... Хотя, вполне можно.

Правда, и там есть проблема. Дело в том, что интерпретатор функций типа xmlload или htmlload работает лишь для... html 4. Да, до сих пор. Поэтому новые теги, например, <video>, <aside> и т.д. вызывают ошибку. Создатели языка обещались, что, вроде бы, в 8-й версии они добавят (или уже добавили?) поддержку новых тегов html 5.

Кстати…

Попутно, зададимся вопросом: а где же могут применяться столь большие файлы, как выше в примере – до 42 МБ? Содержащие сотни тысяч сообщений. Ведь это уже, по сути, объем не столько уже файла, сколько (очень небольшой) базы данных. И очень редко бывает так, чтобы для одной html-страницы имелся такой большой объем комментариев. В самом деле, если принять средний размер одного комментария, вместе с его html-разметкой, равным 4 кБ, то число сообщений в таком файле составит 42.000/4 = 10500 сообщений. Такое, по-видимому, бывает крайне редко. Автору данной статьи за многие-многие годы встречался только один сайт, где на одной странице число сообщений превысило 10 тысяч. Ну, можно вспомнить еще социальные сети, конечно. Когда человек пишет посты в своей ленте. Например, нынче у крупных Telegram-каналов число таких постов как раз составляет сотни тысяч.

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

Замечание

Приведенные выше результаты обнадеживают и прямо-таки оптимистично настраивают на использование функции preg_match_all и аналогичных ей, столь же быстродействующих. Однако, в общем случае их применение может показаться… сложным, особенно начинающим; и для тех, кто привык решать «все проблемы» одним-двумя запросами к базе данных.

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

Правда, в последние годы среди вебразработчиков бытует утверждение, что, мол, «следует отделять верстку от функциональности». В целом, это правильно, но… они забывают о том, что сам язык РНР и был придуман как раз для того, чтобы СМЕШИВАТЬ html-верстку и код на PHP. Точнее, чтобы вставлять в html-страницы активное содержимое, обрабатываемое сервером. Там, где это требуется. Поэтому видится, что иногда, в целях обеспечения более высокой скорости открытия html-страниц, можно поступиться этим принципом (который, по идее, выглядит несколько спорным). И планировать верстку сообщений таким образом, чтобы она была, по возможности, более приспособлена к использованию простых, но быстродействующих функций языка PHP. Пусть и требующих более тщательной работы. 

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

Другое дело, что использование технологий баз данных существенно облегчает выборку информации. Например, чтобы получить все сообщения одного пользователя, нам пришлось писать программные коды (для демонстрации овзможности). А в случае с базами данных – там будет лишь один запрос (например, SQL). Т.е., по сути, весь код уместится в одну строчку.

Другое дело, если потребуется получить все сообщения пользователя не для какой-либо одной страницы, а для нескольких (в самом сложном случае – для всех). Там также будет, скорее всего, лишь один запрос. А вот при работе с файлами придется просматривать каждый их них и применять один из приведенных выше кодов. Это может, в ряде случаев, существенно увеличить время работы скрипта.

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

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

1. Все дело в безопасности. Не зря ведь, в конце концов, в языке РНР все старые функции для работы с SQL-запросами были признаны устаревшими (т.е. уязвимыми) и вместо них были разработаны полностью новые, аналогичные функции. В сухом остатке это означает, что, в свое время, сайты, которые использовали те (ныне устаревшие) функции, были уязвимыми. И такие уязвимости носили, очевидно, массовый характер. И это правда: достаточно посмотреть статьи в интернете на предмет уязвимостей. Поэтому риск от использования удобных, но готовых решений все же есть. До сих пор.

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

Однако, не стоит обольщаться. Если вебразработчиком будет принято решение – выполнить сайт на файлах, без использования готовых баз данных, то придется, по сути, создать… свою, кастомную базу данных. Если, конечно, не будет идти речь о простом статическом сайте.

Насколько она будет эффективнее готовых решений – вопрос дискуссионный. И это при том, что каждую мелочь, даже такую, как выборку сообщений по тому или иному критерию, придется реализовывать самостоятельно. И в ряде случаев это будет не слишком простой задачей. Если использовать-то "низкоуровневые" функции типа preg_match_all. Тут придется, повторимся, тщательно продумывать и HTML-верстку, и структуру каждого соответствующего файла и т.д. Придется думать о том, как ускорить доступ к соответствующей части файла; как производить индексацию (в виде заранее запасенных копий соответствующих выборок). Подобные задачи по силам лишь опытным специалистам в области PHP.

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


Комментарии:
Всего комментариев:0
Пожалуйста, не забудьте ознакомиться с правилами оставления комментариев.



Подписаться на комментарии на этой странице

Мы можем выполнить

Другие услуги
Интересная и полезная
информация
НАПИШИТЕ НАМ
Яндекс.Метрика
Номер телефона
© Copyright Все права защищены 2013-2025 Научный консалтинг