Гру 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 – два разных кеша для кода и данных первого и второго уровня, какой то из них можно отключить.)

Гру 272011
 

Выносите многомерные массивы из “текстовых строк” – ускорение 25-30%. Одномерные можно не выносить.

При использовании многомерных массивов в строках наблюдается заметное снижение скорости Из-за многомерности нужно заключать переменные в парные фигурные скобки.

  1. {$x="test ".$myarray["name"]["second"][1]." test";       }
  2. {$x="test {$myarray[name][second][1]} test";             }
  3. {$x="test ";$x.=$myarray["name"]["second"][1];$x=" test";}

 

счетчик кол-во
вызовов
общее
вpемя
сpеднее
вpемя
% от min % от max
test N1 1 3.5369 3.5369 00.0% 69.9%
test N2 1 5.0605 5.0605 43.1% 100.0%
test N3 1 4.6017 4.6017 30.1% 90.9%

Тот же пример с ассоциативным 3-х мерным массивом, но с обращением к элементу по его индексному номеру:

  1. {$x="test ".$myarray[3][2][0]." test";       }
  2. {$x="test {$myarray[3][2][0]} test";         }
  3. {$x="test ";$x.=$myarray[3][2][0];$x=" test";}

 

счетчик кол-во
вызовов
общее
вpемя
сpеднее
вpемя
% от min % от max
test N1 1 3.3012 3.3012 00.0% 73.1%
test N2 1 4.1667 4.1667 26.2% 92.3%
test N3 1 4.5145 4.5145 36.8% 100.0%

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

  1. {$x="test".$myarray["test"]."test";}
  2. {$x="test$myarray[test]test";      }
  3. {$x="test{$myarray[test]}test";    }

 

счетчик кол-во
вызовов
общее
вpемя
сpеднее
вpемя
% от min % от max
test N1 1 2.8495 2.8495 00.0% 80.9%
test N2 1 2.9519 2.9519 03.6% 83.9%
test N3 1 3.5202 3.5202 23.5% 100.0%

А теперь, на основании всех трех тестов, однозначный вывод: использовать фигурные скобки для обозначения границ имени элемента многомерного массива НЕЛЬЗЯ. Это сильно снижает скорость работы – 25-30% (в третьем варианте от простого добавления скобок скорость понизилась на четверть). Не использовать скобки нельзя. Следовательно, единственный способ не терять 30% скорости – выносить многомерные массивы из скобок.

Такой же тест, но для одномерных:

  1. {$x="test".$myarray["test"]."test".$myarray["test"]."test".$myarray["test"];   }
  2. {$x="test$myarray[test]testtest$myarray[test]testtest$myarray[test]test";      }
  3. {$x="test{$myarray[test]}testtest{$myarray[test]}testtest{$myarray[test]}test";}

 

счетчик кол-во
вызовов
общее
вpемя
сpеднее
вpемя
% от min % от max
test N1 1 6.5843 6.5843 00.0% 78.0%
test N2 1 6.7770 6.7770 02.9% 80.3%
test N3 1 8.4406 8.4406 28.2% 100.0%

Сравнивая два последних теста очевидно, что одномерные массивы можно и не выносить, потери всего 3-4% ( а вот на простых переменных – потери 25-40%!).

Гру 272011
 

Тормозят ли массивы в PHP? Вернее, как именно. Ускорение 40%.

А вот и не тормозят. Я где-то читал, якобы ассоциативные массивы в PHP жутко тормозят. Конечно, тест простой, но большой разницы между непрерывным простым (1), простым (2) и ассоциативным (3) массивами нет (элемент 0000 преобразуется в 0 – это же число, а не строка). И уж явно не тормозят “не ассоциативные не сплошные массивы”.

  1. {$test[0000]=1;$test[0001]=1;$test[0002]=1;$test[0003]=1;$test[0004]=1;     }
  2. {$test[1000]=1;$test[1001]=1;$test[1002]=1;$test[1003]=1;$test[1004]=1;     }
  3. {$test["aa"]=1;$test["bb"]=1;$test["cc"]=1;$test["dd"]=1;$test["ee"]=1;     }
  4. {$test[aa]=1;  $test[bb]=1;  $test[cc]=1;  $test[dd]=1;  $test[ee]=1;       }
  5. {$test[0][0]=1;$test[0][1]=1;$test[0][2]=1;$test[0][3]=1;$test[0][4]=1;     }
  6. {$test[2][1]=1;$test[3][8]=1;$test[4][9]=1;$test[33][99]=1;$test[123][99]=1;}
  7. {$test[a][b]=1;$test[x][y]=1;$test[d][c]=1;$test[a][s]=1;$test[b][n]=1;     }

 

счетчик кол-во
вызовов
общее
вpемя
сpеднее
вpемя
% от min % от max
test N1 1 5.3924 5.3924 01.1% 28.0%
test N2 1 5.3332 5.3332 00.0% 27.7%
test N3 1 5.7651 5.7651 08.1% 29.9%
test N4 1 7.6543 7.6543 43.5% 39.7%
test N5 1 6.6649 6.6649 25.0% 34.6%
test N6 1 6.6221 6.6221 24.2% 34.3%
test N7 1 19.2820 19.2820 261.5% 100.0%

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

 Posted by at 10:35
Гру 272011
 

Короткие переменные не более 7 символов – ускорение 15%

