php String Concatenation, Performance


63

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

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

55

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

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

эхо, например, будет принимать токены, разделенные запятыми для вывода.

// This... 
echo 'one', 'two'; 

// Is the same as this 
echo 'one'; 
echo 'two'; 

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

// This... 
echo 'one', 'two'; 

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

Если вам нужно, чтобы захватить этот вывод в переменную, вы можете сделать это с output buffering functions.

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

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

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

+21

И используйте одинарные кавычки, когда это возможно. 29 дек. 102010-12-29 23:15:31

+1

Почему не двойные кавычки? 05 окт. 132013-10-05 15:52:28

+4

@gekannt Поскольку PHP расширяет/интерпретирует переменные, а также дополнительные escape-последовательности в строках, заключенные в двойные кавычки. Например, '$ x = 5; echo "x = $ x"; 'будет печатать' x = 5', а '$ x = 5; echo 'x = $ x'; 'будет печатать' x = $ x'. 10 окт. 132013-10-10 19:37:44

  0

ему может понадобиться его расширить, а также не расширять/интерпретировать, это зависит от ситуации. 11 окт. 132013-10-11 18:31:57

+12

Немного мифа, одна цитата: http://nikic.github.io/2012/01/09/ Disproving-the-Single-Quotes-Performance-Myth.html 03 апр. 142014-04-03 11:48:50

  0

Хорошая информация, @alimack, но для записи этот ответ не касается одиночных или двойных кавычек, а также о конкатенации и интерполяции. Речь идет об использовании 'echo' с параметризованными токенами против конкатенированных токенов. 05 апр. 142014-04-05 22:11:16

  0

Всегда используйте эхо с точкой и двойные кавычки, где он повышает читаемость кода. Такие оптимизации злые 11 окт. 172017-10-11 13:12:20


-4

нет такого ограничения в PHP, PHP может конкатенацию strng с точкой (.) Оператором

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

выходов "привет мир"

+1

Это не то, что вопрос спрашивает .... 23 сен. 082008-09-23 21:45:08

+4

людей здесь быстро реагирующий .. я печатал в .. случайно ударил вкладку темно затем введите .. 23 сен. 082008-09-23 21:45:51


2

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

 
echo str1,str2,str3

вместо

 
echo str1.str2.str3
, чтобы получить его немного быстрее.

  0

ли эта функция работает таким образом? $ newstring = str1.srt2.str3; echo $ newstring; 25 ноя. 082008-11-25 17:06:09


5

Строки 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'; 
  0

Я не эксперт по php. «$ String. = 'A» не является короткой формой «$ string = $ string.» A? »И php не создает новую строку (и не меняет старый)? 18 фев. 132013-02-18 09:02:30

  0

Да, это короткая форма. Но, к вашему второму вопросу, внутреннее поведение PHP таково, что он действительно напоминает замену строки на более длинный байт. Внутри, однако, он выполняет буферизацию, как StringBuilder. 24 мар. 172017-03-24 22:26:48


1

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

echo $a,$b,$c; 

чем

echo $a . $b . $c; 

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


0

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

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

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

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

)


11

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

+2

Действительно, беспокоиться об этом просто откровенно глупо, когда, как правило, гораздо важнее беспокоиться о проблемах, таких как дизайн базы данных, большой анализ O() и правильное профилирование. 24 сен. 082008-09-24 04:18:13

+2

Это очень верно, но я видел ситуации в Java и C#, где использование изменяемого строкового класса (vs s + = "blah") действительно значительно увеличило производительность. 29 сен. 102010-09-29 00:46:35


10

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

class StringBuilder { 

    private $str = array(); 

    public function __construct() { } 

    public function append($str) { 
    $this->str[] = $str; 
    } 

    public function toString() { 
    return implode($this->str); 
    } 

} 
+9

Хорошее решение. В конце функции 'append' вы можете добавить' return $ this; ', чтобы разрешить цепочку методов:' $ sb-> append ("one") -> append ("two"); '. 03 дек. 102010-12-03 21:20:57

+6

Это совершенно не нужно в PHP. На самом деле, я готов поспорить, что это значительно медленнее, чем обычная конкатенация. 27 апр. 112011-04-27 14:55:39

+8

ryeguy: true, поскольку эти строки изменяются в PHP, этот метод «лишний», человек попросил аналогичную реализацию для StringBuilder Java, поэтому здесь вы идете ... Я бы не сказал, что это «значительно» медленнее, я думаю вы немного драматичны. Накладные расходы на создание экземпляра класса, который управляет построением строки, могут включать в себя затраты, но полезность класса StringBuilder можно расширить, включив дополнительные методы в строку. Я рассмотрю, какие дополнительные накладные расходы реализованы путем реализации чего-то подобного в классе и попытайтесь отправить назад. 11 май. 112011-05-11 03:22:48

+5

... и его больше никогда не слышали. 07 ноя. 132013-11-07 15:59:52


10

Аналог 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) РНР, обычный конкатенации: ~ 6ms
2) РНР, используя StringBuilder: ~ 5 мс
3) C#, обычный конкатенации: ~ 520ms
4) C#, используя StringBuilder: ~ 1 мс

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

  0

Java более или менее похож на C# в этом. Хотя более поздние версии сделали некоторую оптимизацию во время компиляции, чтобы облегчить это. Раньше это было (в 1.4 и более ранних версиях, возможно, даже в версии 1.6), что если у вас есть 3 или более элемента для конкатенации, вам лучше использовать StringBuffer/Builder. Хотя в цикле вам все равно нужно использовать StringBuilder. 11 янв. 142014-01-11 09:04:33

  0

Другими словами, PHP был разработан для людей, которые не хотят беспокоиться о соображениях низкого уровня, и они строят буферизацию внутри внутри типа строки. Это не связано с тем, что строки «изменяются» на PHP; для увеличения длины строки по-прежнему требуется копия памяти для большей части памяти, если вы не поддерживаете буфер для ее роста. 24 мар. 172017-03-24 22:25:21

  0

Кстати, это должен быть принятый ответ. Нынешние верхние ответы даже не отвечают на вопрос. 24 мар. 172017-03-24 22:30:49


23

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

<?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: просмотр изображения на другой вкладке, если у вас нет орлиного зрения. enter image description here

+1

должно иметь еще 1 тест: похоже на 'test2', но заменить' .' '', '(без выходного буфера, конечно) 21 ноя. 132013-11-21 07:26:05

+1

Очень полезно, спасибо. Кажется, что конкатенация строк - это путь. Имеет смысл, что они попытаются оптимизировать ад из этого. 18 сен. 142014-09-18 19:03:03


1

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

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

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

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

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

Результат = 10,806,800 байт (~ 10MB ж/о начальном след РНР памяти)

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

Результат = 6,613,320 байт (~ 6MB ж/о начальном след РНР памяти)

Таким образом, на самом деле существует разница, которая может быть существенной при очень больших конкатенациях строк по памяти (я столкнулся с такими примерами при создании очень больших наборов данных или 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>'; 
} 
?>