Intereting Posts
Можно ли изменить ширину табуляции (\ t) в строке PHP? Как заставить класс загрузки файлов CodeIgniter принимать все расширения? Magento – OnePage Checkout – Скрыть Способ оплаты в зависимости от метода доставки PHP проверяет, существует ли параметр url Почему в моем корневом каталоге не работает ../ (dot dot slash)? Закрытие тегов оператора PHP arrow Пагинальные записи на стороне клиента PHP Получить минимальные и максимальные значения в двумерном ассоциативном массиве Codeigniter Класс Наследование между модулями (wiredesigns) Как найти среднее значение различий последних N чтений столбца PHP MySQL Как настроить заголовок ответа HTTP, когда php используется с g-wan Ошибка сбора формы Попытка вызвать неопределенный метод с именем «getRequest» класса. Отправить по электронной почте в SwiftMailer Symfony Код для отправки электронной почты не работает PHP Дата Экспорт в формат CSV

Конкатенация строк php, производительность

В таких языках, как Java и C #, строки неизменяемы, и их можно вычислительно дорого строить по одному символу за раз. На указанных языках существуют классы библиотек для снижения таких затрат, как C # System.Text.StringBuilder и Java java.lang.StringBuilder .

Является ли php (4 или 5, я заинтересован в обоих) поделиться этим ограничением? Если да, существуют ли подобные решения проблемы?

Нет, в PHP нет класса строковых построек, поскольку строки изменяемы.

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

Например, echo будет принимать токены с разделителями-запятыми для вывода.

 // This... echo 'one', 'two'; // Is the same as this echo 'one'; echo 'two'; 

Это означает, что вы можете выводить сложную строку без фактического использования конкатенации, которая будет медленнее

 // This... echo 'one', 'two'; // Is faster than this... echo 'one' . 'two'; 

Если вам нужно записать этот вывод в переменную, вы можете сделать это с помощью функций буферизации вывода .

Кроме того, производительность массива PHP действительно хороша. Если вы хотите сделать что-то вроде списка значений, разделенных запятыми, просто используйте implode ()

 $values = array( 'one', 'two', 'three' ); $valueList = implode( ', ', $values ); 

Наконец, убедитесь, что вы знакомы с строковым типом PHP, и это разные разделители и последствия каждого из них.

Мне было любопытно, поэтому я проверил тест. Я использовал следующий код:

 <?php ini_set('memory_limit', '1024M'); define ('CORE_PATH', '/Users/foo'); define ('DS', DIRECTORY_SEPARATOR); $numtests = 1000000; function test1($numtests) { $CORE_PATH = '/Users/foo'; $DS = DIRECTORY_SEPARATOR; $a = array(); $startmem = memory_get_usage(); $a_start = microtime(true); for ($i = 0; $i < $numtests; $i++) { $a[] = sprintf('%s%sDesktop%sjunk.php', $CORE_PATH, $DS, $DS); } $a_end = microtime(true); $a_mem = memory_get_usage(); $timeused = $a_end - $a_start; $memused = $a_mem - $startmem; echo "TEST 1: sprintf()\n"; echo "TIME: {$timeused}\nMEMORY: $memused\n\n\n"; } function test2($numtests) { $CORE_PATH = '/Users/shigh'; $DS = DIRECTORY_SEPARATOR; $a = array(); $startmem = memory_get_usage(); $a_start = microtime(true); for ($i = 0; $i < $numtests; $i++) { $a[] = $CORE_PATH . $DS . 'Desktop' . $DS . 'junk.php'; } $a_end = microtime(true); $a_mem = memory_get_usage(); $timeused = $a_end - $a_start; $memused = $a_mem - $startmem; echo "TEST 2: Concatenation\n"; echo "TIME: {$timeused}\nMEMORY: $memused\n\n\n"; } function test3($numtests) { $CORE_PATH = '/Users/shigh'; $DS = DIRECTORY_SEPARATOR; $a = array(); $startmem = memory_get_usage(); $a_start = microtime(true); for ($i = 0; $i < $numtests; $i++) { ob_start(); echo $CORE_PATH,$DS,'Desktop',$DS,'junk.php'; $aa = ob_get_contents(); ob_end_clean(); $a[] = $aa; } $a_end = microtime(true); $a_mem = memory_get_usage(); $timeused = $a_end - $a_start; $memused = $a_mem - $startmem; echo "TEST 3: Buffering Method\n"; echo "TIME: {$timeused}\nMEMORY: $memused\n\n\n"; } function test4($numtests) { $CORE_PATH = '/Users/shigh'; $DS = DIRECTORY_SEPARATOR; $a = array(); $startmem = memory_get_usage(); $a_start = microtime(true); for ($i = 0; $i < $numtests; $i++) { $a[] = "{$CORE_PATH}{$DS}Desktop{$DS}junk.php"; } $a_end = microtime(true); $a_mem = memory_get_usage(); $timeused = $a_end - $a_start; $memused = $a_mem - $startmem; echo "TEST 4: Braced in-line variables\n"; echo "TIME: {$timeused}\nMEMORY: $memused\n\n\n"; } function test5($numtests) { $a = array(); $startmem = memory_get_usage(); $a_start = microtime(true); for ($i = 0; $i < $numtests; $i++) { $CORE_PATH = CORE_PATH; $DS = DIRECTORY_SEPARATOR; $a[] = "{$CORE_PATH}{$DS}Desktop{$DS}junk.php"; } $a_end = microtime(true); $a_mem = memory_get_usage(); $timeused = $a_end - $a_start; $memused = $a_mem - $startmem; echo "TEST 5: Braced inline variables with loop-level assignments\n"; echo "TIME: {$timeused}\nMEMORY: $memused\n\n\n"; } test1($numtests); test2($numtests); test3($numtests); test4($numtests); test5($numtests); 

