![](/img/trans.png)
[英]PHP String concatenation - “$a $b” vs $a . “ ” . $b - performance
[英]php String Concatenation, Performance
在 Java 和 C# 等语言中,字符串是不可变的,一次一个字符构建一个字符串的计算成本可能很高。 在上述语言中,有一些库类可以降低这种成本,例如 C# System.Text.StringBuilder
和 Java java.lang.StringBuilder
。
php(4 或 5;我对两者都感兴趣)是否共享此限制? 如果是这样,是否有类似的问题可用的解决方案?
不,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';
如果您需要在变量中捕获此输出,您可以使用输出缓冲函数来实现。
另外,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 是效率最低的方法,无论是在时间还是内存消耗方面。 编辑:除非您有鹰眼,否则请在另一个选项卡中查看图像。
PHP 中不需要 StringBuilder 模拟。
我做了几个简单的测试:
在 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 ms
3) C#,普通拼接:~520ms
4) C#,使用 StringBuilder:~1ms
100000 次迭代:
1) PHP,普通拼接:~63ms
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';
我在这篇文章的末尾编写了代码来测试不同形式的字符串连接,它们在内存和时间足迹方面几乎完全相同。
我使用的两种主要方法是将字符串相互连接,并用字符串填充数组,然后将它们内爆。 我在 php 5.6 中用 1MB 的字符串添加了 500 个字符串(所以结果是 500MB 的字符串)。 在测试的每次迭代中,所有内存和时间占用都非常接近(在 ~$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,不含初始 PHP 内存占用)
$OneMB=str_repeat('x', 1024*1024);
$Final=implode('', Array($OneMB, $OneMB, $OneMB, $OneMB, $OneMB));
print memory_get_peak_usage();
结果=6,613,320 字节(约 6MB,不含初始 PHP 内存占用)
因此,实际上在非常非常大的字符串连接内存方面存在显着差异(我在创建非常大的数据集或 SQL 查询时遇到过这样的例子)。
但即使是这个事实也是有争议的,这取决于数据。 例如,将 1 个字符连接到一个字符串上以获得 5000 万字节(即 5000 万次迭代)在 5.97 秒内最多需要 50,322,512 字节(~48MB)。 在执行数组方法时,最终使用 7,337,107,176 字节(~6.8GB)在 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>';
}
?>
是的。 他们是这样。 例如,如果您想将几个字符串回显在一起,请使用
echo str1,str2,str3
代替
echo str1.str2.str3让它快一点。
首先,如果您不需要连接字符串,请不要这样做:这样做总是会更快
echo $a,$b,$c;
比
echo $a . $b . $c;
但是,至少在 PHP5 中,字符串连接确实非常快,尤其是当给定字符串只有一个引用时。 我猜解释器在内部使用了类似StringBuilder
的技术。
我刚刚遇到了这个问题:
$str .= '字符串连接。 ';
对比
$str = $str 。 '字符串连接。 ';
到目前为止,似乎没有人对此进行过比较。 50.000 次迭代和 php 7.4 的结果非常疯狂:
字符串 1:0.0013918876647949
字符串 2:1.1183910369873
法克托:803 !!!
$currentTime = microtime(true);
$str = '';
for ($i = 50000; $i > 0; $i--) {
$str .= 'String concatenation. ';
}
$currentTime2 = microtime(true);
echo "String 1: " . ( $currentTime2 - $currentTime);
$str = '';
for ($i = 50000; $i > 0; $i--) {
$str = $str . 'String concatenation. ';
}
$currentTime3 = microtime(true);
echo "<br>String 2: " . ($currentTime3 - $currentTime2);
echo "<br><br>Faktor: " . (($currentTime3 - $currentTime2) / ( $currentTime2 - $currentTime));
有人可以证实这一点吗? 我遇到了这个问题,因为我通过读取从一个大文件中删除了一些行,并且只再次将想要的行附加到一个字符串上。
使用 .= 在这里解决了我所有的问题。 在我超时之前!
如果您在 PHP 字符串中放置变量值,我知道使用内联变量包含会稍微快一点(这不是官方名称 - 我不记得是什么)
$aString = 'oranges';
$compareString = "comparing apples to {$aString}!";
echo $compareString
comparing apples to oranges!
必须在双引号内才能工作。 也适用于数组成员(即
echo "You requested page id {$_POST['id']}";
)
php 没有这样的限制,php 可以将 strng 与 dot(.) 运算符连接起来
$a="hello ";
$b="world";
echo $a.$b;
输出“你好世界”
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.