[php] require_once가 왜 그렇게 사용하기에 좋지 않습니까?

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

왜 이런거야?

동일한 작업을 수행하는 올바른 방법은 무엇입니까 require_once? 중요한 경우 PHP 5를 사용하고 있습니다.



답변

require_once그리고 include_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

10 ~ 100 ×와 느린 require_once과 그 호기심의 require_once겉으로는 느린에서입니다 hhvm. 다시 말하지만, *_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 이전에는 꽤 비효율적이었습니다. 그러나 그것은 고쳐졌고 조건부와 여분의 메소드 호출을 위해 컴파일 해야하는 여분의 바이트 코드가 내부 해시 테이블 검사를 훨씬 능가한다는 주장은 내 주장입니다.

이제, 승인 :이 작업은 실행 시간이 거의 없기 때문에 테스트하기가 어렵습니다.

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

  • 구문 분석 시간 동안 요청의 전체 수명 동안 필요한 파일이 무엇인지 정확히 알고 있으면 require()처음부터 opcode 캐시가 다른 모든 파일을 처리합니다.

  • opcode 캐시를 실행하지 않는 경우 어려운 위치에 있습니다. 모든 포함을 하나의 파일로 인라인하면 (개발 중, 프로덕션 환경에서만 수행) 시간을 구문 분석하는 데 도움이 될 수 있지만, 처리하는 데 어려움이 있으며, 작업 중에 포함 할 대상을 정확히 알아야합니다. 의뢰.

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

  • 포함이 10 개 ( 봉투 계산 의 매우 역임) 인 경우 데이터베이스 쿼리 또는 무언가를 최적화하는 것만으로도 가치가 없습니다.


답변

궁금해서 Adam Backstrom의 Tech Your Universe 링크를 확인했습니다. . 이 기사에서는 require_once 대신 require를 사용해야하는 이유 중 하나에 대해 설명합니다. 그러나 그들의 주장은 내 분석에 영향을 미치지 않았다. 솔루션을 잘못 분석 한 곳을 보는 데 관심이 있습니다. 비교를 위해 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를 만들어 100 개의 파일을로드했습니다. 이것은 다음과 같았다 :

<?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 with 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와 require_once로 이것을 실행할 때 큰 차이를 찾지 못했습니다. 사실, 초기 테스트는 require_once가 약간 빠르다는 것을 암시하는 것처럼 보였지만 반드시 그렇게 생각하지는 않습니다. 10000 개의 입력 파일로 실험을 반복했습니다. 여기서 나는 일관된 차이를 보았습니다. 테스트를 여러 번 실행했는데 결과는 비슷하지만 require_once는 평균 30.8 개의 사용자 지프와 72.6 개의 시스템 지프를 사용합니다. 평균 39.4 사용자 지프와 72.0 시스템 지프가 필요합니다. 따라서 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

이것은 require와 대조됩니다 :

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

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

또 다른 참고 사항은 APC 최적화 패키지에 “apc.include_once_override”라는 옵션이 있으며 require_once 및 include_once 호출에 의해 수행되는 시스템 호출 수를 줄입니다 ( PHP 문서 참조 ).


답변

이러한 코딩 관행에 대한 링크를 피할 수 있습니까? 내가 아는 한, 그것은 완전한 비 이슈 입니다. 나는 소스 코드를 직접 보았다하지 않은,하지만 난 사이의 유일한 차이는 상상 것 include과는 include_onceinclude_once배열마다 시간이 지남에 따라 배열 및 검사에 그 파일 이름을 추가합니다. 해당 배열을 정렬 된 상태로 유지하는 것은 쉬운 일이므로 O (log n)를 검색해야하며 중간 규모의 응용 프로그램조차도 수십 가지가 포함됩니다.


답변

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


답변

나쁜 기능을 사용하지 않습니다. 전체 코드 기반에서 사용 방법과시기를 잘못 이해하고 있습니다. 나는 아마도 오해의 여지가있는 개념에 약간 더 많은 맥락을 추가 할 것입니다.

사람들은 require_once가 느린 기능이라고 생각해서는 안됩니다. 코드를 다른 방식으로 포함시켜야합니다. require_once()require()속도는 문제가되지 않습니다. 맹목적으로 사용하면 발생할 수있는 경고를 막는 성능에 관한 것입니다. 문맥을 고려하지 않고 광범위하게 사용하면 메모리 낭비 나 코드 낭비가 발생할 수 있습니다.

내가 본 사실은 거대한 모 놀리 식 프레임 워크 require_once()가 잘못된 방식으로, 특히 복잡한 객체 지향 (OO) 환경에서 사용될 때 입니다.

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클래스가로드 될 때 해당 클래스를 사용할 수 있게하려면 다음을 얻습니다.

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

여기에는 아무런 문제가 없습니다. 그러나 한 페이지 요청에 15 개의 유사한 클래스가 포함되는 경우. 당신은 require_once15 번 실행 하거나 멋진 시각을 위해 :

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");통해 응용 프로그램 또는 프레임 워크의 부트 스트랩에서 사용 하는 것이 좋습니다. 그러나 모든 것이 상대적이기 때문에 클래스 의 무게 대 사용 빈도 가 15-100 줄을 절약 할 가치가 있는지 여부에 달려 있습니다. 그러나 주어진 요청 에서 파일을 사용하지 않을 확률이 없으면 주 클래스에 있어야합니다. 데 각 클래스에서 별도로 자원의 낭비가된다.helpersrequire_once()helpersrequirerequire_once


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


답변

PEAR2 위키 (존재 하는 경우)는 최소한 라이브러리 코드에 대해 자동로드위해 모든 require / include 지시문 을 포기해야하는 적절한 이유 를 나열하는 데 사용되었습니다 . 이것들은 phar 와 같은 대체 패키징 모델 이 수평선에 있을 때 견고한 디렉토리 구조와 연결됩니다 .

업데이트 : 위키의 웹 보관 버전이 눈에 띄게 추악하므로 아래에서 가장 매력적인 이유를 복사했습니다.

  • (PEAR) 패키지를 사용하려면 include_path가 필요합니다. 따라서 PEAR 패키지를 다른 응용 프로그램 내에 자체 include_path와 번들로 묶어 필요한 클래스가 포함 된 단일 파일을 작성하고 광범위한 소스 코드 수정없이 PEAR 패키지를 phar 아카이브로 이동하기가 어렵습니다.
  • 최상위 레벨 require_once가 조건부 require_once와 혼합되면 APC와 같은 opcode 캐시로 캐시 할 수없는 코드가 생길 수 있습니다 .PHP 6과 함께 번들로 제공됩니다.
  • relative require_once는 include_path가 올바른 값으로 설정되어 있어야하며, 올바른 include_path없이 패키지를 사용할 수 없습니다