강의 컨설팅 트레이닝 무료진단 무료책자 마케팅편지 마케팅정보공유 다이어리 서비스제휴 고객센터

PHP 성능 튜닝 관련 글
작성자 : 13 김영철
등록날짜 : 2009.01.14 22:52
1,448
며칠 전에 "PHP에서 성능 개선을 위한 팁"download.asp?FileID=30284937이라는 글을 읽고 몇 가지 테스트를 해보았다.
그러면서 몇가지 잘못 알려진 부분과 새로 알게된 사실이 있어서 공유하고자 글을 남긴다.

참고. 테스터 코드 소스는 제일 밑에 붙여놓았다.

테스트 환경은 다음과 같다.

PHP 5.1.2 (cli)
eAccelerator 0.9.5
Linux



1. 최대한 쌍따옴표 대신 일반따옴표를 사용하라고?

<?php
  function string1() {
       for ($i=0; $i<1000000; $i++)
           $str = "This is a message.";
  }
  function string2() {
       for ($i=0; $i<1000000; $i++)
           $str = 'This is a message.';
  }
?>

결과
:!php tester.php 1 string1
Elasped Time: TOTAL 347.93 msec, USR 347.95 msec (100.0%), SYS 0.00 msec (0.0%)

:!php tester.php 1 string2
Elasped Time: TOTAL 359.78 msec, USR 357.95 msec (99.5%), SYS 2.00 msec (0.6%)

위에서 보다시피 쌍따옴표와 일반따옴표는 속도 차이가 거의 없다.
그래서 이번에는 문자열 사이에 변수가 들어가는 경우를 테스트 해보았다.

