web-dev-qa-db-ja.com

php文字列の連結、パフォーマンス

JavaやC#のような言語では、文字列は不変であり、一度に1文字ずつ文字列を作成すると計算コストが高くなる可能性があります。 System.Text.StringBuilderおよびJava Java.lang.StringBuilder

Php(4または5。両方に興味があります)はこの制限を共有していますか?もしそうなら、利用可能な問題に対する同様の解決策はありますか?

69
Chris

いいえ、文字列は変更可能であるため、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';

この出力を変数にキャプチャする必要がある場合は、 output buffering functions を使用してキャプチャできます。

また、PHPの配列パフォーマンスは本当に優れています。値のコンマ区切りリストのような何かをしたい場合は、単にimplode()を使用します

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

最後に、 PHPの文字列タイプ に精通していることを確認してください。これは異なる区切り文字であり、それぞれの意味合いです。

60
Peter Bailey

私はこれに興味があったので、テストを実行しました。次のコードを使用しました。

<?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は、時間とメモリ消費の両方の面で最も効率の悪い方法です。編集:イーグルビジョンがない限り、別のタブで画像を表示します。 enter image description here

27
Evilnode

時限比較を行う場合、違いは非常に小さいため、あまり意味がありません。コードを読みやすく、理解しやすい選択肢を選ぶと、より多くのことができるでしょう。

12
SeanDowney

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、通常の連結:〜63ms
2)PHP、StringBuilderを使用:〜555ms
3)C#、通常の連結:〜91000ms // !!!
4)C#、StringBuilderを使用:〜17ms

12
nightcoder

私はあなたが何について話しているか知っています。 Java StringBuilderクラスをエミュレートするこの単純なクラスを作成しました。

class StringBuilder {

  private $str = array();

  public function __construct() { }

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

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

}
10
ossys

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';
6
Jeremy Ruten

この投稿の最後に、さまざまな形式の文字列連結をテストするためのコードを作成しましたが、実際には、メモリとタイムフットプリントの両方でほぼ正確に同じです。

私が使用した2つの主な方法は、文字列を互いに連結し、配列に文字列を入力してから、それらを展開することです。 PHP 5.6で1MBの文字列で500個の文字列を追加しました(結果は500MBの文字列です)。テストを繰り返すたびに、すべてのメモリと時間のフットプリントが非常に近くなりました(〜$ 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();

Result = 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文字を文字列に連結して5,000万バイト(つまり5,000万回の反復)を取得するには、5.97秒で最大50,322,512バイト(〜48MB)かかりました。配列メソッドを実行している間、7,337,107,176バイト(〜6.8GB)を使用して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>';
}
?>
2
Dakusan

はい。彼らはします。たとえば、いくつかの文字列を一緒にエコーしたい場合は、

 echo str1、str2、str3 

の代わりに

 echo str1.str2.str3 
2
mixdev

まず、文字列を連結する必要がない場合は、それを行わないでください。常に高速になります

echo $a,$b,$c;

より

echo $a . $b . $c;

ただし、少なくともPHP5では、特に特定の文字列への参照が1つしかない場合、文字列の連結は非常に高速です。インタープリターはStringBuilderのようなテクニックを内部的に使用していると思います。

1

PHP文字列内に変数値を配置している場合、インライン変数インクルージョンを使用する方がわずかに速いことを理解しています(正式名称ではありません-何であるか思い出せません)

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

動作するには二重引用符で囲む必要があります。配列メンバー(つまり、.

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

0
cori