… И получил следующие результаты. Изображение прилагается. Очевидно, что sprintf – наименее эффективный способ сделать это, как с точки зрения времени, так и потребления памяти. EDIT: просмотр изображения на другой вкладке, если у вас нет зрелища орла. введите описание изображения здесь

Когда вы проводите сопоставление по времени, различия настолько малы, что это не очень важно. Это сделало бы еще больше, потому что для выбора, который делает ваш код более легким для чтения и понимания.

Аналогом StringBuilder в PHP не нужен.

Я сделал пару простых тестов:

в PHP:

 $iterations = 10000; $stringToAppend = 'TESTSTR'; $timer = new Timer(); // based on microtime() $s = ''; for($i = 0; $i < $iterations; $i++) { $s .= ($i . $stringToAppend); } $timer->VarDumpCurrentTimerValue(); $timer->Restart(); // Used purlogic's implementation. // I tried other implementations, but they are not faster $sb = new StringBuilder(); for($i = 0; $i < $iterations; $i++) { $sb->append($i); $sb->append($stringToAppend); } $ss = $sb->toString(); $timer->VarDumpCurrentTimerValue(); 

в C # (.NET 4.0):

 const int iterations = 10000; const string stringToAppend = "TESTSTR"; string s = ""; var timer = new Timer(); // based on StopWatch for(int i = 0; i < iterations; i++) { s += (i + stringToAppend); } timer.ShowCurrentTimerValue(); timer.Restart(); var sb = new StringBuilder(); for(int i = 0; i < iterations; i++) { sb.Append(i); sb.Append(stringToAppend); } string ss = sb.ToString(); timer.ShowCurrentTimerValue(); 

Результаты:

10000 итераций:
1) PHP, обычное конкатенация: ~ 6 мс
2) PHP, используя StringBuilder: ~ 5 мс
3) C #, обычная конкатенация: ~ 520 мс
4) C #, используя StringBuilder: ~ 1ms

100000 итераций:
1) PHP, обычная конкатенация: ~ 63 мс
2) PHP, используя StringBuilder: ~ 555ms
3) C #, обычная конкатенация: ~ 91000ms // !!!
4) C #, используя StringBuilder: ~ 17ms

Я знаю, о чем ты говоришь. Я просто создал этот простой класс для эмуляции класса Java StringBuilder.

 class StringBuilder { private $str = array(); public function __construct() { } public function append($str) { $this->str[] = $str; } public function toString() { return implode($this->str); } } 

Строки PHP изменяемы. Вы можете изменить определенные символы следующим образом:

 $string = 'abc'; $string[2] = 'a'; // $string equals 'aba' $string[3] = 'd'; // $string equals 'abad' $string[5] = 'e'; // $string equals 'abad e' (fills character(s) in between with spaces) 

И вы можете добавить символы в строку следующим образом:

 $string .= 'a'; 

Да. Они делают. Например, если вы хотите повторить пару строк вместе, используйте

 echo str1, str2, str3 

вместо

 echo str1.str2.str3 

чтобы получить его немного быстрее.

Во-первых, если вам не нужны строки для конкатенации, не делайте этого: всегда будет быстрее сделать

 echo $a,$b,$c; 

чем

 echo $a . $b . $c; 

Однако, по крайней мере, в PHP5 конкатенация строк происходит довольно быстро, особенно если есть только одна ссылка на заданную строку. Я предполагаю, что интерпретатор использует метод StringBuilder внутри.

Я написал код в конце этого сообщения, чтобы проверить различные формы конкатенации строк, и они на самом деле почти точно равны как по памяти, так и по времени.

