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



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



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

835   11  

11 ответов:

нет, в PHP нет типа класса stringbuilder, так как строки изменчивы.

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

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';

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

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

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

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

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

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 секунды последовательно, которые, скорее всего, находятся в пределах допустимых пределов погрешности.

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

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

$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 секунд. При выполнении метода array было использовано 7,337,107,176 байт (~6,8 ГБ) для создания массива за 12,1 секунды, а затем потребовалось еще 4,32 секунды для объединения строк из массива.

кто угодно... ниже приведен эталон код, который я упомянул в начале, который показывает, что методы в значительной степени равны. Он выводит довольно 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>';
}
?>

Если вы размещаете значения переменных в строках 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;

выводит "hello world"

Comments

    Ничего не найдено.