>  기사  >  백엔드 개발  >  PHP 코어 - PHP 영혼 HashTble에 대한 간략한 설명

PHP 코어 - PHP 영혼 HashTble에 대한 간략한 설명

黄舟
黄舟원래의
2017-03-09 09:53:431660검색

하나. 머리말

HashTable은 PHP의 영혼입니다. HashTable 저장소에 적용되는 변수 테이블, 상수 테이블, 함수 테이블 등 Zend 엔진에서 널리 사용되는 HashTable이기 때문입니다. 또한, PHP의 배열도 HashTble을 이용하여 구현되므로 PHP의 HashTable을 이해해야만 PHP를 제대로 이해할 수 있습니다.


읽기의 편의를 위해 에 나오는 기본 개념은 다음과 같습니다. HashTable 구현. 해시 테이블은 해시 함수를 통해 특정 키를 특정 값에 매핑하는 데이터 구조입니다. 키와 값 사이의 일대일 대응을 유지합니다.

  • 키: PHP 배열의 인덱스, 문자열 키 등 데이터를 조작하는 데 사용되는 표시기입니다.

  • 슬롯/버킷: 해시 테이블에서 데이터를 저장하는 데 사용되는 단위로, 데이터가 실제로 저장되는 컨테이너입니다.

  • 해시 함수: 데이터가 저장되어야 하는 슬롯의 위치에 키를 매핑하는 함수입니다.

  • 해시 충돌: 해시 함수가 두 개의 서로 다른 키를 동일한 인덱스에 매핑하는 상황입니다.

PHP의 해시 테이블은 Zend/zend_hash.h에 구현되어 있습니다. 먼저 PHP 구현의 데이터 구조를 살펴보겠습니다. PHP는 해시 테이블을 구현하기 위해 다음 두 가지 데이터 구조를 사용합니다. HashTable 구조는 해시 테이블 전체에 필요한 기본 정보를 저장하는 구조이고, Bucket 구조는 특정 데이터 내용을 저장하는 구조입니다, (자세한 소스코드는 끝부분 참조)

둘. 예시

그럼 변수 생성을 예로 들면, 내부적으로는 정확히 무슨 일이 일어날까요?

변수 생성 단계: $str = "hello";

1: zval 구조를 생성하고 해당 유형을 IS_STRING으로 설정

2: 해당 값을 hello로 설정

3: 기호 테이블에 추가

{    
zval *fooval;     
MAKE_STD_ZVAL(fooval);    
ZVAL_STRING(fooval, "hello", 1);    
ZEND_SET_SYMBOL( EG(active_symbol_table) ,  "foo" , fooval);
}

처음 두 단계는 이전 글의 변수 구조에서 언급했는데, 자세히 PHP 커널 저장 메커니즘(분리/변경)

심볼 테이블이란 무엇입니까?

답변: 심볼 테이블은 해시 테이블입니다. 변수 이름 ->변수의 zval 구조체 주소

// zend/zend_globals.h 심볼 테이블 161줄

struct _zend_executor_globals { 
      ...   
      ...	
HashTable *active_symbol_table; /*活动符号表*/	
HashTable symbol_table;		/* 全局符号表 */	
HashTable included_files;	/* files already included */
     ...
}

함수가 실행되면 함수 이름, 매개변수, 실행 단계, 클래스(메서드인 경우), 해당 함수에 대해 생성된 심볼 테이블을 포함하여 함수의 "실행 환경 구조"가 생성됩니다. . 심볼 테이블은 스택에 함께 배치됩니다. 그리고 새로 생성된 심볼 테이블


Zend/zend_compiles.h 라인 384, 실행 환경 구조에 대해 active_symbol_table을 지정합니다.

struct _zend_execute_data {
	struct _zend_op *opline;
	zend_function_state function_state;
	zend_op_array *op_array;//函数编译后的执行逻辑,编译后的opcode二进制代码,称为op_array
	zval *object;
	HashTable *symbol_table;//此函数的符号表地址
	struct _zend_execute_data *prev_execute_data;
	zval *old_error_reporting;
	zend_bool nested;
	zval **original_return_value;
	zend_class_entry *current_scope;
	zend_class_entry *current_called_scope;
	zval *current_this;  
	struct _zend_op *fast_ret; /* used by FAST_CALL/FAST_RET (finally keyword) */
	call_slot *call_slots;
	call_slot *call;
};

위는 현재 함수가 실행되었을 때의 심볼 테이블이다.

다음 예제를 통해 PHP가 함수 실행 시 다양한 저장 공간을 할당하는 방식을 설명하고, PHP의 정적 변수를 공유할 수 있는 이유를 설명하겠습니다.

함수가 실행되면 함수 이름, 매개변수, 실행 단계, 클래스(해당되는 경우)를 포함하여 함수의 "실행 환경 구조"가 생성됩니다. 메소드) 그리고 이 함수에 대한 기호 테이블을 생성합니다. 그리고 active_symbol_table이 새로 생성된 기호 테이블을 가리킵니다.


解释:

1.执行t1时,形成t1的环境结构体,t1调入到执行栈,t1也有自己的符号表,符号表里边存储的变量对应这个t1环境(局部变量嘛)

2.执行t1到第三行,执行了t2,形成t2的环境结构体,t2入栈,t2也有自己的变量自己的符号表,与t1互不影响。

3.假使t1函数内部出现了递归调用t1,此时会生成第二个t1环境结构体,和【1】中是两个结构体,互不影响


函数执行时的栈变化

