Development Tip

php 문자열 연결, 성능

yourdevel 2020. 11. 4. 20:54
반응형

php 문자열 연결, 성능


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

예. 그들이하다. 예를 들어, 몇 개의 문자열을 함께 에코하려면 다음을 사용하십시오.

에코 str1, str2, str3 

대신에

echo str1.str2.str3 
조금 더 빨리 얻을 수 있습니다.


나는이 포스트의 마지막 부분에 다양한 형태의 문자열 연결을 테스트하기 위해 코드를 작성했으며, 그것들은 메모리와 시간 풋 프린트에서 거의 정확히 동일합니다.

내가 사용한 두 가지 기본 방법은 문자열을 서로 연결하고 배열을 문자열로 채운 다음 파열하는 것입니다. 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 쿼리를 만들 때 이러한 예제를 살펴 보았습니다).

그러나이 사실조차도 데이터에 따라 논쟁의 여지가 있습니다. 예를 들어, 5 천만 바이트 (5 천만 반복)를 얻기 위해 1 개의 문자를 문자열에 연결하면 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>';
}
?>

첫째, 문자열을 연결할 필요가없는 경우에는하지 마십시오. 항상 수행하는 것이 더 빠릅니다.

echo $a,$b,$c;

보다

echo $a . $b . $c;

그러나 적어도 PHP5에서는 문자열 연결이 매우 빠릅니다. 특히 주어진 문자열에 대한 참조가 하나 뿐인 경우 더욱 그렇습니다. 인터프리터가 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를 dot (.) 연산자와 연결할 수 있습니다.

$a="hello ";
$b="world";
echo $a.$b;

"hello world"를 출력합니다.

참고 URL : https://stackoverflow.com/questions/124067/php-string-concatenation-performance

반응형