php - require_once를 사용하는 것이 왜 그렇게 나쁜가요?


더 나은 PHP 코딩 방법에 대해 읽은 모든 것은 속도 때문에 require_once 사용하지 말라는 것을 계속 말합니다.

왜 이런거야?

require_once 와 똑같은 일을하는 적절한 / 더 좋은 방법은 무엇입니까? 중요한 경우 PHP5를 사용하고 있습니다.




Answers


require_onceinclude_once 는 시스템이 이미 포함 / 필수 항목에 대한 로그를 유지하도록 요구합니다. 모든 *_once 호출은 해당 로그를 확인하는 것을 의미합니다. 따라서 거기에서 수행되는 추가 작업이 있지만 앱 전체의 속도를 저하시킬만큼 충분합니까?

... 나는 정말로 그것을 의심합니다 ... 당신이 정말로 오래된 하드웨어를 사용하거나 많이 하지 않는 한 아닙니다.

*_once 수천을하고 있다면, 당신은 더 가벼운 방법으로 직접 작업을 할 수 있습니다. 간단한 앱의 경우 한 번만 포함했는지 확인하는 것만으로 충분하지만 오류를 다시 정의하는 경우 다음과 같이 할 수 있습니다.

if (!defined('MyIncludeName')) {
    require('MyIncludeName');
    define('MyIncludeName', 1);
}

나는 개인적으로 *_once 문장을 고수 하겠지만 어리석은 백만 번 통과 벤치 마크에서 두 가지의 차이를 볼 수 있습니다.

                php                  hhvm
if defined      0.18587779998779     0.046600103378296
require_once    1.2219581604004      3.2908599376678

require_once 10-100 배 느리고 hhvm 에서는 require_once 가 느리게 느려지는 것이 궁금합니다. 다시 말하지만, 이것은 수천 번 *_once 실행하는 경우에만 코드와 관련이 있습니다.

<?php // test.php

$LIMIT = 1000000;

$start = microtime(true);

for ($i=0; $i<$LIMIT; $i++)
    if (!defined('include.php')) {
        require('include.php');
        define('include.php', 1);
    }

$mid = microtime(true);

for ($i=0; $i<$LIMIT; $i++)
    require_once('include.php');

$end = microtime(true);

printf("if defined\t%s\nrequire_once\t%s\n", $mid-$start, $end-$mid);
<?php // include.php

// do nothing.



