php String-Verkettung, Leistung

Lesezeit: 12 Minuten

In Sprachen wie Java und C# sind Strings unveränderlich und es kann rechenintensiv sein, einen String zeichenweise zu erstellen. In diesen Sprachen gibt es Bibliotheksklassen, um diese Kosten zu reduzieren, wie z. B. C# System.Text.StringBuilder und Java java.lang.StringBuilder.

Teilt PHP (4 oder 5; ich interessiere mich für beide) diese Einschränkung? Wenn ja, gibt es ähnliche Lösungen für das Problem?

Nein, es gibt keine Art von Stringbuilder-Klasse in PHP, da Strings änderbar sind.

Abgesehen davon gibt es verschiedene Möglichkeiten, eine Saite aufzubauen, je nachdem, was Sie tun.

echo akzeptiert beispielsweise durch Kommas getrennte Tokens für die Ausgabe.

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

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

Das bedeutet, dass Sie eine komplexe Zeichenfolge ausgeben können, ohne tatsächlich eine Verkettung zu verwenden, die langsamer wäre

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

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

Wenn Sie diese Ausgabe in einer Variablen erfassen müssen, können Sie dies mit dem tun Ausgangspufferfunktionen.

Außerdem ist die Array-Leistung von PHP wirklich gut. Wenn Sie so etwas wie eine durch Kommas getrennte Liste von Werten erstellen möchten, verwenden Sie einfach implode()

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

Stellen Sie sicher, dass Sie sich schließlich damit vertraut machen Der String-Typ von PHP und es sind verschiedene Trennzeichen und die Auswirkungen der einzelnen.

  • Und verwenden Sie nach Möglichkeit einfache Anführungszeichen.

    – Stefan

    29. Dezember 2010 um 23:15 Uhr

  • warum keine doppelten Anführungszeichen?

    – Tebe

    5. Oktober 2013 um 15:52 Uhr

  • @gekannt Weil PHP Variablen sowie zusätzliche Escape-Sequenzen in Strings erweitert/interpretiert, die in doppelte Anführungszeichen eingeschlossen sind. Zum Beispiel, $x = 5; echo "x = $x"; drucken würde x = 5 während $x = 5; echo 'x = $x'; drucken würde x = $x.

    – samitny

    10. Oktober 2013 um 19:37 Uhr

  • man kann es erweitern oder nicht erweitern/interpretieren, es hängt von der Situation ab

    – Tebe

    11. Oktober 2013 um 18:31 Uhr


  • Ein bisschen ein Mythos, die Sache mit den einfachen Anführungszeichen: nikic.github.io/2012/01/09/…

    – Alimack

    3. April 2014 um 11:48 Uhr

Das hat mich neugierig gemacht, also habe ich einen Test gemacht. Ich habe folgenden Code verwendet:

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

… und bekam folgende Ergebnisse. Bild anbei. Sprintf ist eindeutig der am wenigsten effiziente Weg, sowohl in Bezug auf die Zeit als auch auf den Speicherverbrauch. BEARBEITEN: Bild in einem anderen Tab anzeigen, es sei denn, Sie haben Adlerblick.
Geben Sie hier die Bildbeschreibung ein

  • sollte noch 1 Test haben: ähnlich test2 aber ersetzen . mit , (natürlich ohne Ausgangspuffer)

    – Raubvogel

    21. November 2013 um 7:26 Uhr


  • Sehr nützlich, danke. String-Verkettung scheint der richtige Weg zu sein. Es macht Sinn, dass sie versuchen würden, die Hölle daraus zu optimieren.

    – Chris Middleton

    18. September 2014 um 19:03 Uhr

StringBuilder analog wird in PHP nicht benötigt.

Ich habe ein paar einfache Tests gemacht:

bei 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();

in 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();

Ergebnisse:

10000 Iterationen:
1) PHP, gewöhnliche Verkettung: ~6ms
2) PHP mit StringBuilder: ~5 ms
3) C#, gewöhnliche Verkettung: ~520 ms
4) C#, mit StringBuilder: ~1ms