<?php
  function string5() {
       $title = 'message';

       for ($i=0; $i<1000000; $i++)
           $str = "This is a {$title}.";
  }

  function string6() {
       $title = 'message';

       for ($i=0; $i<1000000; $i++)
           $str = "This is a ".$title.".";
  }

  function string7() {
       $title = 'message';

       for ($i=0; $i<1000000; $i++)
           $str = 'This is a '.$title.'.';
?>

결과
:!php tester.php 1 string5
Elasped Time: TOTAL 1,756.88 msec, USR 1,755.73 msec (99.9%), SYS 0.00 msec (0.0%)

:!php tester.php 1 string6
Elasped Time: TOTAL 649.77 msec, USR 648.90 msec (99.9%), SYS 0.00 msec (0.0%)

:!php tester.php 1 string7
Elasped Time: TOTAL 634.70 msec, USR 634.90 msec (100.0%), SYS 0.00 msec (0.0%)

"...{$var}..." 형식을 쓴 string5() 함수가 시간이 상당히 많이 걸린 것을 볼 수 있었다.
하지만, 그것은 쌍따옴표 안에서 {} 를 이용하여 변수를 넣었을 때에 해당되는 것이고,
string6() 과 string7() 에서 보듯이 쌍따옴표와 일반따옴표의 차이는 없었다.
결국, 성능은 쌍따옴표를 쓰냐 안 쓰냐가 아니라 문자열 중간에 변수를 어떻게 처리하는지에 문제인 것이다.

오히려 일반따옴표에서는 \n 처리가 애매하기 때문에 대신 PHP_EOL 을 사용했었는데,
"\n" 와 PHP_EOL 의 속도를 비교해보면 "\n" 가 더 빨랐다.



2. 레퍼런스 파라미터의 함정

PHP5 에서부터는 객체를 함수의 파라미터로 전달할 때 인스턴스의 주소를 넘기도록 수정되었다.(PHP4 에서는 객체를 복사해서 넘김) 하지만, 배열은 PHP4 와 마찬가지로 복사해서 넘긴다.
그럼 다음의 코드 중에 어떤 코드가 더 빠를 것이라고 생각하는가?

<?php
  function ref1() {
       $arr = array_pad(array(), 1000, 1);
       for ($i=0; $i<5000; $i++)
           $cnt = ref1_count($arr);
  }

  function ref1_count(&$arr) {
       return count($arr);
  }

  function ref2() {
       $arr = array_pad(array(), 1000, 1);
       for ($i=0; $i<5000; $i++)
           $cnt = ref2_count($arr);
  }

  function ref2_count($arr) {
       return count($arr);
  }
?>

결과
:!php tester.php 1 ref1
Elasped Time: TOTAL 920.32 msec, USR 918.86 msec (99.8%), SYS 0.00 msec (0.0%)

:!php tester.php 1 ref2
Elasped Time: TOTAL 8.45 msec, USR 8.00 msec (94.7%), SYS 1.00 msec (11.8%)

ref1() 함수에서는 $arr 변수를 레퍼런스로 넘기고, ref2() 함수에서는 $arr 변수를 VALUE로 넘긴다.
ref2() 에서는 VALUE 로 전달하기 때문에 ref2() 가 시간이 더 걸릴 것 같지만 실제로는 반대로 ref1() 함수가 훨씬 더 많은 시간이 걸린다.

그 원인을 분석하기 위하여 추가로 다음과 같은 테스트를 더 해보았다.

<?php
  function ref3() {
       global $arr2;

       for ($i=0; $i<5000; $i++)
           $cnt = ref2_count($arr2);
  }

  function ref4() {
       $arr =& $GLOBALS['arr2'];

       for ($i=0; $i<5000; $i++)
           $cnt = ref2_count($arr);
  }
?>

결과
:!php tester.php 1 ref3
Elasped Time: TOTAL 1,013.52 msec, USR 1,012.85 msec (99.9%), SYS 1.00 msec (0.1%)

:!php tester.php 1 ref4
Elasped Time: TOTAL 964.66 msec, USR 964.85 msec (100.0%), SYS 0.00 msec (0.0%)

이번에는 ref2() 함수에서 사용한 ref2_count() 를 사용했음에도 불구하고 ref3() 와 ref4() 는 ref1() 결과와 거의 유사하다. ref1() 과 ref3(), ref4() 의 공통점은 모두 넘기는 변수인 $arr과 $arr2 가 레퍼런스로 전달한다는 것이다.

그럼 이런 차이가 생기는가? 이는 PHP Zend 엔진을 이해해야 한다. ref2() 에서 $arr 를 ref2_count() 함수에 VALUE 로 전달하나 실제 변수의 복사가 이루어지는 시점은 그 변수를 수정할 때이다. 다시 말해, PHP Zend 엔진에서는 VALUE 로 값을 전달받았다고 하더라도 굳이 복사가 필요없는 경우(함수 내부에서 파라미터를 수정하지 않는 경우)에는 값을 복사하지 않는다. 대신 파라미터 값이 바뀌는 시점에 복사를 시작하게 된다.

하지만, 레퍼런스 변수를 다시 다른 함수에 전달하게 되면 값의 수정이 필요하든 없든 상관없이 그 순간에 복사가 이루어진다. 위의 코드에서는 "count($arr)" 부분에서 복사가 이루어지게 된다. 위에서 ref_countX() 함수들은 단순히 변수값을 참조하기만 하기 때문에 복사가 필요없지만 쓸데없이 복사가 이루어지느라 많은 시간이 걸리게 되는 것이다.

결론. 레퍼런스 파라미터는 함수내에서 수정이 필요한 경우에만 사용하는 것이 좋다.




3. 비교문의 성능과 올바른 습관

이번에는 IF 문에서 문자열 비교 구문을 테스트 해보았다.

<?php
  function ifstring1() {
       $a = "abcd";

       for ($i=0; $i<1000000; $i++)
           if (!$a);
  }

  function ifstring2() {
       $a = "abcd";

       for ($i=0; $i<1000000; $i++)
           if ($a === "");
  }

  function ifstring3() {
       $a = "abcd";

       for ($i=0; $i<1000000; $i++)
           if ($a == "");
  }

  function ifstring4() {
       $a = "abcd";

       for ($i=0; $i<1000000; $i++)
           if (strlen($a) <= 0);
  }
?>

결과
:!php tester.php 1 ifstring1
Elasped Time: TOTAL 242.82 msec, USR 242.96 msec (100.1%), SYS 0.00 msec (0.0%)

:!php tester.php 1 ifstring2
Elasped Time: TOTAL 240.84 msec, USR 240.96 msec (100.1%), SYS 0.00 msec (0.0%)

:!php tester.php 1 ifstring3
Elasped Time: TOTAL 425.76 msec, USR 422.94 msec (99.3%), SYS 0.00 msec (0.0%)

:!php tester.php 1 ifstring4
Elasped Time: TOTAL 610.61 msec, USR 610.91 msec (100.0%), SYS 0.00 msec (0.0%)

ifstring4() 의 경우에는 함수에 대한 시간 손실 때문에 제일 많은 시간이 걸렸다.
ifstring3() 의 경우에는 변수 Type Conversion 때문에 시간 손실이 발생한다.
성능상 제일 좋은 방법은 ifstring1() 과 ifstring2() 의 경우이다.

여기서 내가 추천하는 방법은 ifstring2() 이다. ifstring1() 의 경우에는 다음과 같은 경우가 버그가 생길 수 있다.

<?php
  $a = "0";
  if ($a) echo "Success"; else "Failed";
?>

위의 코드에서 어떤 값이 출력될 것 같은가? 정답은 "Failed" 이다. $a 가 자동으로 integer 로 변환되고, 숫자 0 이 자동으로 FALSE 로 변환되기 때문이다.

결론. 가능한한 "===" 연산자를 사용하여 비교하는 것이 좋다.



4. switch 와 if

가끔 switch 와 if 중에 뭘 쓸까 고민할 때가 있다. 예전에 PHP.net 에서 switch 가 더 빠르다는 글을 읽고 지금까지 주로 switch 를 사용했는데 이번에 테스트해보았다.

<?php
  function switch1() {
       $a = "delete";

       for ($i=0; $i<1000000; $i++) {
           switch ($a) {
               case "insert":
                   break;
               case "update":
                   break;
               case "delete":
                   break;
               case "select":
                   break;
               default:
                   break;
           }
       }
  }

  function switch2() {
       $a = "delete";

       for ($i=0; $i<1000000; $i++) {
           if ($a === "insert") {
           }
           else if ($a === "update") {
           }
           else if ($a === "delete") {
           }
           else if ($a === "select") {
           }
           else {
           }
       }
  }
?>

결과
:!php tester.php 1 switch1
Elasped Time: TOTAL 975.25 msec, USR 970.85 msec (99.5%), SYS 0.00 msec (0.0%)

:!php tester.php 1 switch2
Elasped Time: TOTAL 547.84 msec, USR 544.92 msec (99.5%), SYS 0.00 msec (0.0%)

이 결과는 IF 문에서 "==" 을 사용할 것인가 "===" 사용할 것인가와 같은 문제이다. switch 에서는 기본적으로 "==" 을 사용하는 것으로 보인다. 따라서 위와 같은 결과가 나왔다.

결론. switch 와 if 문 중에서는 if 문이 더 좋다. 단, === 연산자를 사용한다는 전제하에...



5. 테스터 코드

<?php
  require_once "func.inc.php";

  if (isset($_SERVER['argv'][1]))
       $try = $_SERVER['argv'][1];
  else
       $try = 1;

  if (isset($_SERVER['argv'][2]))
       $funcname = $_SERVER['argv'][2];
  else
       exit;

  init();

  for ($n=0; $n<$try; $n++) {
       $dblTotTime1  = microtime(TRUE);
       $dictResUsage = getrusage();
       $dblUsrTime1  = $dictResUsage['ru_utime.tv_sec'] + $dictResUsage['ru_utime.tv_usec'] / 1000000;
       $dblSysTime1  = $dictResUsage['ru_stime.tv_sec'] + $dictResUsage['ru_stime.tv_usec'] / 1000000;

       $funcname();

       $dblTotTime2  = microtime(TRUE);
       $dictResUsage = getrusage();
       $dblUsrTime2  = $dictResUsage['ru_utime.tv_sec'] + $dictResUsage['ru_utime.tv_usec'] / 1000000;
       $dblSysTime2  = $dictResUsage['ru_stime.tv_sec'] + $dictResUsage['ru_stime.tv_usec'] / 1000000;

       $dblTotTime   = $dblTotTime2 - $dblTotTime1;
       $dblUsrTime   = $dblUsrTime2 - $dblUsrTime1;
       $dblSysTime   = $dblSysTime2 - $dblSysTime1;

       $strTotTime   = number_format($dblTotTime * 1000, 2);
       $strUsrTime   = number_format($dblUsrTime * 1000, 2);
       $strSysTime   = number_format($dblSysTime * 1000, 2);

       $strUsrPer    = number_format(($dblUsrTime / $dblTotTime) * 100, 1);
       $strSysPer    = number_format(($dblSysTime / $dblTotTime) * 100, 1);

       echo 'Elasped Time: TOTAL '.$strTotTime.' msec, USR '.$strUsrTime.' msec ('.$strUsrPer.'%), SYS '.$strSysTime.' msec ('.$strSysPer.'%)'.PHP_EOL;
  }
?>



http://teeroz.egloos.com/2703307download.asp?FileID=30284937

출처 : Tong - ddakzzi님의 PHP통

"쇼핑몰·홈페이지·오픈마켓
블로그·페이스북·이메일 등의 각종 마케팅 글쓰기,
각종 광고, 영업, 판매, 제안서, 전단지
반응율 3배×10배 이상 높이는 마법의 8단계 공식"
자세히보기

Comments

번호 제목 글쓴이 날짜 조회
3165 event.srcElement 99 단국강토 02.10 1423
3164 한글 및 이미지 정렬 M 최고의하루 12.19 1424
3163 table 태그를 알자 99 단국강토 02.10 1424
3162 Warning: session_register(): Cannot send session cache limiter 13 김영철 01.14 1425
3161 액션스크립 정의 99 단국강토 01.12 1426
3160 [ PHP ] PHP has encountered an Access Violation at 77FCAFE6 M 최고의하루 12.19 1427
3159 Smarty QuickStart Guide 13 김영철 01.14 1427
3158 word-break:break-all, nowrap 유용하게 쓰기 M 최고의하루 12.26 1428
3157 me 속성 , rules 속성 99 단국강토 01.19 1428
3156 MSSQL Server DBA 가이드-2 M 최고의하루 12.26 1429
3155 [설치/설정] 확장모듈 설치 방법 13 김영철 01.14 1430
3154 MSSQL Server DBA 가이드-1 M 최고의하루 12.26 1431
3153 RSS 포멧과 구독기 만들기-2 M 최고의하루 12.24 1434
3152 FORM 태그 기본 ★ M 최고의하루 12.20 1435
3151 php5 99 단국강토 01.02 1436
3150 웹페이지의 특정 부분만 인쇄 99 단국강토 02.16 1437
3149 inner html 99 단국강토 02.16 1439
3148 class.layout을 사용한 동적인 웹페이지 13 김영철 01.13 1439
3147 table을 엑셀 편집 하듯이 M 최고의하루 12.23 1440
3146 플래쉬 강좌 - 이음새없이 흐르는 이미지 99 단국강토 01.06 1440
3145 자바스크립트의기본 99 단국강토 02.10 1442
3144 IE 패치, 플래쉬 활성화 시키기, 플래쉬 점선 없애기 99 단국강토 02.02 1442
3143 파일업로드 썸네일 제작 class 13 김영철 01.13 1443
3142 Eclipse에서 Php 사용하기 13 김영철 01.13 1445
3141 [쇼핑몰구축] PHP 사용 함수 설명 13 김영철 01.13 1449
열람중 PHP 성능 튜닝 관련 글 13 김영철 01.14 1449
3139 테이블 타이틀 내용과 같이 스크롤 99 단국강토 02.19 1452
3138 [hatelove님의 JBBS 알고리즘 강좌 11] 13 김영철 01.14 1452
3137 MS-SQL2005에서 2000 DTS Package 보기 13 김영철 01.23 1458
3136 php 파일 업, 다운로드 13 김영철 01.13 1461
마케팅
특별 마케팅자료
다운로드 마케팅자료
창업,경영
기획,카피,상품전략
동기부여,성취