Как влияет длина имен переменных на скорость программы? Если использовать очень длинные переменные – очевидно, что весьма сильно. Однако и с короткими именеми не все просто:

  1. {$x=1;}
  2. {$x2=1;}
  3. {$x03=1;}
  4. {$x004=1;}
  5. {$x0005=1;}
  6. {$x00006=1;}
  7. {$x000007=1;}
  8. {$x0000008=1;}
  9. {$x000000010=1;}
  10. {$x00000000012=1;}
  11. {$x0000000000014=1;}
  12. {$x000000000000016=1;}
  13. {$x0000000000000000000000000000032=1;}

выдает предсказуемый результат:

 

 

счетчик кол-во
вызовов
общее
вpемя
сpеднее
вpемя
% от min % от max
test N1 1 1.7000 1.7000 00.0% 68.5%
test N2 1 1.7028 1.7028 00.2% 68.6%
test N3 1 1.7182 1.7182 01.1% 69.2%
test N4 1 1.7228 1.7228 01.3% 69.4%
test N5 1 1.7536 1.7536 03.2% 70.6%
test N6 1 1.7504 1.7504 03.0% 70.5%
test N7 1 1.7799 1.7799 04.7% 71.7%
test N8 1 1.9604 1.9604 15.3% 78.9%
test N9 1 1.9865 1.9865 16.9% 80.0%
test N10 1 2.0119 2.0119 18.3% 81.0%
test N11 1 2.0302 2.0302 19.4% 81.7%
test N12 1 2.1288 2.1288 25.2% 85.7%
test N13 1 2.4835 2.4835 46.1% 100.0%

Переменные от 32 символов могут тормознуть программу почти на половину.

Но если заполнять пробелами (" "), чтобы все строки “$x=1; ...” по длине занимали одно и тоже расстояние, то получается вот что:

 

  1. {$x=1;                                   }
  2. {$x2=1;                                  }
  3. {$x03=1;                                 }
  4. {$x004=1;                                }
  5. {$x0005=1;                               }
  6. {$x00006=1;                              }
  7. {$x000007=1;                             }
  8. {$x0000008=1;                            }
  9. {$x000000010=1;                          }
  10. {$x00000000012=1;                        }
  11. {$x0000000000014=1;                      }
  12. {$x000000000000016=1;                    }
  13. {$x0000000000000000000000000000032=1;    }

 

счетчик кол-во
вызовов
общее
вpемя
сpеднее
вpемя
% от min % от max
test N1 1 2.1167 2.1167 01.9% 83.3%
test N2 1 2.0766 2.0766 00.0% 81.7%
test N3 1 2.0937 2.0937 00.8% 82.4%
test N4 1 2.0821 2.0821 00.3% 81.9%
test N5 1 2.1145 2.1145 01.8% 83.2%
test N6 1 2.0921 2.0921 00.7% 82.3%
test N7 1 2.1076 2.1076 01.5% 82.9%
test N8 1 2.3058 2.3058 11.0% 90.7%
test N9 1 2.3046 2.3046 11.0% 90.7%
test N10 1 2.3107 2.3107 11.3% 90.9%
test N11 1 2.3111 2.3111 11.3% 90.9%
test N12 1 2.3680 2.3680 14.0% 93.2%
test N13 1 2.5418 2.5418 22.4% 100.0%

Уж как комментировать тест переменной из одного символа (на 2% медленне самого быстрого) – не знаю… Наверно, тесты имеют большую погрешность. Предлагаю кому-нибудь запустить тест на час (исходники теста внизу страницы).

Одно ясно – при длине переменных в 8 и более символов происходит резкое снижение производительности, до 15%! А команд, включающих названия переменных, очень много. Еще один менее резкий скачек на переменных с именем 16 символов в длину и более. А в остальных случаях – чем больше, тем дольше, весьма линейная зависимость.

Вывод – не используйте переменны из 8 и более символов, выиграете 15% скорости (вернее, сэкономите).

Гру 272011
 

Выносите $переменные из “текстовых строк” – ускорение 25-40%

Одна и таже операция присваивания (либо echo/print для вывода на экран) в зависимости от того, заключены ли переменные в кавычеки или нет, сильно влияет на скорость. В первом и втором вариантах добавлены пробелы, чтобы выравнять размер общего кода для парсинга.

  1. {$x="test".$test;    }
  2. {$x="test $test";    }
  3. {$x="test";$x.=$test;}

Переменная $test содержит строку “1234567890”.

 

 

 

счетчик кол-во
вызовов
общее
вpемя
сpеднее
вpемя
% от min % от max
test N1 1 3.5911 3.5911 00.0% 70.9%
test N2 1 5.0616 5.0616 40.9% 100.0%
test N3 1 4.9870 4.9870 38.9% 98.5%

Итак, никогда не пишите $a="$b", ибо это затормозит программу (в этой строке) на 40%.

Однако, если у вас большая строка, где много текста и переменных, различия в скорости уменьшаются, т.к. общие затраты на парсинг становятся намного больше, чем разные по эффективности команды. Но почему бы и не увеличить скорость программы (строк присваивания) почти на четверть таким простым методом?

  1. {$x="test ".$test." test ".$test." test ".$test;}
  2. {$x="test $test test $test test $test";}
  3. {$x="test ";$x.=$test;$x="test ";$x.=$test;$x="test ";$x.=$test;}

счетчик кол-во
вызовов
общее
вpемя
сpеднее
вpемя
% от min % от max
test N1 1 7.6894 7.6894 00.0% 66.0%
test N2 1 9.5515 9.5515 24.2% 82.0%
test N3 1 11.6506 11.6506 51.5% 100.0%