Garbage Collection 성능 고려 사항

우리는 이전 섹션에서 단순히 가능한 루트를 수집하는 것이 성능에 아주 작은 영향을 미친다는 것을 이미 언급했지만 이것은 PHP 5.2와 PHP 5.3을 비교할 때입니다. PHP 5.2와 같이 루트를 전혀 기록하지 않는 것과 비교할 때 가능한 루트의 기록은 더 느리지만 PHP 5.3의 PHP 런타임에 대한 다른 변경 사항으로 인해 이러한 특정 성능 손실이 표시되지 않았습니다.

성능이 영향을 받는 두 가지 주요 영역이 있습니다. 첫 번째 영역은 메모리 사용량 감소이고 두 번째 영역은 가비지 수집 메커니즘이 메모리 정리를 수행할 때 런타임 지연입니다. 우리는 이 두 가지 문제를 모두 살펴볼 것입니다.


메모리 사용량 감소

우선, 가비지 수집 메커니즘을 구현하는 모든 이유는 전제 조건이 충족되는 즉시 순환 참조 변수를 정리하여 메모리 사용량을 줄이기 위한 것입니다. PHP 구현에서 이것은 루트 버퍼가 가득 차자 마자 또는 gc_collect_cycles() 함수가 호출될 때 발생합니다. 아래 그래프에서 PHP 5.2와 PHP 5.3 모두에서 아래 스크립트의 메모리 사용량을 표시합니다. 시작 시 PHP 자체가 사용하는 기본 메모리는 제외됩니다.

예제 #1 메모리 사용 예

                  
<?php
class Foo
{
    public $var = '3.14159265359';
}

$baseMemory = memory_get_usage();

for ( $i = 0; $i <= 100000; $i++ )
{
    $a = new Foo;
    $a->self = $a;
    if ( $i % 500 === 0 )
    {
        echo sprintf( '%8d: ', $i ), memory_get_usage() - $baseMemory, "\n";
    }
}
?>
                  
                

Comparison of memory usage between PHP 5.2 and PHP 5.3

이 매우 학문적인 예에서 우리는 속성이 개체 자체를 다시 가리키도록 설정된 개체를 만들고 있습니다. 스크립트의 $a 변수가 루프의 다음 반복에서 재할당되면 일반적으로 메모리 누수가 발생합니다. 이 경우 두 개의 zval-container(객체 zval 및 속성 zval)가 누출되지만 가능한 루트는 하나만 발견됩니다. 바로 설정되지 않은 변수입니다. 루트 버퍼가 10,000회 반복(총 10,000개의 가능한 루트 포함) 후에 가득 차면 가비지 수집 메커니즘이 시작되어 가능한 루트와 관련된 메모리를 해제합니다. 이것은 PHP 5.3의 들쭉날쭉한 메모리 사용량 그래프에서 매우 명확하게 볼 수 있습니다. 각 10,000번의 반복 후에 메커니즘이 작동하여 순환 참조 변수와 관련된 메모리를 해제합니다. 이 예에서는 메커니즘 자체가 많은 작업을 수행할 필요가 없습니다. 누출되는 구조가 매우 간단하기 때문입니다. 다이어그램에서 PHP 5.3의 최대 메모리 사용량이 약 9Mb인 반면 PHP 5.2의 메모리 사용량은 계속 증가하고 있음을 알 수 있습니다.


런타임 속도 저하

가비지 수집 메커니즘이 성능에 영향을 미치는 두 번째 영역은 "누출된" 메모리를 해제하기 위해 가비지 수집 메커니즘이 시작되는 데 걸리는 시간입니다. 이것이 얼마인지 확인하기 위해 이전 스크립트를 약간 수정하여 더 많은 수의 반복을 허용하고 중간 메모리 사용량 수치를 제거합니다. 두 번째 스크립트는 다음과 같습니다.

예제 #2 GC 성능 영향

                  
<?php
class Foo
{
    public $var = '3.14159265359';
}

