Оптимизация программ на PHP
Автор: Дмитрий Бородин (php.spb.ru)
В этой статье на простых и очевидных примерах рассказано о некоторых способах оптимизировать любую (готовую) программу, не меняя ни одного алгоритма. Для такой оптимизации можно даже написать программу для автоматического выполнения всех рекомендаций, все они очень простые (правда, для начала придется написать парсер пхп-кода).
Выносите $переменные из "текстовых строк" - ускорение 25-40%
Одна и таже операция присваивания (либо echo/print для вывода на экран) в зависимости от того, заключены ли переменные в кавычеки или нет, сильно влияет на скорость. В первом и втором вариантах добавлены пробелы, чтобы выравнять размер общего кода для парсинга.- {$x="test".$test; }
- {$x="test $test"; }
- {$x="test";$x.=$test;}
счетчик | кол-во вызовов |
общее в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% |
Однако, если у вас большая строка, где много текста и переменных, различия в скорости уменьшаются, т.к. общие затраты на парсинг становятся намного больше, чем разные по эффективности команды. Но почему бы и не увеличить скорость программы (строк присваивания) почти на четверть таким простым методом?
- {$x="test ".$test." test ".$test." test ".$test; }
- {$x="test $test test $test test $test"; }
- {$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% |
Короткие переменные не более 7 символов - ускорение 15%
Как влияет длина имен переменных на скорость программы? Если использовать очень длинные переменные - очевидно, что весьма сильно. Однако и с короткими именеми не все просто:- {$x=1;}
- {$x2=1;}
- {$x03=1;}
- {$x004=1;}
- {$x0005=1;}
- {$x00006=1;}
- {$x000007=1;}
- {$x0000008=1;}
- {$x000000010=1;}
- {$x00000000012=1;}
- {$x0000000000014=1;}
- {$x000000000000016=1;}
- {$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% |
Но если заполнять пробелами (
- {$x=1; }
- {$x2=1; }
- {$x03=1; }
- {$x004=1; }
- {$x0005=1; }
- {$x00006=1; }
- {$x000007=1; }
- {$x0000008=1; }
- {$x000000010=1; }
- {$x00000000012=1; }
- {$x0000000000014=1; }
- {$x000000000000016=1; }
- {$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% |
Одно ясно - при длине переменных в 8 и более символов происходит резкое снижение производительности, до 15%! А команд, включающих названия переменных, очень много. Еще один менее резкий скачек на переменных с именем 16 символов в длину и более. А в остальных случаях - чем больше, тем дольше, весьма линейная зависимость.
Вывод - не используйте переменны из 8 и более символов, выиграете 15% скорости (вернее, сэкономите).
Тормозят ли массивы в PHP? Вернее, как именно. Ускорение 40%.
А вот и не тормозят. Я где-то читал, якобы ассоциативные массивы в PHP жутко тормозят. Конечно, тест простой, но большой разницы между непрерывным простым (1), простым (2) и ассоциативным (3) массивами нет (элемент 0000 преобразуется в 0 - это же число, а не строка). И уж явно не тормозят "не ассоциативные не сплошные массивы".- {$test[0000]=1;$test[0001]=1;$test[0002]=1;$test[0003]=1;$test[0004]=1; }
- {$test[1000]=1;$test[1001]=1;$test[1002]=1;$test[1003]=1;$test[1004]=1; }
- {$test["aa"]=1;$test["bb"]=1;$test["cc"]=1;$test["dd"]=1;$test["ee"]=1; }
- {$test[aa]=1; $test[bb]=1; $test[cc]=1; $test[dd]=1; $test[ee]=1; }
- {$test[0][0]=1;$test[0][1]=1;$test[0][2]=1;$test[0][3]=1;$test[0][4]=1; }
- {$test[2][1]=1;$test[3][8]=1;$test[4][9]=1;$test[33][99]=1;$test[123][99]=1;}
- {$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% |
Выносите многомерные массивы из "текстовых строк" - ускорение 25-30%. Одномерные можно не выносить.
При использовании многомерных массивов в строках наблюдается заметное снижение скорости Из-за многомерности нужно заключать переменные в парные фигурные скобки.- {$x="test ".$myarray["name"]["second"][1]." test"; }
- {$x="test {$myarray[name][second][1]} test"; }
- {$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% |
- {$x="test ".$myarray[3][2][0]." test"; }
- {$x="test {$myarray[3][2][0]} test"; }
- {$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% |
- {$x="test".$myarray["test"]."test";}
- {$x="test$myarray[test]test"; }
- {$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% |
Такой же тест, но для одномерных:
- {$x="test".$myarray["test"]."test".$myarray["test"]."test".$myarray["test"]; }
- {$x="test$myarray[test]testtest$myarray[test]testtest$myarray[test]test"; }
- {$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% |
Регулярные выражения: PHP(POSIX) vs Perl.
PHP поддерживает регулярные выражения стандарта POSIX/eger*/ и PERL/preg*/-ориентированные. Кто из них работает быстрее? Хочу заранее предупредить любителей Перла, чтобы не радовались: хоть перловые реги и круче пхпышных, только ничто и никто не мешает использовать в PHP перловые реги! Наверно, потому их и встроили в PHP, что уж больно тормоза большие... :-)Итак, простейший текст. Поиск простого выражения в тексте, который состоит из многократного повторения данной статьи (получается размер переменной $text в 3 Мб).
Тест вызывает всего 1 раз, ибо реги имеют встроенное средство для кеширования результатов компиляции. Т.е. перед запуском проиходит компиляции, а повторные реги не компилируются. Это особенности регулярных выражений. Разные языки программирования в состоянии хранить разное число откомпилированных выражений, что вызывались (в порядке вызова в программе). И в данном тесте как раз нет эффекта от компиляции, т.к. функция вызывается всего один раз.
- {eregi("МаС+иВ",$text);}
- {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% |
- {eregi("(ма[a-zа-я]{1,20})",$text);}
- {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% |
- {eregi("(ма[a-zа-я]{1,20})",$text);}
- {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% |
Далее один очень показательный пример на этой же статье (увеличение до 28Мб). Пример ищет в тексте e-mail. По свойству "жадности" регулярных выражений будет найден самый большой и наболее близкий к левому краю адрес.
Этот пример огорчит любителей перла. Приятно их огорчать :-)
- {eregi("([a-z_-]+@([a-z][a-z-]*\.)+([a-z]{2}|com|mil|org|net|gov|edu|arpa|info|biz))",$text);}
- {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% |
А теперь тот же пример, но только в статье (увеличение до 28Мб) нет НИ ОДНОГО символа "@" (я специально сделал копию статьи и стер эти символы):
- {eregi("([a-z_-]+@([a-z][a-z-]*\.)+([a-z]{2}|com|mil|org|net|gov|edu|arpa|info|biz))",$text,$ok); echo $ok[1];}
- {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% |
Итак, вывод о скорости с примерами был дан выше. Вывод однозначный - надо использовать Perl-ориентированные регулярные выражения. В начеле главы я упоминал о кешировании откомпилированных копий регов. Если в программе одно и тоже выражение встречается неоднократно, производительность может отличаться не просто многократно, а в 10-100-1000 раз!
Селдующий пример вызывается 200 раз подряд над текстом в 250Кб:
- {eregi("МаС+иВ",$text);}
- {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% |
Циклы: for, foreach, while, count/sizeof() - ускорение 15%-30%
В начале программы создается массив $test из целых чисел (100 000 элементов). Потом один раз запускаются приведенные ниже примеры. Цикл проходит данный массив 3-мя способами (разными циклами) и выполняет кое-какие операции. Не выполнять в цикле ничего нельзя, ибо это будет уже совсем не реальный тест.- {$x=0; foreach($test as $n) { $x=sprintf("test%08i",$i); }}
- {$x=0; for ($it=0; $it<100000; $it++) { $x=sprintf("test%08i",$i); }}
- {$x=0; $it=0; while($it<100000) { $x=sprintf("test%08i",$i); $it++; }}
- {$x=0; for ($it=0; $it<count($test); $it++) { $x=sprintf("test%08i",$i); }}
- {$x=0; $it=0; while($it<count($test)) { $x=sprintf("test%08i",$i); $it++; }}
- {$x=0; $co=count($test); for ($it=0; $it<$co; $it++) { $x=sprintf("test%08i",$i); }}
- {$x=0; $co=count($test); $it=0; while($it<$co) { $x=sprintf("test%08i",$i); $it++; }}
счетчик | кол-во вызовов |
общее вpемя |
сpеднее вpемя |
% от min | % от max | общее время |
||
test N1 | 1 | 12.0313 | 12.0313 | 154.4% | 100.0% | |||
test N2 | 1 | 4.7290 | 4.7290 | 00.0% | 39.3% | |||
test N3 | 1 | 4.7712 | 4.7712 | 00.9% | 39.7% | |||
test N4 | 1 | 10.2847 | 10.2847 | 117.5% | 85.5% | |||
test N5 | 1 | 10.3466 | 10.3466 | 118.8% | 86.0% | |||
test N6 | 1 | 9.1271 | 9.1271 | 93.0% | 75.9% | |||
test N7 | 1 | 9.1409 | 9.1409 | 93.3% | 76.0% |
Теперь о деле. Бесспорный вывод - использование foreach сильно тормозит дело, а между for и while большой разницы нет. (На голом тесте
Вывод с
Вывод о не ассоциативных массивах: 1) foreach существенно замедляет работу 2) использование
Сравнение count() и sizeof().
Судя по мануалу - это алиасы. Об этом написано на страницах самих функций и дополнительной странице "Appendex => Aliases list". Что же мы видим на массиве в 100000 элементов:
- {$x=0; for ($it=0; $it<count($test); $it++) { $x=sprintf("test%08i",$test[$it]);}}
- {$x=0; for ($it=0; $it<sizeof($test); $it++) { $x=sprintf("test%08i",$test[$it]);}}
счетчик | кол-во вызовов |
общее вpемя |
сpеднее вpемя |
% от min | % от max | общее время |
||
test N1 | 1 | 3.0087 | 3.0087 | 15.7% | 100.0% | |||
test N2 | 1 | 2.5998 | 2.5998 | 00.0% | 86.4% |
Если кол-во элементов в массиве меньше 65000 (64К), то эти функции по скорости практически не различимы. Тут вывод простой - переходим на использование
Ассоциативные массивы: тестирование разных способов перебора
С ними наблюдается таже проблема: на разных по величине массивах разные функции эффективны, но лучше всех foreach!
Массив в 200 элементов и 1000 повторов программы:
- {$x=0; foreach($test as $k=>$v) { $x=sprintf("%s=>%s\n",$k,$v); }}
- {$x=0; reset($test); while (list($k, $v) = each($test)) { $x=sprintf("%s=>%s\n",$k,$v); }}
- {$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]]); }}
- {$x=0; reset($test); while ($k=key($test)) { $x=sprintf("%s=>%s\n",$k,current($test)); next($test); }}
счетчик | кол-во вызовов |
общее вpемя |
сpеднее вpемя |
% от min | % от max | общее время |
||
test N1 | 1 | 8.1222 | 8.1222 | 00.0% | 78.7% | |||
test N2 | 1 | 10.3221 | 10.3221 | 27.1% | 100.0% | |||
test N3 | 1 | 9.7921 | 9.7921 | 20.6% | 94.9% | |||
test N4 | 1 | 8.9711 | 8.9711 | 10.5% | 86.9% |
счетчик | кол-во вызовов |
общее вpемя |
сpеднее вpемя |
% от min | % от max | общее время |
||
test N1 | 1 | 14.4473 | 14.4473 | 00.0% | 67.2% | |||
test N2 | 1 | 18.6801 | 18.6801 | 29.3% | 86.9% | |||
test N3 | 1 | 21.5056 | 21.5056 | 48.9% | 100.0% | |||
test N4 | 1 | 15.8514 | 15.8514 | 09.7% | 73.7% |
счетчик | кол-во вызовов |
общее вpемя |
сpеднее вpемя |
% от min | % от max | общее время |
||
test N1 | 1 | 3.5116 | 3.5116 | 00.0% | 82.8% | |||
test N2 | 1 | 3.9724 | 3.9724 | 13.1% | 93.6% | |||
test N3 | 1 | 4.2436 | 4.2436 | 20.8% | 100.0% | |||
test N4 | 1 | 4.0026 | 4.0026 | 14.0% | 94.3% |
Резюме:
- sizeof() лучше, чем count()
- в циклах sizeof лучше вообще заменить на переменную
- for и while практически не отличимы
- для перебора простых индексных массивов нужно использовать for или while
- для перебора ассоциативных массивов нужно использотьва foreach
Для чтения файла file() быстрее, чем fopen+цикл - ускорение 40%
Чтобы прочитать в массив $x файл размером 1Мб (100 000 строк по 10 байт) можно воспользоваться двумя вариантами: чтение файла с помощью file(), либо традиционным методом fopen/fgets. Разумеется, для файлов разного объема и содержимого скорость может меняться. Но в данном примере статистика такова:$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); |
0 коментарі:
Отправить комментарий