Два основных метода, которые я использовал, представляют собой конкатенирование строк друг на друга и заполнение массива строками, а затем их развязывание. Я сделал 500 добавлений строк со строкой 1 МБ в php 5.6 (так что результат – строка 500 МБ). На каждой итерации теста все память и временные следы были очень близкими (в ~ $ IterationNumber * 1MB). Время выполнения обоих тестов составляло 50,398 секунды и 50,843 секунды, что наиболее вероятно в допустимых пределах ошибки.

Сбор мусора строк, которые больше не ссылаются, кажется довольно быстрым, даже не выходя из сферы действия. Поскольку строки изменяемы, после факта не требуется дополнительной памяти.

ОДНАКО , следующие тесты показали, что в пиковой памяти используется другое значение WHILE, когда строки конкатенируются.

 $OneMB=str_repeat('x', 1024*1024); $Final=$OneMB.$OneMB.$OneMB.$OneMB.$OneMB; print memory_get_peak_usage(); 

Результат = 10 806 800 байтов (~ 10 МБ без начального объема памяти PHP)

 $OneMB=str_repeat('x', 1024*1024); $Final=implode('', Array($OneMB, $OneMB, $OneMB, $OneMB, $OneMB)); print memory_get_peak_usage(); 

Результат = 6,613,320 байт (~ 6 МБ без первоначального объема памяти PHP)

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

Но даже этот факт является спорным в зависимости от данных. Например, объединение 1 символа в строку, чтобы получить 50 миллионов байт (так 50 миллионов итераций), взяло максимум 50 322 512 байт (~ 48 МБ) за 5,97 секунды. В то время как метод массива закончил использование 7 337 107 717 байт (~ 6.8 ГБ) для создания массива за 12,1 секунды, а затем потребовалось дополнительные 4,32 секунды для объединения строк из массива.