이 스레드는 이미 "솔루션 게시"가 있었기 때문에 나를 싫어하게 만듭니다. 모든 의도와 목적으로 잘못되었습니다. 열거하자 :

  1. 정의는 PHP에서 정말 비쌉니다. 이를 직접 보거나 직접 테스트 할 수 있지만, PHP에서 전역 상수를 정의하는 유일한 효율적인 방법은 확장을 사용하는 것입니다. (클래스 상수는 실제로 현명한 꽤 현명한 성능이지만, 이것은 논점입니다. 왜냐하면 2)

  2. require_once() 적절하게 사용하고 있다면, 즉 클래스를 포함하기 위해 정의가 필요하지 않습니다. class_exists('Classname') 만 확인하십시오. 포함하는 파일에 코드가 포함되어있는 경우 (즉, 절차 적 방식으로 사용하는 경우 require_once() 가 반드시 필요한 이유는 없습니다. 파일을 포함시킬 때마다 서브 루틴 호출을한다고 가정합니다.

그래서 잠시 동안 많은 사람들이 그들의 포함을 위해 class_exists() 메소드를 사용했습니다. 그것은 추한 것 때문에 맘에 들지 않지만 좋은 이유가 있습니다 : require_once() 는 최신 버전의 PHP보다 약간 비효율적이었습니다. 그러나 그것은 고쳐졌고, 조건부로 컴파일해야하는 여분의 바이트 코드와 추가 메서드 호출은 내부 해시 테이블 검사를 훨씬 능가 할 것이라는 것이 나의 주장이다.

이제, 입장료 : 테스트 시간이 거의 필요치 않기 때문에 테스트하기가 어렵습니다.

인터프리터가 구문 분석 모드로 전환 할 때마다 opcode를 생성 한 다음 뒤로 이동해야하기 때문에 일반적으로 PHP에서는 비용이 많이 든다. 100 개 이상의 포함을 사용하면 실적에 큰 영향을 미칩니다. require_once를 사용하거나 사용하지 않는 것이 중요한 질문 인 이유는 opcode 캐시가 어려워지기 때문입니다. 이것에 대한 설명은 여기에서 찾을 수 있습니다.하지만이 점은 다음과 같습니다.

  • 구문 분석하는 동안 요청의 전체 수명 동안 필요한 포함 파일을 정확히 알고 있다면 처음부터 require() 하고 opcode 캐시가 다른 모든 것을 처리합니다.

  • opcode 캐시를 실행하지 않는다면 어려운 위치에 있습니다. 모든 인클루드를 하나의 파일로 인라인 (개발 중에는 프로덕션에서만 수행하지 않음)하면 시간을 구문 분석하는 데 확실히 도움이 될 수 있지만 수행하는 데에는 어려움이 있습니다. 또한 파일에 포함 할 내용을 정확히 알아야합니다. 의뢰.

  • 자동로드는 매우 편리하지만 속도가 느립니다. 왜냐하면 자동로드 논리가 포함이 완료 될 때마다 실행되어야하기 때문입니다. 실제로, 하나의 요청에 대해 여러 개의 특수 파일을 자동로드하면 문제가 너무 많이 발생하지 않지만 필요한 모든 파일을 자동로드하지 않아야합니다.

  • 어쩌면 10 개가 포함 된 경우 (이것은 엔벨로프 계산의 뒤에 있습니다),이 모든 것이 가치가 없습니다. 데이터베이스 쿼리 나 다른 것을 최적화하십시오.




나는 호기심을 가지고 Tech Your Universe에 대한 Adam Backstrom의 링크를 확인했다. 이 기사에서는 require_once 대신 require를 사용해야하는 이유 중 하나에 대해 설명합니다. 그러나 그들의 주장은 나의 분석을 고수하지 못했다. 나는 해결책을 misanalysed 할지도 모른 지보고 싶을 것이다. 비교를 위해 PHP 5.2.0을 사용했습니다.

require_once를 사용하여 다른 헤더 파일을 포함하는 100 개의 헤더 파일을 작성했습니다. 이 파일들 각각은 다음과 같이 보입니다 :

<?php
// /home/fbarnes/phpperf/hdr0.php
require_once "../phpperf/common_hdr.php";

?>

빠른 bash 해킹을 사용하여 다음과 같이 만들었습니다.

for i in /home/fbarnes/phpperf/hdr{00..99}.php; do
  echo "<?php
// $i" > $i
  cat helper.php >> $i;
done

이 방법을 사용하면 require_once를 사용하여 쉽게 교체 할 수 있으며 헤더 파일을 포함 할 때 필요합니다. 그런 다음 app.php를 만들어 백개의 파일을로드했습니다. 이 모양은 다음과 같습니다.

<?php

// Load all of the php hdrs that were created previously
for($i=0; $i < 100; $i++)
{
  require_once "/home/fbarnes/phpperf/hdr$i.php";
}

// Read the /proc file system to get some simple stats
$pid = getmypid();
$fp = fopen("/proc/$pid/stat", "r");
$line = fread($fp, 2048);
$array = split(" ", $line);

// write out the statistics; on RedHat 4.5 w/ kernel 2.6.9
// 14 is user jiffies; 15 is system jiffies
$cntr = 0;
foreach($array as $elem)
{
  $cntr++;
  echo "stat[$cntr]: $elem\n";
}
fclose($fp);

?>

require_once 헤더와 헤더 파일을 사용하는 require 헤더를 대조했습니다 :

<?php
// /home/fbarnes/phpperf/h/hdr0.php
if(!defined('CommonHdr'))
{
  require "../phpperf/common_hdr.php";
  define('CommonHdr', 1);
}

?>

require vs. require_once를 사용하여 이것을 실행할 때 많은 차이점을 발견하지 못했습니다. 실제로 나의 초기 테스트는 require_once가 약간 더 빠르다는 것을 암시하는 것처럼 보였지만, 필자는 그것을 반드시 믿지는 않는다. 나는 10000 개의 입력 파일로 실험을 반복했다. 여기서 나는 일관된 차이를 보았다. 테스트를 여러 번 실행했지만 결과는 비슷하지만 require_once를 사용하면 평균 30.8 개의 사용자 jiffies와 72.6 개의 시스템 jiffies를 사용합니다. 평균 39.4 사용자 jiffies 및 72.0 시스템 jiffies에서 요구 사용을 사용합니다. 따라서 require_once를 사용하여로드가 약간 더 낮게 나타납니다. 그러나 벽시계는 약간 증가합니다. 10,000 건의 require_once 호출은 평균 10.15 초를 사용하고 10,000 건의 호출은 평균 9.84 초를 사용합니다.

다음 단계는 이러한 차이점을 조사하는 것입니다. strace를 사용하여 시스템 호출을 분석했습니다.

require_once에서 파일을 열기 전에 다음 시스템 호출이 작성됩니다.

time(NULL)                              = 1223772434
lstat64("/home", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
lstat64("/home/fbarnes", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
lstat64("/home/fbarnes/phpperf", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
lstat64("/home/fbarnes/phpperf/h", {st_mode=S_IFDIR|0755, st_size=270336, ...}) = 0
lstat64("/home/fbarnes/phpperf/h/hdr0.php", {st_mode=S_IFREG|0644, st_size=88, ...}) = 0
time(NULL)                              = 1223772434
open("/home/fbarnes/phpperf/h/hdr0.php", O_RDONLY) = 3

이는 요구 사항과 대조됩니다.

time(NULL)                              = 1223772905
lstat64("/home", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
lstat64("/home/fbarnes", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
lstat64("/home/fbarnes/phpperf", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
lstat64("/home/fbarnes/phpperf/h", {st_mode=S_IFDIR|0755, st_size=270336, ...}) = 0
lstat64("/home/fbarnes/phpperf/h/hdr0.php", {st_mode=S_IFREG|0644, st_size=146, ...}) = 0
time(NULL)                              = 1223772905
open("/home/fbarnes/phpperf/h/hdr0.php", O_RDONLY) = 3

Tech Your Universe는 require_once가 더 많은 lstat64 호출을해야 함을 의미합니다. 그러나 둘 다 동일한 수의 lstat64 호출을합니다. 아마, 위의 코드를 최적화하기 위해 APC를 실행하지 않는다는 차이점이있을 수 있습니다. 그러나, 내가 한 다음은 전체 실행에 대한 strace의 출력을 비교했습니다.

[fbarnes@myhost phpperf]$ wc -l strace_1000r.out strace_1000ro.out 
  190709 strace_1000r.out
  210707 strace_1000ro.out
  401416 total

require_once를 사용할 때 헤더 파일 당 약 2 개의 시스템 호출이 효과적으로 발생합니다. 한가지 차이점은 require_once가 time () 함수에 대한 추가 호출을한다는 것입니다.

[fbarnes@myhost phpperf]$ grep -c time strace_1000r.out strace_1000ro.out 
strace_1000r.out:20009
strace_1000ro.out:30008

다른 시스템 호출은 getcwd ()입니다.

[fbarnes@myhost phpperf]$ grep -c getcwd strace_1000r.out strace_1000ro.out 
strace_1000r.out:5
strace_1000ro.out:10004

이것은 hdrXXX 파일에서 참조 된 상대 경로로 결정 되었기 때문에 호출됩니다. 이 값을 절대적인 참조로 만들면 유일한 차이점은 코드에서 작성한 추가 시간 (NULL) 호출입니다.

[fbarnes@myhost phpperf]$ wc -l strace_1000r.out strace_1000ro.out 
  190705 strace_1000r.out
  200705 strace_1000ro.out
  391410 total
[fbarnes@myhost phpperf]$ grep -c time strace_1000r.out strace_1000ro.out
strace_1000r.out:20008
strace_1000ro.out:30008

이것은 상대 경로가 아닌 절대 경로를 사용하여 시스템 호출 수를 줄일 수 있음을 의미합니다. 그 밖의 유일한 차이점은 무엇이 더 빠른지 비교하기 위해 코드를 계측하는 데 사용되는 것으로 보이는 시간 (NULL) 호출입니다.

한 가지 주목할 점은 APC 최적화 패키지에는 require_once 및 include_once 호출 ( PHP 문서 참조)에 의한 시스템 호출 수가 감소한다고 주장하는 "apc.include_once_override"옵션이 있습니다.

긴 게시물을 유감으로 생각합니다. 나는 호기심이 많다.




코딩을 피하기 위해 이러한 코딩 관행에 대한 링크를 제공 할 수 있습니까? 내가 아는 한, 그것은 완전한 논쟁 거리가 아닙니다 . 필자는 소스 코드를 직접 보지 않았지만 includeinclude_once 의 유일한 차이점은 include_once 가 파일 이름을 배열에 추가하고 매번 배열을 검사한다는 것입니다. 그 배열을 정렬 된 상태로 유지하는 것은 쉬울 것입니다. 따라서 검색은 O (log n)이어야하며 중형 어플리케이션은 두 개 정도만 포함됩니다.




더 좋은 방법은 객체 지향 접근법을 사용하고 __autoload () 를 사용하는 것입니다.




*_once() 함수는 모든 상위 디렉토리를 포함하여 포함시키고 자하는 파일이 이미 포함되어 있는지 확인합니다. 그것은 경기 침체의 원인의 일부입니다.

Siege 와 같은 도구를 벤치마킹에 사용하는 것이 좋습니다. 제안 된 모든 방법을 시도하고 응답 시간을 비교할 수 있습니다.

Tech Your Universe의 require_once() 에 대한 추가 정보.




PEAR2 위키 (존재했을 때)는 최소한 라이브러리 코드에 대해 자동로드선호하는 모든 require / include 지시어를 버리는 좋은 이유 를 나열하는 데 사용되었습니다. phar 와 같은 대체 패키징 모델이 수평선에있을 때, 이들은 엄격한 디렉토리 구조에 묶여 있습니다.

업데이트 : wiki의 웹 아카이브 버전이 눈에 거슬 린 추악한 것처럼, 나는 아래의 가장 강력한 이유를 복사했습니다 :

  • include_path는 (PEAR) 패키지를 사용하기 위해 필요합니다. 이로 인해 광범위한 소스 코드 수정없이 PEAR 패키지를 phar 아카이브로 옮기기 위해 필요한 클래스를 포함하는 단일 파일을 생성하기 위해 자체 include_path와 함께 다른 응용 프로그램 내에 PEAR 패키지를 묶는 것이 어렵습니다.
  • 최상위 레벨의 require_once가 조건부 require_once와 섞이면 PHP 6 번들로 제공 될 APC와 같은 opcode 캐시에 의해 uncacheable 인 코드가 생성 될 수 있습니다.
  • 상대적인 require_once는 include_path가 이미 올바른 값으로 설정되어야 함을 요구하므로 적절한 include_path없이 패키지를 사용할 수 없습니다



그것은 나쁜 기능을 사용하지 않습니다. 전반적인 코드 기반에서 언제 어떻게 사용하는지에 대한 잘못된 이해입니다. 아마도 오해의 개념에 조금 더 많은 내용을 추가 할 것입니다.

사람들은 require_once가 느린 함수라고 생각해서는 안됩니다. 코드를 편입해야합니다. require_once()require() 의 속도는 문제가 아닙니다. 그것은 맹목적으로 사용하여 발생할 수있는주의 사항을 방해하는 성능에 관한 것입니다. 컨텍스트에 대한 고려없이 광범위하게 사용되는 경우 엄청난 메모리 낭비 또는 낭비되는 코드가 발생할 수 있습니다.

내가보기에는 정말 거대한 모 놀리 식 프레임 워크가 모든 잘못된 방법으로 require_once() 를 사용할 때, 특히 복잡한 객체 지향 환경에서 특히 그렇습니다.

많은 라이브러리에서 볼 수있는 모든 클래스의 맨 위에서 require_once() 를 사용하는 예제를 보자.

require_once("includes/usergroups.php");
require_once("includes/permissions.php");
require_once("includes/revisions.php");
class User{
  //user functions
}

따라서 User 클래스는 다른 세 클래스를 모두 사용하도록 설계되었습니다. 공정! 하지만 방문자가 사이트를 탐색하고 로그인하지 않고 프레임 워크가로드되는 경우 require_once("includes/user.php"); 매 요청마다.

그것은 특별한 요청 중에 사용하지 않을 1 + 3 개의 불필요한 클래스를 포함합니다. 이것은 비 대한 프레임 워크가 5MB 이하가 아닌 요청 당 40MB를 사용하는 방식입니다.

오용 될 수있는 다른 방법은 클래스가 다른 많은 사람들에 의해 재사용 될 때입니다! helper 함수를 사용하는 약 50 개의 클래스가 있다고 가정 해보십시오. helpers 가로드 될 때 해당 클래스에서 helpers 를 사용할 수 있도록하려면 다음을 수행하십시오.

require_once("includes/helpers.php");
class MyClass{
  //Helper::functions();//etc..
}

여기에 그 자체로 잘못된 것은 없습니다. 그러나 한 페이지 요청이 비슷한 종류의 15 개 클래스를 포함하면 발생합니다. require_once 15 번 실행하거나 멋진 시각적 효과를 require_once 다음을 수행하십시오.

require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");

require_once ()를 사용하면 이러한 불필요한 행을 구문 분석하는 것 외에 기술적으로 14 번 해당 함수를 실행하는 성능에 영향을 미칩니다. 그 비슷한 문제를 가진 10 개의 다른 클래스들과 함께, 그것보다는 무의미한 반복적 인 코드의 100+ 라인을 설명 할 수 있습니다.

그것으로 아마도 require("includes/helpers.php"); 사용할 가치가 있습니다 require("includes/helpers.php"); 대신 앱이나 프레임 워크의 부트 스트랩에서. 그러나 모든 것이 상대적이므로, helpers 클래스의 가중치 대 사용 빈도가 require_once() 의 15-100 줄을 절약하는 데 가치가 있는지 여부는 모두 다릅니다 . 그러나 주어진 요청에서 helpers 파일을 사용하지 않을 확률이 아무 것도 없다면 require 는 반드시 메인 클래스에 있어야합니다. 각 클래스에 require_once 를 별도로 require_once 자원 낭비가됩니다.

require_once 함수는 필요할 때 유용하지만 모든 클래스를로드하기 위해 어디에서나 사용할 수있는 모 놀리 식 솔루션으로 간주해서는 안됩니다.




require_onceinclude_once requireinclude (또는 대안이 존재할 수도 있음)보다 느릴지라도 여기서 가장 미세한 마이크로 최적화 수준을 말합니다. require_once 와 같은 것을 걱정하는 것보다 저조한 루프 또는 데이터베이스 쿼리를 최적화하는 것이 훨씬 더 낫습니다.

이제는 require_once 코딩 방법을 허용한다는 주장을 제기 할 수 있습니다. 왜냐하면 포함 된 항목을 깨끗하게 정리하는 데주의 할 필요가 없기 때문에 기능 자체 및 특히 ​​속도와 관련이 없습니다.

분명히, 자동 로딩은 코드의 청결 함과 유지 보수의 편의를 위해 더 낫지 만, 이것이 속도 와는 아무런 관련이 없음을 분명히하고 싶습니다.




include, oli의 대안 및 __autoload ()를 사용하여 테스트합니다. 설치된 APC같은 것으로 테스트하십시오. 나는 상수를 사용하여 물건을 빠르게하는 것을 의심한다.




네, 보통 ol 'require ()보다 약간 비쌉니다. 요점은 당신이 중복을 포함하지 않을만큼 코드를 체계적으로 관리 할 수 ​​있다면 * _once () 함수를 사용하지 말라는 것입니다.

하지만 _once () 함수를 사용하면 앱을 종료하지 않습니다. 기본적으로, 귀하의 포함을 구성 할 필요가 없다는 핑계로 사용하지 마십시오 . 어떤 경우에는 그것을 사용하는 것을 여전히 피할 수 없으며 큰 문제는 아닙니다.




PEAR 문서에는 require, require_once, include 및 include_once에 대한 권장 사항이 있다고 생각합니다. 나는 그 지침을 따른다. 귀하의 신청서가 더 분명합니다.




속도와는 아무런 관련이 없습니다. 그것은 정상적으로 실패하는 것입니다.

require_once ()가 실패하면 스크립트가 완료됩니다. 그 밖의 것은 처리되지 않습니다. include_once ()를 사용하면 스크립트의 나머지 부분이 렌더링을 계속 시도하므로 사용자가 스크립트에서 실패한 부분에 잠재적으로 아무런 지혜가 없을 수 있습니다.




필자가 개인적으로 require_once (또는 include_once)를 사용하는 것은 require_once가 이미 해당 파일을 포함하고 있으며 포함 된 두 파일의 오류를 억제하면 (예 : 함수 / 클래스 / 등의 중복 선언과 같은) 치명적인 오류를 발생시키지 않으므로 사용자를 확인하기 때문에 바람직하지 않습니다. .

파일을 포함해야하는지 알고 있어야합니다.