質問
Java や C# などの言語では、文字列は不変であり、一度に 1 文字ずつ文字列を構築すると計算コストが高くなる可能性があります。上記の言語には、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ミリ秒
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';
はい。そうです。たとえば、いくつかの文字列を一緒にエコーしたい場合は、次のように使用します。
echo str1,str2,str3
の代わりに
echo str1.str2.str3もう少し早く取得するために。
この投稿の最後で、さまざまな形式の文字列連結をテストするコードを書きましたが、メモリと時間のフットプリントはすべてほぼ正確に同じです。
私が使用した 2 つの主な方法は、文字列を相互に連結する方法と、配列に文字列を入力してからそれらを内破する方法です。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 バイト (PHP の初期メモリ占有量を除くと約 10MB)
$OneMB=str_repeat('x', 1024*1024);
$Final=implode('', Array($OneMB, $OneMB, $OneMB, $OneMB, $OneMB));
print memory_get_peak_usage();
結果 = 6,613,320 バイト (PHP の初期メモリ占有量を除くと約 6MB)
したがって、実際には、非常に大規模な文字列連結ではメモリ的に重大な違いが生じる可能性があります (非常に大規模なデータ セットや SQL クエリを作成するときに、そのような例に遭遇したことがあります)。
しかし、この事実もデータによっては議論の余地があります。たとえば、文字列に 1 文字を連結して 5,000 万バイト (つまり 5,000 万回の反復) を取得するには、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 $a,$b,$c;
よりも
echo $a . $b . $c;
ただし、少なくとも PHP5 では、特に特定の文字列への参照が 1 つしかない場合、文字列の連結は非常に高速です。通訳者が使用していると思います StringBuilder
内部的には - のようなテクニック。
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」を出力します