100000 Iterationen:
1) PHP, gewöhnliche Verkettung: ~63ms
2) PHP mit StringBuilder: ~555 ms
3) C#, gewöhnliche Verkettung: ~91000ms // !!!
4) C#, mit StringBuilder: ~17ms

  • Java ist in dieser Hinsicht mehr oder weniger dasselbe wie C#. Obwohl die späteren Versionen zur Kompilierzeit einige Optimierungen vorgenommen haben, um dies zu verringern. Früher war es so (in 1.4 und früher, vielleicht sogar in 1.6), dass Sie, wenn Sie 3 oder mehr Elemente zu verketten haben, besser dran waren, einen StringBuffer/Builder zu verwenden. Obwohl in einer Schleife, müssen Sie immer noch den StringBuilder verwenden.

    – A.Grandt

    11. Januar 2014 um 9:04 Uhr

  • Mit anderen Worten, PHP wurde für Leute entwickelt, die sich keine Gedanken über Low-Level-Überlegungen machen wollen, und es führt eine interne Zeichenfolgenpufferung für den Zeichenfolgentyp durch. Das hat nichts damit zu tun, dass Strings in PHP “änderbar” sind; Das Verlängern einer Zeichenfolge erfordert immer noch eine Speicherkopie in einen größeren Speicherbereich, es sei denn, Sie unterhalten einen Puffer, in den sie hineinwachsen kann.

    – Thomas Rutter

    24. März 2017 um 22:25 Uhr


  • Übrigens sollte dies die akzeptierte Antwort sein. Die aktuellen Top-Antworten beantworten die Frage nicht einmal wirklich.

    – Thomas Rutter

    24. März 2017 um 22:30 Uhr

Wenn Sie einen Zeitvergleich durchführen, sind die Unterschiede so gering, dass es nicht sehr relevant ist. Es würde mehr bringen, sich für die Wahl zu entscheiden, die Ihren Code leichter lesbar und verständlich macht.

php String Verkettung Leistung
ossis

Ich weiß, wovon du sprichst. Ich habe gerade diese einfache Klasse erstellt, um die Java StringBuilder-Klasse zu emulieren.

class StringBuilder {

  private $str = array();

  public function __construct() { }

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

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

}

  • Schöne Lösung. Am Ende von append Funktion, die Sie hinzufügen können return $this; Methodenverkettung zulassen: $sb->append("one")->append("two");.

    – Jabba

    3. Dezember 2010 um 21:20 Uhr

  • Dies ist in PHP völlig unnötig. Tatsächlich bin ich bereit zu wetten, dass dies erheblich langsamer ist als eine normale Verkettung.

    – ryeguy

    27. April 2011 um 14:55 Uhr


  • ryeguy: stimmt, da Strings in PHP veränderbar sind, ist diese Methode “unnötig”, die Person bat um eine ähnliche Implementierung wie Javas StringBuilder, also los geht’s … Ich würde nicht sagen, dass es “deutlich” langsamer ist, denke ich dir sind ein wenig dramatisch. Der Aufwand für die Instanziierung einer Klasse, die die Zeichenfolgenerstellung verwaltet, kann Kosten beinhalten, aber die Nützlichkeit der StringBuilder-Klasse kann erweitert werden, um zusätzliche Methoden für die Zeichenfolge einzubeziehen. Ich werde untersuchen, welcher zusätzliche Overhead realisiert wird, wenn so etwas in einer Klasse implementiert wird, und versuchen, zurück zu posten.

    – ossis

    11. Mai 2011 um 3:22 Uhr

  • … und er wurde nie wieder gehört.

    – Nigralbus

    7. November 2013 um 15:59 Uhr

PHP-Strings sind änderbar. Sie können bestimmte Zeichen wie folgt ändern:

$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)

Und Sie können Zeichen wie folgt an eine Zeichenfolge anhängen:

