Приветствую Вас ГостьЧетверг, 22.01.2026, 17:51

Light Midnight Inc.


Каталог статей

Главная » Статьи » Программирование » PHP/JavaScripts

Оптимизация программ на PHP (2)

Регулярные выражения: PHP(POSIX) vs Perl.

PHP поддерживает регулярные выражения стандарта POSIX/eger*/ и PERL/preg*/-ориентированные (об их различии тут - php.spb.ru/regular_expression.html). Кто из них работает быстрее?

Хочу заранее предупредить любителей Перла, чтобы не радовались: хоть перловые реги и круче пхпышных, только ничто и никто не мешает использовать в PHP перловые реги! Наверно, потому их и встроили в PHP, что уж больно тормоза большие... :-)

Итак, простейший текст. Поиск простого выражения в тексте, который состоит из многократного повторения данной статьи (получается размер переменной $text в 3 Мб).

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

  1. {eregi("МаС+иВ",$text);}
  2. {preg_match("/МаС+иВ/im",$text);}
счетчиккол-во
вызовов
общее
вpемя
сpеднее
вpемя
% от min% от maxобщее
время
test N111.33391.3339107.8%100.0%
speed
speed
test N210.64170.641700.0%48.1%
speed
speed
  1. {eregi("(ма[a-zа-я]{1,20})",$text);}
  2. {preg_match("/(ма[a-zа-я]{1,20})/im",$text);}
счетчиккол-во
вызовов
общее
вpемя
сpеднее
вpемя
% от min% от maxобщее
время
test N110.45210.452176.9%100.0%
speed
speed
test N210.25560.255600.0%56.5%
speed
speed

Пример для другого выражения и 30-мегабайтного текста (все те же повторы статьи, что вы сейчас читаете):

  1. {eregi("(ма[a-zа-я]{1,20})",$text);}
  2. {preg_match("/(ма[a-zа-я]{1,20})/im",$text);}
счетчиккол-во
вызовов
общее
вpемя
сpеднее
вpемя
% от min% от maxобщее
время
test N111.34301.343060.6%100.0%
speed
speed
test N210.83650.836500.0%62.3%
speed
speed

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

Далее один очень показательный пример на этой же статье (увеличение до 28Мб). Пример ищет в тексте e-mail. По свойству "жадности" регулярных выражений будет найден самый большой и наболее близкий к левому краю адрес.

Этот пример огорчит любителей перла. Приятно их огорчать :-)

  1. {eregi("([a-z_-]+@([a-z][a-z-]*\.)+([a-z]{2}|com|mil|org|net|gov|edu|arpa|info|biz))",$text);}
  2. {preg_match("/([a-z_-]+@([a-z][a-z-]*\.)+([a-z]{2}|com|mil|org|net|gov|edu|arpa|info|biz))/im",$text);}
счетчиккол-во
вызовов
общее
вpемя
сpеднее
вpемя
% от min% от maxобщее
время
test N1111.682811.6828680.3%100.0%
speed
speed
test N211.49731.497300.0%12.8%
speed
speed

Из одного теста делать вывод сложно, но, видимо, чем сложнее регулярное выражение, тем больше POSIX отстает от Perl.

А теперь тот же пример, но только в статье (увеличение до 28Мб) нет НИ ОДНОГО символа "@" (я специально сделал копию статьи и стер эти символы):

  1. {eregi("([a-z_-]+@([a-z][a-z-]*\.)+([a-z]{2}|com|mil|org|net|gov|edu|arpa|info|biz))",$text,$ok); echo $ok[1];}
  2. {preg_match("/([a-z_-]+@([a-z][a-z-]*\.)+([a-z]{2}|com|mil|org|net|gov|edu|arpa|info|biz))/im",$text,$ok); echo $ok[1];}
счетчиккол-во
вызовов
общее
вpемя
сpеднее
вpемя
% от min% от maxобщее
время
test N110.58540.585400.0%10.2%
speed
speed
test N215.76715.7671885.2%100.0%
speed
speed

Что мы видим?.. Ничто в этом мире не совершенно. Конечно, это очень не оптимизированное выражение для поиска email'ов, но всё же все те, кто кричал мне в форуме "ereg - отстой", на этом и похожих примерах могут отдыхать. Бывает же, что в тексте нет ни одной собачки :-)

Итак, вывод о скорости с примерами был дан выше. Вывод однозначный - надо использовать Perl-ориентированные регулярные выражения. В начеле главы я упоминал о кешировании откомпилированных копий регов. Если в программе одно и тоже выражение встречается неоднократно, производительность может отличаться не просто многократно, а в 10-100-1000 раз!

Селдующий пример вызывается 200 раз подряд над текстом в 250Кб:

  1. {eregi("МаС+иВ",$text);}
  2. {preg_match("/МаС+иВ/im",$text);}
счетчиккол-во
вызовов
общее
вpемя
сpеднее
вpемя
% от min% от maxобщее
время
test N1117.638417.638472381.4%100.0%
speed
speed
test N210.02430.024300.0%00.1%
speed
speed

Что такое кеш - знают все. Видимо именно с кешем в PHP проблеммы... Кстати, ради примера, отключите в BIOSе вашего комптьютера кеш процессора и попробуйте загрузить Windows 2000... Не дождетесь! (Кажется, их называют L1 и L2 - два разных кеша для кода и данных первого и второго уровня, какой то из них можно отключить.)


Циклы: for, foreach, while, count/sizeof() - ускорение 15%-30%

В начале программы создается массив $test из целых чисел (100 000 элементов). Потом один раз запускаются приведенные ниже примеры. Цикл проходит данный массив 3-мя способами (разными циклами) и выполняет кое-какие операции. Не выполнять в цикле ничего нельзя, ибо это будет уже совсем не реальный тест.
  1. {$x=0; foreach($test as $n)                          { $x=sprintf("test%08i",$i);        }}
  2. {$x=0; for ($it=0; $it<100000; $it++)                { $x=sprintf("test%08i",$i);        }}
  3. {$x=0; $it=0; while($it<100000)                      { $x=sprintf("test%08i",$i); $it++; }}
  4. {$x=0; for ($it=0; $it<count($test); $it++)          { $x=sprintf("test%08i",$i);        }}
  5. {$x=0; $it=0; while($it<count($test))                { $x=sprintf("test%08i",$i); $it++; }}
  6. {$x=0; $co=count($test); for ($it=0; $it<$co; $it++) { $x=sprintf("test%08i",$i);        }}
  7. {$x=0; $co=count($test); $it=0; while($it<$co)       { $x=sprintf("test%08i",$i); $it++; }}
счетчиккол-во
вызовов
общее
вpемя
сpеднее
вpемя
% от min% от maxобщее
время
test N1112.031312.0313154.4%100.0%
speed
speed
test N214.72904.729000.0%39.3%
speed
speed
test N314.77124.771200.9%39.7%
speed
speed
test N4110.284710.2847117.5%85.5%
speed
speed
test N5110.346610.3466118.8%86.0%
speed
speed
test N619.12719.127193.0%75.9%
speed
speed
test N719.14099.140993.3%76.0%
speed
speed

Почему sprintf, а не реальное echoecho использовать нельзя, т.к. от него будет немерянный буфер (OUTPUT в браузер или консоль).

Теперь о деле. Бесспорный вывод - использование foreach сильно тормозит дело, а между for и while большой разницы нет. (На голом тесте for/while/foreach {..} тормоза foreach - 30%). Это не удивительно, т.к. foreach делает копию массива, на что тратиться масса времени (хотя это только слухи).

Вывод с count() не столь очевиден, потому что от разного текста в цикле % тормознутости от самого быстрого варианта резко возрастает... Я взял цикл с небольшой нагрузкой - проход по огромному массиву $test + форматирование функцией sprintf. Как видите, варинты с count() и заменяющей эту функцию перемнной $co различаются на 10% по скорости между собой (не смотрите на варинант с константой в 100000, заранее знать кол-во элементов невозможно).

Вывод о не ассоциативных массивах: 1) foreach существенно замедляет работу 2) использование count() в простых циклах - замедленение 10%. Но на сложных циклах потери от лишних запусков count() будут абсолютно незаметны, так что ситуация не очевидна.

Сравнение count() и sizeof().

Судя по мануалу - это алиасы. Об этом написано на страницах самих функций и дополнительной странице "Appendex => Aliases list". Что же мы видим на массиве в 100000 элементов:

  1. {$x=0; for ($it=0; $it<count($test); $it++)  { $x=sprintf("test%08i",$test[$it]);}}
  2. {$x=0; for ($it=0; $it<sizeof($test); $it++) { $x=sprintf("test%08i",$test[$it]);}}
счетчиккол-во
вызовов
общее
вpемя
сpеднее
вpемя
% от min% от maxобщее
время
test N113.00873.008715.7%100.0%
speed
speed
test N212.59982.599800.0%86.4%
speed
speed

Пусть тесты будут иметь погрешности... Но результат один - count() заметно отстает по скорости от sizeof()! Хм, я бы к записи в мануале сделал приписку: "The sizeof() function is an alias for count(), but последний сильно тормозит!"

Если кол-во элементов в массиве меньше 65000 (64К), то эти функции по скорости практически не различимы. Тут вывод простой - переходим на использование sizeof(), как ускоренного алиаса count(). Это принесет свои результаты на огромных массивах.

Ассоциативные массивы: тестирование разных способов перебора

С ними наблюдается таже проблема: на разных по величине массивах разные функции эффективны, но лучше всех foreach!

Массив в 200 элементов и 1000 повторов программы:

  1. {$x=0; foreach($test as $k=>$v) { $x=sprintf("%s=>%s\n",$k,$v);                                                           }}
  2. {$x=0; reset($test); while (list($k, $v) = each($test)) { $x=sprintf("%s=>%s\n",$k,$v);                                   }}
  3. {$x=0; $k=array_keys($test); $co=sizeof($k); for ($it=0; $it<$co; $it++) { $x=sprintf("%s=>%s\n",$k[$it],$test[$k[$it]]); }}
  4. {$x=0; reset($test); while ($k=key($test)) { $x=sprintf("%s=>%s\n",$k,current($test)); next($test);                       }}
счетчиккол-во
вызовов
общее
вpемя
сpеднее
вpемя
% от min% от maxобщее
время
test N118.12228.122200.0%78.7%
speed
speed
test N2110.322110.322127.1%100.0%
speed
speed
test N319.79219.792120.6%94.9%
speed
speed
test N418.97118.971110.5%86.9%
speed
speed

Тоже самое, но массив в 5000 элементов и 200 повторов:

счетчиккол-во
вызовов
общее
вpемя
сpеднее
вpемя
% от min% от maxобщее
время
test N1114.447314.447300.0%67.2%
speed
speed
test N2118.680118.680129.3%86.9%
speed
speed
test N3121.505621.505648.9%100.0%
speed
speed
test N4115.851415.851409.7%73.7%
speed
speed

Опять тоже самое, но массив в 100 000 элементов и без повторов:

счетчиккол-во
вызовов
общее
вpемя
сpеднее
вpемя
% от min% от maxобщее
время
test N113.51163.511600.0%82.8%
speed
speed
test N213.97243.972413.1%93.6%
speed
speed
test N314.24364.243620.8%100.0%
speed
speed
test N414.00264.002614.0%94.3%
speed
speed

Другие тесты на холостых циклах тоже показывают преимущество foreach.

Резюме:

  • sizeof() лучше, чем count()
  • в циклах sizeof лучше вообще заменить на переменную
  • for и while практически не отличимы
  • для перебора простых индексных массивов нужно использовать for или while
  • для перебора ассоциативных массивов нужно использотьва foreach


Для чтения файла file() быстрее, чем fopen+цикл - ускорение 40%

Чтобы прочитать в массив $x файл размером 1Мб (100 000 строк по 10 байт) можно воспользоваться двумя вариантами: чтение файла с помощью file(), либо традиционным методом fopen/fgets. Разумеется, для файлов разного объема и содержимого скорость может меняться. Но в данном примере статистика такова:file("1Mb_file.txt") работает на 40% быстрее, чем:

 $f=fopen("1Mb_file.txt","r") or die(1);
 while($x[]=fgets($f,1000));
 fclose($f);

Аналогичные варианты

 $f=fopen("1Mb_file.txt","r") or die(1);
 while($s=fgets($f,1000)) $x[]=$s;
 fclose($f);

или

 $f=fopen("1Mb_file.txt","r") or die(1);
 while(!feof($f))) $x[]=fgets($f,1000);
 fclose($f);

работают еще медленнее (во втором случае лишняя функция feof() заметно снижает скорость). Тот же тест, но на 15Мб файле (100 000 строк по 150 байт) показывает разницу в 50%, в пользу file(). Тест проводился так, чтобы исключить фоновый своппинг во время работы из-за предшествующих команд создания/чтения таких больших файлов. Подсчитать тоже самое на очень маленьких файлах в 1-2 Кб не представляется возможным, т.к. операцию чтения нельзя повторять в течении одного теста, операции чтения будут кешироваться... 

Категория: PHP/JavaScripts | Добавил: Cromartie (31.01.2013)
Просмотров: 455 | Рейтинг: 0.0/0
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]
Наш опрос
Оцените мой сайт
Всего ответов: 543
Статистика

Онлайн всего: 1
Гостей: 1
Пользователей: 0
Реклама
Cheсking
Часы
Мини-чат
200
Друзья Сайта
  • Light Midnight - Ваша Еда
  • Light Midnight - Anim as life style
  • Поиск