Дек 272011
 

Регулярные выражения: 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 N1 1 1.3339 1.3339 107.8% 100.0%
test N2 1 0.6417 0.6417 00.0% 48.1%

 

  1. {eregi("(ма[a-zа-я]{1,20})",$text);}
  2. {preg_match("/(ма[a-zа-я]{1,20})/im",$text);}

 

счетчик кол-во
вызовов
общее
вpемя
сpеднее
вpемя
% от min % от max
test N1 1 0.4521 0.4521 76.9% 100.0%
test N2 1 0.2556 0.2556 00.0% 56.5%

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

  1. {eregi("(ма[a-zа-я]{1,20})",$text);}
  2. {preg_match("/(ма[a-zа-я]{1,20})/im",$text);}

 

счетчик кол-во
вызовов
общее
вpемя
сpеднее
вpемя
% от min % от max
test N1 1 1.3430 1.3430 60.6% 100.0%
test N2 1 0.8365 0.8365 00.0% 62.3%

Я еще писал штук пять разных выражений, но тенденция не меняется. Скорость может меняться, но 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 N1 1 11.6828 11.6828 680.3% 100.0%
test N2 1 1.4973 1.4973 00.0% 12.8%

Из одного теста делать вывод сложно, но, видимо, чем сложнее регулярное выражение, тем больше 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 N1 1 0.5854 0.5854 00.0% 10.2%
test N2 1 5.7671 5.7671 885.2% 100.0%

Что мы видим?.. Ничто в этом мире не совершенно. Конечно, это очень не оптимизированное выражение для поиска email’ов, но всё же все те, кто кричал мне в форуме «ereg — отстой», на этом и похожих примерах могут отдыхать. Бывает же, что в тексте нет ни одной собачки 🙂

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

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

  1. {eregi("МаС+иВ",$text);}
  2. {preg_match("/МаС+иВ/im",$text);}

 

счетчик кол-во
вызовов
общее
вpемя
сpеднее
вpемя
% от min % от max
test N1 1 17.6384 17.6384 72381.4% 100.0%
test N2 1 0.0243 0.0243 00.0% 00.1%

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