for ( $i = 0; $i <= 1000000; $i++ )
{
    $a = new Foo;
    $a->self = $a;
}

echo memory_get_peak_usage(), "\n";
?>
                  
                

이 스크립트를 두 번 실행합니다. 한 번은 zend.enable_gc 설정이 켜져 있고 한 번은 꺼져 있습니다.

예제 #3 위 스크립트 실행

time php -dzend.enable_gc=0 -dmemory_limit=-1 -n example2.php
# and
time php -dzend.enable_gc=1 -dmemory_limit=-1 -n example2.php
                

내 컴퓨터에서 첫 번째 명령은 일관되게 약 10.7초가 소요되는 반면 두 번째 명령은 약 11.4초가 소요됩니다. 이것은 약 7%의 감속입니다. 그러나 스크립트에서 사용하는 최대 메모리 양은 931Mb에서 10Mb로 98% 감소합니다. 이 벤치마크는 그다지 과학적이지 않거나 실제 응용 프로그램을 대표하지 않지만 이 가비지 수집 메커니즘이 제공하는 메모리 사용 이점을 보여줍니다. 좋은 점은 이 특정 스크립트의 경우 속도 저하가 항상 7%로 동일하지만 스크립트 실행 중에 더 많은 순환 참조가 발견됨에 따라 메모리 절약 기능이 점점 더 많은 메모리를 절약한다는 것입니다.


PHP의 내부 GC 통계

PHP 내에서 가비지 수집 메커니즘이 실행되는 방법에 대한 정보를 조금 더 얻을 수 있습니다. 그러나 그렇게 하려면 벤치마크 및 데이터 수집 코드를 활성화하기 위해 PHP를 다시 컴파일해야 합니다. 원하는 옵션으로 ./configure를 실행하기 전에 CFLAGS 환경 변수를 -DGC_BENCH=1로 설정해야 합니다. 다음 시퀀스가 ​​트릭을 수행해야 합니다.

예제 #4 GC 벤치마킹을 활성화하기 위해 PHP 재컴파일

export CFLAGS=-DGC_BENCH=1
./config.nice
make clean
make
                

새로 빌드된 PHP 바이너리로 위의 예제 코드를 다시 실행하면 PHP 실행이 완료된 후 다음이 표시되는 것을 볼 수 있습니다.

예제 #5 GC 통계

GC Statistics
-------------
Runs:               110
Collected:          2072204
Root buffer length: 0
Root buffer peak:   10000

      Possible            Remove from  Marked
        Root    Buffered     buffer     grey
      --------  --------  -----------  ------
ZVAL   7175487   1491291    1241690   3611871
ZOBJ  28506264   1527980     677581   1025731
                

가장 유익한 통계가 첫 번째 블록에 표시됩니다. 여기에서 가비지 수집 메커니즘이 110번 실행되었으며 총 110번의 실행 동안 2백만 개 이상의 메모리 할당이 해제되었음을 알 수 있습니다. 가비지 수집 메커니즘이 한 번 이상 실행되자마자 "루트 버퍼 피크"는 항상 10000입니다.


결론

일반적으로 PHP의 가비지 수집기는 주기 수집 알고리즘이 실제로 실행될 때만 속도를 저하시키는 반면, 일반(작은) 스크립트에서는 성능 저하가 전혀 없어야 합니다.

그러나 주기 수집 메커니즘이 일반 스크립트에 대해 실행되는 경우에 제공되는 메모리 감소로 인해 총 메모리가 그렇게 많이 사용되지 않기 때문에 서버에서 더 많은 스크립트를 동시에 실행할 수 있습니다.

이점은 긴 테스트 스위트 또는 데몬 스크립트와 같이 오래 실행되는 스크립트에서 가장 분명합니다. 또한 일반적으로 웹용 스크립트보다 더 오래 실행되는 » PHP-GTK 애플리케이션의 경우 새로운 메커니즘은 시간이 지남에 따라 서서히 들어오는 메모리 누수와 관련하여 상당한 차이를 만들어야 합니다.