$string .= 'a';

  • Schöne Lösung. Am Ende von append Funktion, die Sie hinzufügen können return $this; Methodenverkettung zulassen: $sb->append("one")->append("two");.

    – Jabba

    3. Dezember 2010 um 21:20 Uhr

  • Dies ist in PHP völlig unnötig. Tatsächlich bin ich bereit zu wetten, dass dies erheblich langsamer ist als eine normale Verkettung.

    – ryeguy

    27. April 2011 um 14:55 Uhr


  • ryeguy: stimmt, da Strings in PHP veränderbar sind, ist diese Methode “unnötig”, die Person bat um eine ähnliche Implementierung wie Javas StringBuilder, also los geht’s … Ich würde nicht sagen, dass es “deutlich” langsamer ist, denke ich dir sind ein wenig dramatisch. Der Aufwand für die Instanziierung einer Klasse, die die Zeichenfolgenerstellung verwaltet, kann Kosten beinhalten, aber die Nützlichkeit der StringBuilder-Klasse kann erweitert werden, um zusätzliche Methoden für die Zeichenfolge einzubeziehen. Ich werde untersuchen, welcher zusätzliche Overhead realisiert wird, wenn so etwas in einer Klasse implementiert wird, und versuchen, zurück zu posten.

    – ossis

    11. Mai 2011 um 3:22 Uhr

  • … und er wurde nie wieder gehört.

    – Nigralbus

    7. November 2013 um 15:59 Uhr

1646264650 241 php String Verkettung Leistung
Dakusan

Ich habe den Code am Ende dieses Beitrags geschrieben, um die verschiedenen Formen der Zeichenfolgenverkettung zu testen, und sie sind wirklich alle fast genau gleich in Bezug auf Speicher und Zeitbedarf.

Die beiden primären Methoden, die ich verwendet habe, sind das Verketten von Strings miteinander und das Füllen eines Arrays mit Strings und das anschließende Implodieren. Ich habe 500 String-Ergänzungen mit einem 1-MB-String in PHP 5.6 durchgeführt (das Ergebnis ist also ein 500-MB-String). Bei jeder Iteration des Tests waren alle Speicher- und Zeitabdrücke sehr, sehr eng (bei ~$IterationNumber*1MB). Die Laufzeit beider Tests betrug 50,398 Sekunden und 50,843 Sekunden hintereinander, was höchstwahrscheinlich innerhalb akzeptabler Fehlergrenzen liegt.

Die Garbage-Collection von Zeichenfolgen, auf die nicht mehr verwiesen wird, scheint ziemlich unmittelbar zu sein, auch ohne jemals den Bereich zu verlassen. Da die Strings änderbar sind, wird im Nachhinein kein zusätzlicher Speicher benötigt.

ABERDie folgenden Tests haben gezeigt, dass es einen Unterschied in der Spitzenspeicherauslastung gibt WÄHREND Die Strings werden verkettet.

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

Ergebnis = 10.806.800 Bytes (~ 10 MB ohne den anfänglichen PHP-Speicherbedarf)

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

Ergebnis = 6.613.320 Bytes (~ 6 MB ohne den anfänglichen PHP-Speicherbedarf)

Es gibt also tatsächlich einen Unterschied, der bei sehr, sehr großen Zeichenfolgenverkettungen speichermäßig von Bedeutung sein könnte (ich bin auf solche Beispiele gestoßen, als ich sehr große Datensätze oder SQL-Abfragen erstellte).

Aber auch diese Tatsache ist aufgrund der Daten umstritten. Beispielsweise benötigte das Verketten von 1 Zeichen mit einer Zeichenfolge, um 50 Millionen Bytes (also 50 Millionen Iterationen) zu erhalten, eine maximale Menge von 50.322.512 Bytes (~48 MB) in 5,97 Sekunden. Während der Ausführung der Array-Methode wurden am Ende 7.337.107.176 Bytes (~6,8 GB) verwendet, um das Array in 12,1 Sekunden zu erstellen, und es dauerte dann zusätzliche 4,32 Sekunden, um die Zeichenfolgen aus dem Array zu kombinieren.

Wie auch immer … das Folgende ist der Benchmark-Code, den ich zu Beginn erwähnt habe, der zeigt, dass die Methoden ziemlich gleich sind. Es gibt eine hübsche HTML-Tabelle aus.

<?
//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>';
}
?>

  • Danke dafür. array_push war 100x schneller als das Verketten von Strings in meinem Code.

    – Kyouma

    13. August 2020 um 9:05 Uhr

917370cookie-checkphp String-Verkettung, Leistung

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy