検索

約束どおり、私もこのシリーズの記事を書き始めます。 zval の変更点について話す前に、zval について見てみましょう。 PHP5 ではどのようになりますか

PHP5

zval のレビュー

PHP5 では、zval の定義は次のとおりです:

struct _zval_struct { union { long lval; double dval; struct { char *val; int len;        } str;        HashTable *ht;        zend_object_value obj;        zend_ast *ast;    } value;    zend_uint refcount__gc;    zend_uchar type;    zend_uchar is_ref__gc;};

zval はすべてのデータを表すことができるため、PHP5 カーネルを知っている学生はこの構造に精通しているはずです。したがって、この zval が格納する値のタイプを示す type フィールドが含まれています。一般的なオプションは、IS_NULL、IS_LONG、IS_STRING、IS_ARRAY、IS_OBJECT などです。

type フィールドの値に応じて、次のことを行う必要があります。 use Different value の値を共用体として解釈します。たとえば、type が IS_STRING の場合は、value.str を使用して zval.value フィールドを解釈する必要があり、type が IS_LONG の場合は、value を使用する必要があります。

さらに、PHP は基本的なガベージ コレクションに参照カウントを使用することがわかっているため、zval には refcount__gc フィールドがあり、この zval への参照の数を示しますが、ここで注意すべき点が 1 つあります。 5.3、このフィールドの名前は refcount とも呼ばれます。5.3 以降、循環参照カウントを処理するために新しいガベージ コレクション アルゴリズムが導入されたとき、作成者はエラーの表示を高速化するために多数のマクロを追加しました。 refcount__gc という名前に変更され、refcount を操作するように強制されました。

同様に、この値は PHP の型が参照であるかどうかを示します

。これは、PHP5 時代の zval です。2013 年に PHP5 の opcache JIT に取り組んでいたとき、JIT のパフォーマンスが低かったため、この構造を書き直すことから PHPNG プロジェクトが始まりました。問題

PHP5 の zval 定義は Zend Engine 2 で誕生しました。時間が経つにつれて、当時の設計の限界がますます明らかになりました:

まず第一に、この構造のサイズは (64 ビット システム上では) ) 24 バイト、この zval.value の組み合わせを詳しく見てみましょう。その中で、zend_object_value が最大の長いボードです。これにより、値全体が 16 バイト必要になります。これは、移動して置き換えるなどの最適化が簡単です。結局のところ、IS_OBJECT は最も一般的に使用されるタイプではないためです。

第二に、この構造体の各フィールドには明確な意味があり、その結果、多くの最適化を行う場合にはカスタム フィールドが予約されません。 PHP5 の時代では、zval に関連する情報を保存する必要がある場合、zval を拡張するために他の構造マッピング、または外部のパッケージ化とパッチを使用する必要がありました。たとえば、5.3 では循環参照を解決するために新しい GC が導入されました。次の比較を使用してはなりません ハックメソッド:

/* The following macroses override macroses from zend_alloc.h */ #undef ALLOC_ZVAL#define ALLOC_ZVAL(z)                                   \ do {                                                \        (z) = (zval*)emalloc(sizeof(zval_gc_info));     \ GC_ZVAL_INIT(z);                                \    } while (0)

これは、zval_gc_info を使用して zval の割り当てをハイジャックします:

typedef struct _zval_gc_info {    zval z; union {        gc_root_buffer       *buffered; struct _zval_gc_info *next;    } u;} zval_gc_info;

その後、zval_gc_info を使用して zval を展開します。つまり、実際に PHP5 時代に zval を適用するときは、 、実際には 32 バイトを割り当てますが、実際には GC は IS_ARRAY と IS_OBJECT タイプのみを考慮する必要があるため、大量のメモリを浪費します

また、以前に作成した Taint 拡張機能と同様に、いくつかのタグを保存する必要があります。いくつかの文字列があり、zval には場所がありません。これは使用できるので、特別な手段を使用する必要がありました:

Z_STRVAL_PP(ppzval) = erealloc(Z_STRVAL_PP(ppzval), Z_STRLEN_PP(ppzval) + 1 + PHP_TAINT_MAGIC_LENGTH); PHP_TAINT_MARK(*ppzval, PHP_TAINT_MAGIC_POSSIBLE);

文字列の長さを int で拡張してから、マジックナンバーでマークして書き込むだけです。このアプローチは技術的にはより安全で安定しているという保証はありません