当函数调用时,为此函数生成了一个”执行环境变量”的结构体,里面存储了当前函数的名称,参数,对应的类....等等信息.称为_zend_execute_data {}结构体

struct _zend_execute_data {
	struct _zend_op *opline;
	zend_function_state function_state;
	zend_op_array *op_array;//函数编译后的执行逻辑,编译后的opcode二进制代码,称为op_array
	zval *object;
	HashTable *symbol_table;//此函数的符号表地址
	struct _zend_execute_data *prev_execute_data;
	zval *old_error_reporting;
	zend_bool nested;
	zval **original_return_value;
	zend_class_entry *current_scope;
	zend_class_entry *current_called_scope;
	zval *current_this;  
	struct _zend_op *fast_ret; /* used by FAST_CALL/FAST_RET (finally keyword) */
	call_slot *call_slots;
	call_slot *call;
};

这个结构体中,有2个重要的信息需要注意!:

{

*op_array ------>是函数的执行步骤,公用(静态变量字段存储于此!所以改一次依赖于此逻辑的函数全修改!)

*hash_table---->symbol_table 这个函数对应的符号表

}

 

思考一下: 1个函数,递归调用自己3次, 如t1

问:在栈上,肯定要有3个 execute_data生成.但是,这3个execute_data--->对应几个*op_array;

答:函数编译完了,生成一份*op_array,因为函数的执行逻辑是固定的.

 

问:生成了几个 symbol_table?

答:生成3个符号表.


结论:

1.每一个函数调用是都会生成自己的环境栈和符号表栈,不同的环境栈对应了自己的符号表栈,所以每个函数中的变量常量等,他们是有对应函数内的作用域限制

2.虽然每次会生成不同的环境栈与作用域,但是如果调用的是同一个函数,其 *op_array;是公用1份的,换句话说,t1递归调用自己,每次都会开辟一个环境栈区分独立,但是他们是同一个函数逻辑,所以op_array是一样的,而


三。其他

通过一个哈希算法,它总有碰撞的时候吧。PHP中的哈希表是使用拉链法来解决冲突 (具体点讲就是使用链表来存储哈希到同一个槽位的数据,Zend为了保存数据之间的关系使用了双向链表来链接元素)。

对于HashTable的初始化_zend_hash_init,

插入_zend_hash_add_or_update,

元素访问_zend_hash_add_or_find等操作,源码中有就不再这里叙述。


这样回头一想,变量表,常量表,函数表等,他们在PHP中都是靠HashTable来实现的,如[二]中叙述,hashtable是不是很强大呢?


Zend引擎哈希表结构和关系:


Zend/zend_hash.h 55行

typedef struct bucket {
	ulong h;						/* Used for numeric indexing */
	uint nKeyLength;
	void *pData;
	void *pDataPtr;
	struct bucket *pListNext;
	struct bucket *pListLast;
	struct bucket *pNext;
	struct bucket *pLast;
	const char *arKey;
} Bucket;

typedef struct _hashtable { 
    uint nTableSize;        // hash Bucket的大小,最小为8,以2x增长。
    uint nTableMask;        // nTableSize-1 , 索引取值的优化
    uint nNumOfElements;    // hash Bucket中当前存在的元素个数,count()函数会直接返回此值 
    ulong nNextFreeElement; // 下一个数字索引的位置
    Bucket *pInternalPointer;   // 当前遍历的指针(foreach比for快的原因之一)
    Bucket *pListHead;          // 存储数组头元素指针
    Bucket *pListTail;          // 存储数组尾元素指针
    Bucket **arBuckets;         // 存储hash数组
    dtor_func_t pDestructor;    // 在删除元素时执行的回调函数,用于资源的释放
    zend_bool persistent;       //指出了Bucket内存分配的方式。如果persisient为TRUE,则使用操作系统本身的内存分配函数为Bucket分配内存,否则使用PHP的内存分配函数。
    unsigned char nApplyCount; // 标记当前hash Bucket被递归访问的次数(防止多次递归)
    zend_bool bApplyProtection;// 标记当前hash桶允许不允许多次访问,不允许时,最多只能递归3次
#if ZEND_DEBUG
    int inconsistent;
#endif
} HashTable;


Zend/zend_compiles.h 261行,op_array结构代码

struct _zend_op_array {
	/* Common elements */
	zend_uchar type;
	const char *function_name;		
	zend_class_entry *scope;
	zend_uint fn_flags;
	union _zend_function *prototype;
	zend_uint num_args;
	zend_uint required_num_args;
	zend_arg_info *arg_info;
	/* END of common elements */

	zend_uint *refcount;

	zend_op *opcodes;
	zend_uint last;

	zend_compiled_variable *vars;
	int last_var;

	zend_uint T;

	zend_uint nested_calls;
	zend_uint used_stack;

	zend_brk_cont_element *brk_cont_array;
	int last_brk_cont;

	zend_try_catch_element *try_catch_array;
	int last_try_catch;
	zend_bool has_finally_block;

	/* static variables support */
	HashTable *static_variables;

	zend_uint this_var;

	const char *filename;
	zend_uint line_start;
	zend_uint line_end;
	const char *doc_comment;
	zend_uint doc_comment_len;
	zend_uint early_binding; /* the linked list of delayed declarations */

	zend_literal *literals;
	int last_literal;

	void **run_time_cache;
	int  last_cache_slot;

	void *reserved[ZEND_MAX_RESERVED_RESOURCES];
};



위 내용은 PHP 코어 - PHP 영혼 HashTble에 대한 간략한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.