Heim > Backend-Entwicklung > PHP-Tutorial > PHP-Kern – eine kurze Erklärung von PHP Soul HashTble

PHP-Kern – eine kurze Erklärung von PHP Soul HashTble

黄舟
Freigeben: 2023-03-06 13:26:01
Original
1720 Leute haben es durchsucht

Eins. Vorwort

HashTable ist die Seele von PHP, da HashTable in der Zend-Engine weit verbreitet ist, z. B. als Variablentabelle, Konstantentabelle, Funktionstabelle usw., die an den HashTable-Speicher angepasst sind. Darüber hinaus wird das PHP-Array auch mithilfe von HashTble implementiert. Nur ​​wenn Sie PHPs HashTable verstehen, können Sie PHP wirklich verstehen.


Um das Lesen zu erleichtern, finden Sie hier die grundlegenden Konzepte, die in auftauchen die Implementierung von HashTable . Eine Hash-Tabelle ist eine Datenstruktur, die über eine Hash-Funktion bestimmte Schlüssel bestimmten Werten zuordnet. Sie sorgt für eine Eins-zu-Eins-Entsprechung zwischen Schlüsseln und Werten.

  • Schlüssel: Ein Indikator, der zum Bearbeiten von Daten verwendet wird, z. B. ein Index in einem PHP-Array oder ein Zeichenfolgenschlüssel usw.

  • Slot/Bucket: Eine Einheit in der Hash-Tabelle, die zum Speichern von Daten verwendet wird. Dabei handelt es sich um den Container, in dem die Daten tatsächlich gespeichert werden.

  • Hash-Funktion: Eine Funktion, die den Schlüssel dem Speicherort des Steckplatzes zuordnet, in dem die Daten gespeichert werden sollen.

  • Hash-Kollision: Eine Situation, in der eine Hash-Funktion zwei verschiedene Schlüssel demselben Index zuordnet.

Die Hash-Tabelle in PHP ist in Zend/zend_hash.h implementiert. Schauen wir uns zunächst die Datenstruktur in der PHP-Implementierung an. PHP verwendet die folgenden zwei Datenstrukturen, um die Hash-Tabelle zu implementieren. Die HashTable-Struktur wird zum Speichern der grundlegenden Informationen verwendet, die für die gesamte Hash-Tabelle benötigt werden, während die Bucket-Struktur zum Speichern des spezifischen Dateninhalts verwendet wird, (den spezifischen Quellcode finden Sie am Ende)

Zwei. Beispiel

Am Beispiel der Erstellung einer Variablen: Was passiert genau unter der Haube?

Schritte zum Erstellen von Variablen: $str = "hello";

1: Erstellen Sie eine Zval-Struktur und legen Sie ihren Typ IS_STRING fest

2: Setzen Sie ihren Wert auf Hallo

3: Zur Symboltabelle hinzufügen

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

Die ersten beiden Schritte wurden in der Variablenstruktur des vorherigen Artikels erwähnt, Weitere Informationen finden Sie unter PHP-Kernel-Speichermechanismus (Trennung/Änderung)

Was ist die Symboltabelle?

Antwort: Die Symboltabelle ist eine Hash-Tabelle . Der Variablenname wird darin gespeichert -> Die Adresse der zval-Struktur der Variablen

// zend/zend_globals.h 161 Zeilen der Symboltabelle

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

Wenn eine Funktion ausgeführt wird, wird die „Ausführungsumgebungsstruktur“ der Funktion generiert, einschließlich Funktionsname, Parameter, Ausführungsschritte, Klasse (falls es sich um eine Methode handelt), und eine für diese Funktion generierte Symboltabelle wird einheitlich auf dem Stapel platziert. Und die active_symbol_table zeigt auf die neu generierte Symboltabelle


Zend/zend_compiles .h Zeile 384, Struktur der Ausführungsumgebung:

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;
};
Nach dem Login kopieren
Nach dem Login kopieren

Das Obige ist die Symboltabelle, wenn die aktuelle Funktion ausgeführt wird.

Anhand des folgenden Beispiels beschreiben wir die Zuweisung verschiedener Speicherplätze durch PHP während der Funktionsausführung und erklären, warum die statischen Variablen von PHP gemeinsam genutzt werden können.

Wenn eine Funktion ausgeführt wird, wird die „Ausführungsumgebungsstruktur“ der Funktion generiert, einschließlich des Funktionsnamens, der Parameter, der Ausführungsschritte und der Klasse (falls vorhanden). ist eine Methode) Und generieren Sie eine Symboltabelle für diese Funktion. Und zeigen Sie active_symbol_table auf die neu generierte Symboltabelle


解释:

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;
};
Nach dem Login kopieren
Nach dem Login kopieren

这个结构体中,有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;
Nach dem Login kopieren


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];
};
Nach dem Login kopieren



Das obige ist der detaillierte Inhalt vonPHP-Kern – eine kurze Erklärung von PHP Soul HashTble. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Verwandte Etiketten:
Quelle:php.cn
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage