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

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

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

Ответ 1

Нет, в 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, а также разными разделителями и значениями каждого из них.

Ответ 2

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

<?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

Ответ 3

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

Ответ 4

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

class StringBuilder {

  private $str = array();

  public function __construct() { }

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

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

}

Ответ 5

Аналог 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 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();

в С# (.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, обычное конкатенация: ~ 6ms
2) PHP, используя StringBuilder: ~ 5 мс
3) С#, обычное конкатенация: ~ 520 мс
4) С#, используя StringBuilder: ~ 1мс

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

Ответ 6

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

Ответ 7

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

echo str1,str2,str3 

вместо

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

Ответ 8

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

echo $a,$b,$c;

чем

echo $a . $b . $c;

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

Ответ 9

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

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

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

HOWEVER. Следующие тесты показали, что в пиковой памяти используется другое значение 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,176 байт (~ 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>';
}
?>

Ответ 10

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

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

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

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

)

Ответ 11

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

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

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