-
- struct _zval_struct {
- /* 변수 정보 */
- zvalue_value value; /* value */
- zend_uint refcount__gc;
- zend_uchar type; /* 활성 유형 */
- zend_uchar is_ref__gc;
- };
코드 복사
참조 횟수 필드는 PHP5.3 이전 버전과 비교됩니다. refcount 및 is_ref 필드를 참조할지 여부는 새로운 가비지 수집 메커니즘을 위해 그 뒤에 __gc를 추가해야 합니다. PHP 소스 코드 스타일에서는 매크로가 많다는 것이 매우 특징적입니다. 이러한 매크로는 ALLOC_ZVAL 매크로와 같은 인터페이스 계층 아래의 일부 기본 구현을 보호하는 인터페이스 계층과 동일합니다. PHP5.3 이전에 이 매크로는 할당된 메모리 크기를 결정하기 위해 PHP의 메모리 관리 할당 함수 emalloc을 직접 호출했습니다. 변수의 유형과 크기가 결정됩니다. 가비지 수집 메커니즘이 도입된 후 ALLOC_ZVAL 매크로는 새로운 가비지 수집 단위 구조를 직접 채택하며, 이는 모두 zval_gc_info 구조가 차지하는 메모리 크기입니다. 이 구조는 초기화된 메커니즘입니다.
-
- /* 다음 매크로는 zend_alloc.h의 매크로를 재정의합니다. */
- #undef ALLOC_ZVAL
- #define ALLOC_ZVAL(z)
- do {
- (z) = (zval*)emalloc(sizeof(zval_gc_info));
- GC_ZVAL_INIT(z)
- } while (0)
코드 복사
typedef struct _zval_gc_info {- zval z;
- union {
- gc_root_buffer *buffered;
- struct _zval_gc_info *next;
- } u;
- } zval_gc_info;
-
-
코드 복사
ZVAL 컨테이너에 저장된 모든 변수에 대해 zval 구조가 할당되어 해당 합계가 보장됩니다. zval_gc_info 타입 포인터가 캐스팅될 때 zval로 사용될 수 있도록 zval 변수에 할당된 메모리를 처음에 정렬합니다. zval 필드 뒤에 공용체가 있습니다: u. u에는 gc_root_buffer 구조의 버퍼링된 필드와 zval_gc_info 구조의 다음 필드가 포함됩니다. 이 두 필드 중 하나는 가비지 수집 메커니즘에 의해 캐시된 루트 노드를 나타내고, 다른 하나는 zval_gc_info 목록의 다음 노드를 나타냅니다. 가비지 수집 메커니즘에 의해 캐시된 노드가 루트 노드로 사용되는지 아니면 목록 노드로 사용되는지는 알 수 있습니다. 여기에 반영됩니다. ALLOC_ZVAL은 zval을 대체하는 zval_gc_info를 초기화하기 위해 메모리를 할당한 후 GC_ZVAL_INIT를 호출합니다. zval_gc_info에 있는 멤버 u의 버퍼링된 필드는 가비지 수집 버퍼에 들어갈 때만 값을 갖습니다. NULL이어야 합니다. PHP의 모든 변수는 zval 변수의 형태로 존재하므로 여기에서는 zval_gc_info를 사용하여 zval을 대체함으로써 원래 시스템에 가비지 수집 메커니즘을 성공적으로 통합했습니다.
PHP의 가비지 수집 메커니즘은 PHP5.3에서 기본적으로 활성화되어 있지만 구성 파일을 통해 직접 비활성화하도록 설정할 수 있습니다: zend.enable_gc. 기본적으로 php.ini 파일에는 이 필드가 없습니다. 이 기능을 비활성화해야 하는 경우 php.ini에 zend.enable_gc=0 또는 zend.enable_gc=off를 추가하세요. php.ini 구성 zend.enable_gc를 수정하는 것 외에도 gc_enable()/gc_disable() 함수를 호출하여 가비지 수집 메커니즘을 켜거나 끌 수도 있습니다. 이러한 함수를 호출하는 효과는 가비지 수집 메커니즘을 켜거나 끄기 위해 구성 항목을 수정하는 것과 같습니다. 이 두 가지 함수 외에도 PHP는 루트 버퍼가 가득 차지 않을 때 주기 재활용을 강제하는 gc_collect_cycles() 함수를 제공합니다. PHP 소스 코드에는 가비지 수집 메커니즘이 켜져 있는지 여부와 관련된 몇 가지 작업 및 필드가 있습니다.
zend.c 파일에는 다음 코드가 있습니다.
static ZEND_INI_MH(OnUpdateGCEnabled) /* {{{ */- {
- OnUpdateBool(entry, new_value, new_value_length, mh_arg1, mh_arg2, mh_arg3 , 스테이지 TSRMLS_CC);
- if (GC_G(gc_enabled)) {
- gc_init(TSRMLS_C);
- }
- return SUCCESS;
- }
- /* }}} */
- ZEND_INI_BEGIN()
- ZEND_INI_ENTRY("error_reporting", NULL, ZEND_INI_ALL, OnUpdateErrorReporting)
- STD_ZEND_INI_BOOLEAN("zend.enable_gc", "1", ZEND_INI_ALL, OnUpdateGCEnabled, gc_enabled, zend_gc_globals, gc s)
- #ifdef ZEND_MULTIBYTE
- STD_ZEND_INI_BOOLEAN ("Detect_unicode", "1", Zend_ini_all, OnupdateBool, DETECT_UNICODE, ZEND_COMPILOBALS, COMPILOROBALS) if ZEND_INI_END ()
-
-
- 코드 복사
-
zend.enable_gc의 해당 연산 함수는 ZEND_INI_MH(OnUpdateGCEnabled)입니다. 가비지 수집 메커니즘이 켜져 있는 경우, 즉 GC_G(gc_enabled)가 true이면 gc_init 함수가 호출되어 가비지 초기화 연산을 수행하게 됩니다. 수집 메커니즘. gc_init 함수는 zend/zend_gc.c의 121번째 줄에 있습니다. 이 함수는 가비지 수집 메커니즘이 켜져 있는지 여부를 결정합니다. 켜져 있으면 전체 메커니즘이 초기화됩니다. 즉, 할당을 위해 malloc이 직접 호출됩니다. 전체 캐시 목록에 10,000 gc_root_buffer 메모리 공간. 여기서 10000은 코드에 하드 코딩되어 있으며 매크로 GC_ROOT_BUFFER_MAX_ENTRIES로 존재합니다. 이 값을 수정해야 하는 경우 소스 코드를 수정하고 PHP를 다시 컴파일해야 합니다. gc_init 함수는 메모리를 사전 할당한 후 gc_reset 함수를 호출하여 gc 실행 횟수(gc_runs) 및 gc의 가비지 수(수집)에 대한 통계를 0으로 설정하는 등 전체 메커니즘에 사용되는 일부 전역 변수를 재설정합니다. , 이중 연결 리스트의 헤드 노드를 설정하는 등 이전 노드와 다음 노드가 자신을 가리킵니다. 가비지 수집 메커니즘에 사용되는 언급된 전역 변수 외에도 일반적으로 사용되는 다른 변수가 있으며 그 중 일부는 아래에 설명되어 있습니다.
-
- typedef struct _zend_gc_globals {
- zend_bool gc_enabled; /* 가비지 수집 메커니즘 활성화 여부*/
- zend_bool gc_active; 진행 중*/
- gc_root_buffer *buf; /* 사전 할당된 버퍼 배열, 기본값은 10000(사전 할당된 버퍼 배열) */
- gc_root_buffer 루트 /* 목록의 루트 노드(가능한 주기 루트 목록) */
- gc_root_buffer *unused; /* 사용되지 않은 버퍼 목록 */
- gc_root_buffer *first_unused; /* 사용되지 않은 첫 번째 버퍼 노드 버퍼에 대한 포인터 */
- gc_root_buffer *last_unused /* 사용되지 않은 마지막 버퍼를 가리킵니다. 버퍼 노드, 여기에 마지막으로 사용되지 않은 버퍼에 대한 포인터가 있습니다. */
- zval_gc_info *zval_to_free /* 해제할 zval의 임시 목록이 됩니다. */
- zval_gc_info *free_list /* 임시 변수, 해당 목록의 시작; 해제해야 함 */
- zval_gc_info *next_to_free; /* 임시 변수, 다음 해제할 변수 위치 */
- zend_uint gc_runs; /* gc 실행 횟수 통계 */
- zend_uint ; /* gc의 쓰레기 수*/
- // 생략...
- }
코드 복사
설정되지 않은 작업을 사용하는 경우 이 변수가 차지하는 메모리를 지우면(참조 카운트를 1씩 감소시킬 수도 있음) 현재 기호에서 가져옵니다. 변수 이름에 해당하는 항목은 모든 작업이 수행된 후 해시 테이블에서 삭제됩니다. 심볼 테이블에서 삭제된 항목은 임시 변수가 zval_dtor를 호출하고 일반 변수가 zval_ptr_dtor를 호출합니다.
물론 unset 함수는 언어 구조이기 때문에 PHP 함수 집합에서 찾을 수 없습니다. 해당 중간 코드는 ZEND_UNSET이며, Zend/zend_vm_execute.h 파일에서 관련 구현을 찾을 수 있습니다.
zval_ptr_dtor는 함수가 아니며, 함수처럼 보이는 매크로일 뿐입니다. Zend/zend_variables.h 파일에서 이 매크로는 _zval_ptr_dtor 함수를 가리킵니다. Zend/zend_execute_API.c 424번째 줄의 함수 관련 코드는 다음과 같습니다.
-
- ZEND_API void _zval_ptr_dtor(zval **zval_ptr ZEND_FILE_LINE_DC) /* {{{ */
- {
- #if DEBUG_ZEND>=2
- printf("%x(%x)에 대한 참조 횟수 감소: %d->%dn", *zval_ptr, zval_ptr, Z_REFCOUNT_PP(zval_ptr), Z_REFCOUNT_PP(zval_ptr) - 1);
- #endif
- Z_DELREF_PP (zval_ptr);
- if (Z_REFCOUNT_PP(zval_ptr) == 0) {
- TSRMLS_FETCH();
- if (*zval_ptr != &EG(uninitialized_zval)) {
- GC_REMOVE_ZVAL_FROM_BUFFER(*zval_ptr);
- zval_dtor(*zval_ptr);
- efree_rel(*zval_ptr);
- }
- } else {
- TSRMLS_FETCH();
- if (Z_REFCOUNT_PP(zval_ptr) == 1) {
- Z_UNSET_ISREF_PP(zval_ptr);
- }
- GC_ZVAL_CHECK_POSSIBLE_ROOT(*zval_ptr);
- }
- }
- /* }}} */
코드 복사
코드에서 이 zval의 소멸 프로세스를 명확하게 볼 수 있습니다. 참조 카운팅 필드에서 다음 두 가지 작업이 수행됩니다.
변수의 참조 카운트가 1인 경우, 즉 1을 감소시킨 후 참조 카운트가 0이 되면 해당 변수는 바로 지워집니다. 현재 변수가 캐시되어 있으면 캐시를 지워야 합니다. 변수의 참조 횟수가 1보다 크면, 즉 1을 뺀 참조 횟수가 0보다 크면 해당 변수는 가비지 목록에 배치됩니다. 변경사항에 참조가 있는 경우 해당 참조를 제거하세요.
가비지 목록에 변수를 넣는 작업은 GC_ZVAL_CHECK_POSSIBLE_ROOT이며, 이 역시 매크로이며 gc_zval_check_possible_root 함수에 해당하지만 이 함수는 배열 및 객체에 대한 가비지 수집 작업만 수행합니다. 배열 및 개체 변수의 경우 gc_zval_possible_root 함수를 호출합니다.
-
- ZEND_API void gc_zval_possible_root(zval *zv TSRMLS_DC)
- {
- if (UNEXPECTED(GC_G(free_list) != NULL &&
- GC_ZVAL_ADDRESS (zv) != NULL &&
- GC_ZVAL_GET_COLOR(zv) == GC_BLACK) &&
- (GC_ZVAL_ADDRESS(zv) < GC_G(buf) ||
- GC_ZVAL_ADDRESS(zv) >= GC_G(last_unused)) ) {
- /* 주어진 zval은
- * 현재 실행 중인 GC에 의해 삭제될 쓰레기입니다 */
- return;
- }
- if (zv->type == IS_OBJECT) {
- GC_ZOBJ_CHECK_POSSIBLE_ROOT(zv);
- 반환;
- }
- GC_BENCH_INC(zval_possible_root);
- if (GC_ZVAL_GET_COLOR(zv) != GC_PURPLE) {
- GC_ZVAL_SET_ RPLE(zv) ;
- if (!GC_ZVAL_ADDRESS(zv)) {
- gc_root_buffer *newRoot = GC_G(미사용);
- if (newRoot) {
- GC_G(미사용) = newRoot->prev;
- } else if (GC_G(first_unused) != GC_G(last_unused)) {
- newRoot = GC_G(first_unused);
- GC_G(first_unused) ;
- } else {
- if (!GC_G(gc_enabled) ) {
- GC_ZVAL_SET_BLACK(zv);
- 반환;
- }
- zv->refcount__gc ;
- gc_collect_cycles(TSRMLS_C);
- zv->refcount__gc--;
- newRoot = GC_G(미사용);
- if (!newRoot) {
- return;
- }
- GC_ZVAL_SET_PURPLE(zv);
- GC_G(미사용) = newRoot->prev;
- }
- newRoot->next = GC_G(roots).next;
- newRoot->prev = &GC_G(roots);
- GC_G(roots).next->prev = newRoot;
- GC_G(루트).next = newRoot;
- GC_ZVAL_SET_ADDRESS(zv, newRoot);
- newRoot->handle = 0;
- newRoot->u.pz = zv;
- GC_BENCH_INC(zval_buffered) ;
- GC_BENCH_INC(root_buf_length);
- GC_BENCH_PEAK(root_buf_peak, root_buf_length);
- }
- }
- }
코드 복사
앞서 언급했듯이 gc_zval_check_possible_root 함수는 배열과 객체에 대해서만 가비지 수집 작업을 수행합니다. 그러나 gc_zval_possible_root 함수에서는 객체 유형의 변수에 대해 GC_ZOBJ_CHECK_POSSIBLE_ROOT 매크로가 호출됩니다. 가비지 수집 메커니즘에 사용할 수 있는 다른 변수 유형의 경우 호출 프로세스는 다음과 같습니다.
zval 노드 정보가 노드 버퍼에 들어 있는지 확인하세요. 노드 버퍼에 들어 있었다면 직접 반환해야 성능을 최적화할 수 있습니다. 그런 다음 객체 노드를 처리하고 후속 작업을 수행하지 않고 직접 반환하여 노드가 보라색으로 표시되었는지 확인합니다. 보라색이면 더 이상 노드 버퍼에 추가되지 않습니다. 노드를 한 번만 버퍼링합니다.
노드 색상을 보라색으로 표시하여 해당 노드가 버퍼에 추가되었으므로 다음에 추가할 필요가 없음을 나타냅니다.
새 노드의 위치를 찾아 버퍼가 가득 차면 가비지 수집을 수행합니다.
버퍼가 있는 이중 연결 리스트에 새 노드를 추가합니다.
gc_zval_possible_root 함수에서 버퍼가 가득 차면 프로그램은 gc_collect_cycles 함수를 호출하여 가비지 수집 작업을 수행합니다. 가장 중요한 단계는 다음과 같습니다.
628행은 공식 문서에 있는 알고리즘의 B단계입니다. 알고리즘은 가능한 모든 근을 찾기 위해 깊이 우선 검색을 사용합니다. 이를 찾은 후 각 변수 컨테이너의 참조 횟수가 1씩 감소합니다. 감소되지 않음 두 개의 "1"은 1을 뺀 회색 표시로 표시됩니다.
629행 이는 알고리즘의 C단계로, 알고리즘은 다시 각 루트 노드에 대해 깊이 우선 검색을 사용하여 각 변수 컨테이너의 참조 카운트를 확인합니다. 참조 횟수가 0이면 변수 컨테이너가 흰색으로 표시됩니다. 참조 카운트가 0보다 크면 깊이 우선 탐색을 사용하여 이 지점에서 참조 카운트를 감소시킨(즉, 참조 카운트를 1씩 늘림) 작업을 재개한 다음 검정색으로 다시 표시합니다.
Line 630 알고리즘의 마지막 단계 D인 알고리즘은 루트 버퍼를 순회하여 거기에서 변수 컨테이너 루트(zval 루트)를 제거하는 동시에 이전 단계에서 흰색으로 표시된 변수 컨테이너가 있는지 확인합니다. . 흰색으로 표시된 각 변수 컨테이너가 지워집니다. [gc_collect_cycles() -> gc_collect_roots() -> zval_collect_white() ]에서 흰색으로 표시된 노드가 전역 변수 zval_to_free 목록에 추가되는 것을 볼 수 있습니다. 이 목록은 나중에 사용됩니다.
PHP의 가비지 수집 메커니즘은 실행 중에 상태를 4가지 색상으로 표시합니다.
GC_WHITE 흰색은 쓰레기를 의미합니다.
GC_PURPLE 보라색은 버퍼에 넣었음을 의미합니다.
GC_GREY 회색은 refcount 감소 작업이 수행되었음을 나타냅니다.
GC_BLACK 검정색이 기본 색상이며 일반 색상입니다.
관련 태그 및 작업 코드는 다음과 같습니다.
-
- #define GC_COLOR 0x03
- #define GC_BLACK 0x00
- #define GC_WHITE 0x01
- #define GC_GREY 0x02
- #define GC_PURPLE 0x03
- #define GC_ADDRESS(v)
- ((gc_root_buffer*)((zend_uintptr_t)(v)) & ~GC_COLOR))
- #define GC_SET_ADDRESS(v, a)
- (v) = ( ( gc_root_buffer*)((((zend_uintptr_t)(v)) & GC_COLOR) | ((zend_uintptr_t)(a))))
- #define GC_GET_COLOR(v)
- (((zend_uintptr_t)(v)) & GC_COLOR )
- #define GC_SET_COLOR(v, c)
- (v) = ((gc_root_buffer*)(((zend_uintptr_t)(v)) & ~GC_COLOR) | (c)))
- #define GC_SET_BLACK( v)
- (v) = ((gc_root_buffer*)(((zend_uintptr_t)(v)) & ~GC_COLOR))
- #define GC_SET_PURPLE(v)
- (v) = ((gc_root_buffer* )( ((zend_uintptr_t)(v)) | GC_PURPLE))
코드 복사
위의 비트로 상태를 표시하는 방법은 PHP 소스 코드에서 자주 사용됩니다. , 메모리 관리 등과 같은 이것은 상대적으로 효율적이고 경제적인 솔루션입니다. 그러나 데이터베이스를 설계할 때 필드에 이 방법을 사용하지 못할 수도 있습니다. 보다 직관적이고 읽기 쉬운 방식으로 구현해야 합니다.
|