Anywho … ниже приведен сравнительный код, который я упомянул в начале, который показывает, что методы в значительной степени равны. Он выводит довольно HTML-таблицу.

 <? //Please note, for the recursion test to go beyond 256, xdebug.max_nesting_level needs to be raised. You also may need to update your memory_limit depending on the number of iterations //Output the start memory print 'Start: '.memory_get_usage()."B<br><br>Below test results are in MB<br>"; //Our 1MB string global $OneMB, $NumIterations; $OneMB=str_repeat('x', 1024*1024); $NumIterations=500; //Run the tests $ConcatTest=RunTest('ConcatTest'); $ImplodeTest=RunTest('ImplodeTest'); $RecurseTest=RunTest('RecurseTest'); //Output the results in a table OutputResults( Array('ConcatTest', 'ImplodeTest', 'RecurseTest'), Array($ConcatTest, $ImplodeTest, $RecurseTest) ); //Start a test run by initializing the array that will hold the results and manipulating those results after the test is complete function RunTest($TestName) { $CurrentTestNums=Array(); $TestStartMem=memory_get_usage(); $StartTime=microtime(true); RunTestReal($TestName, $CurrentTestNums, $StrLen); $CurrentTestNums[]=memory_get_usage(); //Subtract $TestStartMem from all other numbers foreach($CurrentTestNums as &$Num) $Num-=$TestStartMem; unset($Num); $CurrentTestNums[]=$StrLen; $CurrentTestNums[]=microtime(true)-$StartTime; return $CurrentTestNums; } //Initialize the test and store the memory allocated at the end of the test, with the result function RunTestReal($TestName, &$CurrentTestNums, &$StrLen) { $R=$TestName($CurrentTestNums); $CurrentTestNums[]=memory_get_usage(); $StrLen=strlen($R); } //Concatenate 1MB string over and over onto a single string function ConcatTest(&$CurrentTestNums) { global $OneMB, $NumIterations; $Result=''; for($i=0;$i<$NumIterations;$i++) { $Result.=$OneMB; $CurrentTestNums[]=memory_get_usage(); } return $Result; } //Create an array of 1MB strings and then join w/ an implode function ImplodeTest(&$CurrentTestNums) { global $OneMB, $NumIterations; $Result=Array(); for($i=0;$i<$NumIterations;$i++) { $Result[]=$OneMB; $CurrentTestNums[]=memory_get_usage(); } return implode('', $Result); } //Recursively add strings onto each other function RecurseTest(&$CurrentTestNums, $TestNum=0) { Global $OneMB, $NumIterations; if($TestNum==$NumIterations) return ''; $NewStr=RecurseTest($CurrentTestNums, $TestNum+1).$OneMB; $CurrentTestNums[]=memory_get_usage(); return $NewStr; } //Output the results in a table function OutputResults($TestNames, $TestResults) { global $NumIterations; print '<table border=1 cellspacing=0 cellpadding=2><tr><th>Test Name</th><th>'.implode('</th><th>', $TestNames).'</th></tr>'; $FinalNames=Array('Final Result', 'Clean'); for($i=0;$i<$NumIterations+2;$i++) { $TestName=($i<$NumIterations ? $i : $FinalNames[$i-$NumIterations]); print "<tr><th>$TestName</th>"; foreach($TestResults as $TR) printf('<td>%07.4f</td>', $TR[$i]/1024/1024); print '</tr>'; } //Other result numbers print '<tr><th>Final String Size</th>'; foreach($TestResults as $TR) printf('<td>%d</td>', $TR[$NumIterations+2]); print '</tr><tr><th>Runtime</th>'; foreach($TestResults as $TR) printf('<td>%s</td>', $TR[$NumIterations+3]); print '</tr></table>'; } ?> с <? //Please note, for the recursion test to go beyond 256, xdebug.max_nesting_level needs to be raised. You also may need to update your memory_limit depending on the number of iterations //Output the start memory print 'Start: '.memory_get_usage()."B<br><br>Below test results are in MB<br>"; //Our 1MB string global $OneMB, $NumIterations; $OneMB=str_repeat('x', 1024*1024); $NumIterations=500; //Run the tests $ConcatTest=RunTest('ConcatTest'); $ImplodeTest=RunTest('ImplodeTest'); $RecurseTest=RunTest('RecurseTest'); //Output the results in a table OutputResults( Array('ConcatTest', 'ImplodeTest', 'RecurseTest'), Array($ConcatTest, $ImplodeTest, $RecurseTest) ); //Start a test run by initializing the array that will hold the results and manipulating those results after the test is complete function RunTest($TestName) { $CurrentTestNums=Array(); $TestStartMem=memory_get_usage(); $StartTime=microtime(true); RunTestReal($TestName, $CurrentTestNums, $StrLen); $CurrentTestNums[]=memory_get_usage(); //Subtract $TestStartMem from all other numbers foreach($CurrentTestNums as &$Num) $Num-=$TestStartMem; unset($Num); $CurrentTestNums[]=$StrLen; $CurrentTestNums[]=microtime(true)-$StartTime; return $CurrentTestNums; } //Initialize the test and store the memory allocated at the end of the test, with the result function RunTestReal($TestName, &$CurrentTestNums, &$StrLen) { $R=$TestName($CurrentTestNums); $CurrentTestNums[]=memory_get_usage(); $StrLen=strlen($R); } //Concatenate 1MB string over and over onto a single string function ConcatTest(&$CurrentTestNums) { global $OneMB, $NumIterations; $Result=''; for($i=0;$i<$NumIterations;$i++) { $Result.=$OneMB; $CurrentTestNums[]=memory_get_usage(); } return $Result; } //Create an array of 1MB strings and then join w/ an implode function ImplodeTest(&$CurrentTestNums) { global $OneMB, $NumIterations; $Result=Array(); for($i=0;$i<$NumIterations;$i++) { $Result[]=$OneMB; $CurrentTestNums[]=memory_get_usage(); } return implode('', $Result); } //Recursively add strings onto each other function RecurseTest(&$CurrentTestNums, $TestNum=0) { Global $OneMB, $NumIterations; if($TestNum==$NumIterations) return ''; $NewStr=RecurseTest($CurrentTestNums, $TestNum+1).$OneMB; $CurrentTestNums[]=memory_get_usage(); return $NewStr; } //Output the results in a table function OutputResults($TestNames, $TestResults) { global $NumIterations; print '<table border=1 cellspacing=0 cellpadding=2><tr><th>Test Name</th><th>'.implode('</th><th>', $TestNames).'</th></tr>'; $FinalNames=Array('Final Result', 'Clean'); for($i=0;$i<$NumIterations+2;$i++) { $TestName=($i<$NumIterations ? $i : $FinalNames[$i-$NumIterations]); print "<tr><th>$TestName</th>"; foreach($TestResults as $TR) printf('<td>%07.4f</td>', $TR[$i]/1024/1024); print '</tr>'; } //Other result numbers print '<tr><th>Final String Size</th>'; foreach($TestResults as $TR) printf('<td>%d</td>', $TR[$NumIterations+2]); print '</tr><tr><th>Runtime</th>'; foreach($TestResults as $TR) printf('<td>%s</td>', $TR[$NumIterations+3]); print '</tr></table>'; } ?> 

Если вы размещаете значения переменных внутри строк PHP, я понимаю, что немного быстрее использовать встроенную переменную включения (это не официальное имя – я не помню, что есть)

 $aString = 'oranges'; $compareString = "comparing apples to {$aString}!"; echo $compareString comparing apples to oranges! 

Должно быть внутри двойных кавычек для работы. Также работает для элементов массива (т.е.

 echo "You requested page id {$_POST['id']}"; 

)

никакое такое ограничение в php, php не может конкатенировать strng с помощью точечного (.) оператора

 $a="hello "; $b="world"; echo $a.$b; 

выходы «привет мир»