第三に、PHP の zval のほとんどは値によって渡され、値は書き込み時にコピーされます。ただし、オブジェクトとリソースの 2 つの例外があります。それらは常に参照によって渡されるため、zval の参照カウントに加えて、オブジェクトとリソースもメモリを確実にリサイクルできるようにするためのグローバル参照カウントが必要になります。たとえば、これには 2 つの参照カウントのセットがあり、1 つは zval 内にあり、もう 1 つは obj 自体のカウントです。

typedef struct _zend_object_store_bucket {    zend_bool destructor_called;    zend_bool valid; union _store_bucket { struct _store_object { void *object; zend_objects_store_dtor_t dtor; zend_objects_free_object_storage_t free_storage; zend_objects_store_clone_t clone; const zend_object_handlers *handlers;            zend_uint refcount;            gc_root_buffer *buffered;        } obj; struct { int next;        } free_list;    } bucket;} zend_object_store_bucket;

上記の 2 つの参照セットに加えて、オブジェクトを取得したい場合は、次の方法で行う必要があります:

EG(objects_store).object_buckets[Z_OBJ_HANDLE_P(z )].bucket.obj

長くて多くのメモリ読み取りの後、実際のオブジェクトオブジェクト自体を効率的に取得できます。

これはすべて、Zend エンジンが最初に設計されたときに考慮されていなかったためであり、一度事故が発生すると、全体の構造が複雑になり、保守性が低下します。例

4 番目に、PHP では多くの計算が文字列指向であることがわかっていますが、参照カウントは zval で機能するため、文字列型の zval をコピーしたい場合は何も存在しません。 PHP5.4でINTERNED STRINGを導入しましたが、zval文字列を配列に追加する場合は文字列をコピーするしかありません。この問題

还比如, PHP中大量的结构体都是基于Hashtable实现的, 增删改查Hashtable的操作占据了大量的CPU时间, 而字符串要查找首先要求它的Hash值, 理论上我们完全可以把一个字符串的Hash值计算好以后, 就存下来, 避免再次计算等等

第五, 这个是关于引用的, PHP5的时代, 我们采用写时分离, 但是结合到引用这里就有了一个经典的性能问题:

<?php   function dummy($array) {}   $array = range(1, 100000);   $b = &$array;   dummy($b); ?>

当我们调用array_count的时候, 本来只是简单的一个传值就行的地方, 但是因为$b 是一个引用, 就必须发生分离, 导致数组复制, 从而极大的拖慢性能, 这里有一个简单的测试:

<?php $array = range(1, 100000);  function dummy($array) {}  $i = 0; $start = microtime(true); while($i++ < 100) {  dummy($array); }  printf("Used %sS\n", microtime(true) - $start);  $b = &$array; //注意这里, 假设我不小心把这个Array引用给了一个变量 $i = 0; $start = microtime(true); while($i++ < 100) {  dummy($array); } printf("Used %sS\n", microtime(true) - $start); ?>

我们在5.6下运行这个例子, 得到如下结果:

$ php-5.6/sapi/cli/php /tmp/1.phpUsed 0.00045204162597656SUsed 4.2051479816437S

相差1万倍之多. 这就造成, 如果在一大段代码中, 我不小心把一个变量变成了引用(比如foreach as &$v), 那么就有可能触发到这个问题, 造成严重的性能问题, 然而却又很难排查.

第六, 也是最重要的一个, 为什么说它重要呢? 因为这点促成了很大的性能提升, 我们习惯了在PHP5的时代调用MAKE_STD_ZVAL在堆内存上分配一个zval, 然后对他进行操作, 最后呢通过RETURN_ZVAL把这个zval的值"copy"给return_value, 然后又销毁了这个zval, 比如pathinfo这个函数:

PHP_FUNCTION(pathinfo){..... MAKE_STD_ZVAL(tmp); array_init(tmp);.... if (opt == PHP_PATHINFO_ALL) { RETURN_ZVAL(tmp, 0, 1);    } else {.....}

这个tmp变量, 完全是一个临时变量的作用, 我们又何必在堆内存分配它呢? MAKE_STD_ZVAL/ALLOC_ZVAL在PHP5的时候, 到处都有, 是一个非常常见的用法, 如果我们能把这个变量用栈分配, 那无论是内存分配, 还是缓存友好, 都是非常有利的

还有很多, 我就不一一详细列举了, 但是我相信你们也有了和我们当时一样的想法, zval必须得改改了, 对吧?

PHP7

现在的zval

到了PHP7中, zval变成了如下的结构, 要说明的是, 这个是现在的结构, 已经和PHPNG时候有了一些不同了, 因为我们新增加了一些解释 (联合体的字段), 但是总体大小, 结构, 是和PHPNG的时候一致的:

struct _zval_struct { union {        zend_long         lval; /* long value */ double dval; /* double value */ zend_refcounted  *counted;        zend_string      *str;        zend_array       *arr;        zend_object      *obj;        zend_resource    *res;        zend_reference   *ref;        zend_ast_ref     *ast;        zval             *zv; void *ptr;        zend_class_entry *ce;        zend_function    *func; struct { uint32_t w1; uint32_t w2;        } ww;    } value; union { struct { ZEND_ENDIAN_LOHI_4(                zend_uchar    type, /* active type */ zend_uchar    type_flags,                zend_uchar    const_flags,                zend_uchar    reserved) /* call info for EX(This) */ } v; uint32_t type_info;    } u1; union { uint32_t var_flags; uint32_t next; /* hash collision chain */ uint32_t cache_slot; /* literal cache slot */ uint32_t lineno; /* line number (for ast nodes) */ uint32_t num_args; /* arguments number for EX(This) */ uint32_t fe_pos; /* foreach position */ uint32_t fe_iter_idx; /* foreach iterator index */ } u2;};

虽然看起来变得好大, 但其实你仔细看, 全部都是联合体, 这个新的zval在64位环境下,现在只需要16个字节(2个指针size), 它主要分为俩个部分, value和扩充字段, 而扩充字段又分为u1和u2俩个部分, 其中u1是type info, u2是各种辅助字段.

其中value部分, 是一个size_t大小(一个指针大小), 可以保存一个指针, 或者一个long, 或者一个double.

而type info部分则保存了这个zval的类型. 扩充辅助字段则会在多个其他地方使用, 比如next, 就用在取代Hashtable中原来的拉链指针, 这部分会在以后介绍HashTable的时候再来详解.

类型

PHP7中的zval的类型做了比较大的调整, 总体来说有如下17种类型:

/* regular data types */ #define IS_UNDEF 0 #define IS_NULL 1 #define IS_FALSE 2 #define IS_TRUE 3 #define IS_LONG 4 #define IS_DOUBLE 5 #define IS_STRING 6 #define IS_ARRAY 7 #define IS_OBJECT 8 #define IS_RESOURCE 9 #define IS_REFERENCE 10 /* constant expressions */ #define IS_CONSTANT 11 #define IS_CONSTANT_AST 12 /* fake types */ #define _IS_BOOL 13 #define IS_CALLABLE 14 /* internal types */ #define IS_INDIRECT 15 #define IS_PTR 17

其中PHP5的时候的IS_BOOL类型, 现在拆分成了IS_FALSE和IS_TRUE俩种类型. 而原来的引用是一个标志位, 现在的引用是一种新的类型.

对于IS_INDIRECT和IS_PTR来说, 这俩个类型是用在内部的保留类型, 用户不会感知到, 这部分会在后续介绍HashTable的时候也一并介绍.

从PHP7开始, 对于在zval的value字段中能保存下的值, 就不再对他们进行引用计数了, 而是在拷贝的时候直接赋值, 这样就省掉了大量的引用计数相关的操作, 这部分类型有:

IS_LONGIS_DOUBLE

当然对于那种根本没有值, 只有类型的类型, 也不需要引用计数了:

IS_NULLIS_FALSEIS_TRUE

而对于复杂类型, 一个size_t保存不下的, 那么我们就用value来保存一个指针, 这个指针指向这个具体的值, 引用计数也随之作用于这个值上, 而不在是作用于zval上了. 以IS_ARRAY为例:

struct _zend_array {    zend_refcounted_h gc; union { struct { ZEND_ENDIAN_LOHI_4(                zend_uchar    flags,                zend_uchar    nApplyCount,                zend_uchar    nIteratorsCount,                zend_uchar    reserve)        } v; uint32_t flags;    } u; uint32_t nTableMask;    Bucket           *arData; uint32_t nNumUsed; uint32_t nNumOfElements; uint32_t nTableSize; uint32_t nInternalPointer;    zend_long         nNextFreeElement; dtor_func_t pDestructor;};

zval.value.arr将指向上面的这样的一个结构体, 由它实际保存一个数组, 引用计数部分保存在zend_refcounted_h结构中:

typedef struct _zend_refcounted_h { uint32_t refcount; /* reference counter 32-bit */ union { struct { ZEND_ENDIAN_LOHI_3(                zend_uchar    type,                zend_uchar    flags, /* used for strings & objects */ uint16_t gc_info) /* keeps GC root number (or 0) and color */ } v; uint32_t type_info;    } u;} zend_refcounted_h;

所有的复杂类型的定义, 开始的时候都是zend_refcounted_h结构, 这个结构里除了引用计数以外, 还有GC相关的结构. 从而在做GC回收的时候, GC不需要关心具体类型是什么, 所有的它都可以当做zend_refcounted*结构来处理.

另外有一个需要说明的就是大家可能会好奇的ZEND_ENDIAN_LOHI_4宏, 这个宏的作用是简化赋值, 它会保证在大端或者小端的机器上, 它定义的字段都按照一样顺序排列存储, 从而我们在赋值的时候, 不需要对它的字段分别赋值, 而是可以统一赋值, 比如对于上面的array结构为例, 就可以通过:

arr1.u.flags = arr2.u.flags;

一次完成相当于如下的赋值序列:

arr1.u.v.flags              = arr2.u.v.flags;arr1.u.v.nApplyCount        = arr2.u.v.nApplyCount;arr1.u.v.nIteratorsCount    = arr2.u.v.nIteratorsCount;arr1.u.v.reserve            = arr2.u.v.reserve;

もう 1 つの考えられる疑問は、なぜ zval 型の前に型 type を置かないのかということです。なぜなら、zval を使用するとき、最初にその型を取得する必要があることがわかっているからです。理由の 1 つは、はい、1 つです。両者に大きな違いはありません。もう 1 つは、将来 JIT が実装された場合、zval の型が型推論によって取得できる場合、その型の値を読み取る必要がなくなることです。続き)

声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
セッションを保存するためにデータベースを使用することの利点は何ですか?セッションを保存するためにデータベースを使用することの利点は何ですか?Apr 24, 2025 am 12:16 AM

データベースストレージセッションを使用することの主な利点には、持続性、スケーラビリティ、セキュリティが含まれます。 1。永続性:サーバーが再起動しても、セッションデータは変更されないままになります。 2。スケーラビリティ:分散システムに適用され、セッションデータが複数のサーバー間で同期されるようにします。 3。セキュリティ:データベースは、機密情報を保護するための暗号化されたストレージを提供します。

PHPでカスタムセッション処理をどのように実装しますか?PHPでカスタムセッション処理をどのように実装しますか?Apr 24, 2025 am 12:16 AM

PHPでのカスタムセッション処理の実装は、SessionHandlerInterfaceインターフェイスを実装することで実行できます。具体的な手順には、次のものが含まれます。1)CussentsessionHandlerなどのSessionHandlerInterfaceを実装するクラスの作成。 2)セッションデータのライフサイクルとストレージ方法を定義するためのインターフェイス(オープン、クローズ、読み取り、書き込み、破壊、GCなど)の書き換え方法。 3)PHPスクリプトでカスタムセッションプロセッサを登録し、セッションを開始します。これにより、データをMySQLやRedisなどのメディアに保存して、パフォーマンス、セキュリティ、スケーラビリティを改善できます。

セッションIDとは何ですか?セッションIDとは何ですか?Apr 24, 2025 am 12:13 AM

SessionIDは、ユーザーセッションのステータスを追跡するためにWebアプリケーションで使用されるメカニズムです。 1.ユーザーとサーバー間の複数のインタラクション中にユーザーのID情報を維持するために使用されるランダムに生成された文字列です。 2。サーバーは、ユーザーの複数のリクエストでこれらの要求を識別および関連付けるのに役立つCookieまたはURLパラメーターを介してクライアントに生成および送信します。 3.生成は通常、ランダムアルゴリズムを使用して、一意性と予測不可能性を確保します。 4.実際の開発では、Redisなどのメモリ内データベースを使用してセッションデータを保存してパフォーマンスとセキュリティを改善できます。

ステートレス環境(APIなど)でセッションをどのように処理しますか?ステートレス環境(APIなど)でセッションをどのように処理しますか?Apr 24, 2025 am 12:12 AM

APIなどのステートレス環境でのセッションの管理は、JWTまたはCookieを使用して達成できます。 1。JWTは、無国籍とスケーラビリティに適していますが、ビッグデータに関してはサイズが大きいです。 2.cookiesはより伝統的で実装が簡単ですが、セキュリティを確保するために慎重に構成する必要があります。

セッションに関連するクロスサイトスクリプティング(XSS)攻撃からどのように保護できますか?セッションに関連するクロスサイトスクリプティング(XSS)攻撃からどのように保護できますか?Apr 23, 2025 am 12:16 AM

セッション関連のXSS攻撃からアプリケーションを保護するには、次の測定が必要です。1。セッションCookieを保護するためにHTTPonlyとセキュアフラグを設定します。 2。すべてのユーザー入力のエクスポートコード。 3.コンテンツセキュリティポリシー(CSP)を実装して、スクリプトソースを制限します。これらのポリシーを通じて、セッション関連のXSS攻撃を効果的に保護し、ユーザーデータを確保できます。

PHPセッションのパフォーマンスを最適化するにはどうすればよいですか?PHPセッションのパフォーマンスを最適化するにはどうすればよいですか?Apr 23, 2025 am 12:13 AM

PHPセッションのパフォーマンスを最適化する方法は次のとおりです。1。遅延セッション開始、2。データベースを使用してセッションを保存します。これらの戦略は、高い並行性環境でのアプリケーションの効率を大幅に改善できます。

session.gc_maxlifetime構成設定とは何ですか?session.gc_maxlifetime構成設定とは何ですか?Apr 23, 2025 am 12:10 AM

thesession.gc_maxlifettinginttinginphpdethinesthelifsessessiondata、setinseconds.1)it'sconfiguredinphp.iniorviaini_set()。 2)AbalanceSneededToAvoidPerformanceIssues andunexpectedLogouts.3)php'sgarbagecollectionisisprobabilistic、影響を受けたBygc_probabi

PHPでセッション名をどのように構成しますか?PHPでセッション名をどのように構成しますか?Apr 23, 2025 am 12:08 AM

PHPでは、session_name()関数を使用してセッション名を構成できます。特定の手順は次のとおりです。1。session_name()関数を使用して、session_name( "my_session")などのセッション名を設定します。 2。セッション名を設定した後、session_start()を呼び出してセッションを開始します。セッション名の構成は、複数のアプリケーション間のセッションデータの競合を回避し、セキュリティを強化することができますが、セッション名の一意性、セキュリティ、長さ、設定タイミングに注意してください。

See all articles

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

Video Face Swap

Video Face Swap

完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

ホットツール

SecLists

SecLists

SecLists は、セキュリティ テスターの究極の相棒です。これは、セキュリティ評価中に頻繁に使用されるさまざまな種類のリストを 1 か所にまとめたものです。 SecLists は、セキュリティ テスターが必要とする可能性のあるすべてのリストを便利に提供することで、セキュリティ テストをより効率的かつ生産的にするのに役立ちます。リストの種類には、ユーザー名、パスワード、URL、ファジング ペイロード、機密データ パターン、Web シェルなどが含まれます。テスターはこのリポジトリを新しいテスト マシンにプルするだけで、必要なあらゆる種類のリストにアクセスできるようになります。

PhpStorm Mac バージョン

PhpStorm Mac バージョン

最新(2018.2.1)のプロフェッショナル向けPHP統合開発ツール

WebStorm Mac版

WebStorm Mac版

便利なJavaScript開発ツール

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

DVWA

DVWA

Damn Vulnerable Web App (DVWA) は、非常に脆弱な PHP/MySQL Web アプリケーションです。その主な目的は、セキュリティ専門家が法的環境でスキルとツールをテストするのに役立ち、Web 開発者が Web アプリケーションを保護するプロセスをより深く理解できるようにし、教師/生徒が教室環境で Web アプリケーションを教え/学習できるようにすることです。安全。 DVWA の目標は、シンプルでわかりやすいインターフェイスを通じて、さまざまな難易度で最も一般的な Web 脆弱性のいくつかを実践することです。このソフトウェアは、