Wie groß sind PHP-Arrays und -Werte?

藏色散人
Freigeben: 2023-04-04 21:42:01
Original
3374 Leute haben es durchsucht

In diesem Artikel geht es um die Speichernutzung von PHP 5. Für die in diesem Artikel beschriebene Situation ist die Speichernutzung in PHP 7 etwa dreimal geringer.

Wie groß sind PHP-Arrays und -Werte?

In diesem Beitrag möchte ich die Speichernutzung von PHP-Arrays (und Werten im Allgemeinen) am Beispiel des folgenden Skripts untersuchen, das ein 100000 Unique Integer-Array erstellt Elemente und hat die Speichernutzung des Ergebnisses gemessen:

$startMemory = memory_get_usage();
$array = range(1, 100000);
echo memory_get_usage() - $startMemory, ' bytes';
Nach dem Login kopieren

Was soll es sein, vereinfacht ausgedrückt, ist eine Ganzzahl 8 Bytes (unter Verwendung des Long-Typs auf einem 64-Bit-Unix-Computer). 100.000 Ganzzahlen, also benötigen Sie offensichtlich 800.000 Bytes.

Versuchen Sie nun, den obigen Code auszuführen. Dies ergibt 14649024 Bytes. Ja, Sie haben richtig gehört, 13,97 MB, 18-mal mehr als wir geschätzt haben.

Woher kommt also der zusätzliche Faktor 18?

Zusammenfassung

Für diejenigen, die nicht die ganze Geschichte wissen wollen: Hier ist eine kurze Zusammenfassung der Speichernutzung der verschiedenen Komponenten:

                             |  64 bit   | 32 bit
---------------------------------------------------
zval                         |  24 bytes | 16 bytes
+ cyclic GC info             |   8 bytes |  4 bytes
+ allocation header          |  16 bytes |  8 bytes
===================================================
zval (value) total           |  48 bytes | 28 bytes
===================================================
bucket                       |  72 bytes | 36 bytes
+ allocation header          |  16 bytes |  8 bytes
+ pointer                    |   8 bytes |  4 bytes
===================================================
bucket (array element) total |  96 bytes | 48 bytes
===================================================
total total                  | 144 bytes | 76 bytes
Nach dem Login kopieren

Die oben genannten Zahlen variieren je nach Betriebssystem, Compiler und Kompilierungsoptionen. Wenn Sie beispielsweise PHP mit Debug oder Thread-Sicherheit kompilieren, erhalten Sie unterschiedliche Zahlen. Aber ich denke, dass die oben angegebene Größe das ist, was Sie in einer 64-Bit-Produktionsversion von PHP 5.3 unter Linux sehen werden.

Wenn Sie diese 144 Bytes mit 100.000 Elementen multiplizieren, erhalten Sie 14.400.000 Bytes, also 13,73 MB, was der tatsächlichen Zahl sehr nahe kommt – der größte Teil des Rests sind nicht initialisierte Bucket-Zeiger, aber das werde ich besprechen später.

Wenn Sie nun eine detailliertere Analyse der oben genannten Werte wünschen, lesen Sie weiter :)

zvalue_value Alliance

Sehen Sie sich zunächst an, wie PHP Werte von speichert. Wie Sie wissen, ist PHP eine schwach typisierte Sprache und benötigt daher eine Möglichkeit, schnell zwischen den Typen zu wechseln. PHP verwendet hierfür Union, die in Zend wie folgt definiert ist.

typedef union _zvalue_value {
    long lval;                // For integers and booleans
    double dval;              // For floats (doubles)
    struct {                  // For strings
        char *val;            //     consisting of the string itself
        int len;              //     and its length
    } str;
    HashTable *ht;            // For arrays (hash tables)
    zend_object_value obj;    // For objects
} zvalue_value;
Nach dem Login kopieren

Wenn Sie C nicht kennen, ist das kein Problem, denn der Code ist sehr einfach: Union ist eine Möglichkeit, bestimmte Werte als verschiedene Typen zugänglich zu machen. Wenn Sie beispielsweise zvalue_value->lval ausführen, erhalten Sie einen Wert, der als Ganzzahl interpretiert wird. Wenn Sie hingegen zvalue_value->ht verwenden, wird der Wert als Zeiger auf eine Hash-Tabelle (d. h. ein Array) interpretiert.

Aber wir wollen hier nicht zu sehr ins Detail gehen. Für uns ist das Einzige, was zählt, dass die Größe einer Union gleich der Größe ihrer größten Komponente ist. Die größte Komponente ist hier die String-Struktur (die zend_object_value-Struktur hat die gleiche Größe wie die str-Struktur, aber ich werde sie der Einfachheit halber weglassen). Die String-Struktur speichert einen Zeiger (8 Bytes) und eine Ganzzahl (4 Bytes), insgesamt 12 Bytes. Aufgrund der Speicherausrichtung (12-Byte-Strukturen sind nicht cool, da sie kein Vielfaches von 64 Bit/8 Byte sind) beträgt die Gesamtgröße der Struktur 16 Byte, was auch der Größe der Union als Ganzes entspricht.

Jetzt wissen wir, dass aufgrund der dynamischen Typisierung von PHP nicht jeder Wert 8 Bytes, sondern 16 Bytes benötigt. Die Multiplikation mit 100.000 Werten ergibt 1600.000 Bytes, also 1,53 MB, aber der tatsächliche Wert beträgt 13,97 MB, sodass wir ihn noch nicht ermitteln können.

Die Struktur von zval

Das ist sehr logisch – die Union speichert nur den Wert selbst, aber PHP benötigt natürlich auch den Speichertyp und einige Garbage-Collection-Informationen. Die Struktur, die diese Informationen enthält, wird Zval genannt, von der Sie vielleicht schon gehört haben. Für weitere Informationen darüber, warum PHP es braucht, empfehle ich die Lektüre eines Artikels von Sara Golemon. Wie auch immer, diese Struktur ist wie folgt definiert: Die Größe der

struct _zval_struct {
    zvalue_value value;     // The value
    zend_uint refcount__gc; // The number of references to this value (for GC)
    zend_uchar type;        // The type
    zend_uchar is_ref__gc;  // Whether this value is a reference (&)
};
Nach dem Login kopieren

-Struktur wird durch die Summe der Größen ihrer Komponenten bestimmt: 16 Bytes für zvalue_value (berechnet wie oben), 4 Bytes für zend_uint und 1 Byte für zend_uchars . Die Gesamtmenge beträgt 22 Byte. Aufgrund der Speicherausrichtung beträgt die tatsächliche Größe 24 Byte.

Wenn wir also 100.000 Elemente in 24 Bytes speichern, beträgt die Gesamtzahl 2.400.000, also 2,29 MB. Die Lücke schließt sich, aber der tatsächliche Wert beträgt immer noch mehr als das Sechsfache des Originals.

Circular Collector (ab PHP 5.3)

PHP 5.3 führt einen neuen zirkulären Referenz-Garbage Collector ein. Dazu muss PHP einige zusätzliche Daten speichern. Ich möchte hier nicht erklären, wie dieser Algorithmus funktioniert, Sie können es auf der verlinkten Seite des Handbuchs nachlesen. Was für unsere Größenberechnung wichtig ist, ist, dass PHP jedes zval in zval_gc_info:

typedef struct _zval_gc_info {
    zval z;
    union {
        gc_root_buffer       *buffered;
        struct _zval_gc_info *next;
    } u;
} zval_gc_info;
Nach dem Login kopieren

einschließt. Wie Sie sehen können, fügt Zend einfach eine Union hinzu, die aus zwei Zeigern besteht. Ich hoffe, Sie erinnern sich daran, dass die Größe einer Union der Größe ihrer größten Komponente entspricht: Beide Union-Komponenten sind Zeiger, also sind sie beide 8 Byte groß. Die Größe der Union beträgt also ebenfalls 8 Byte.

Wenn wir das zu 24 Bytes addieren, haben wir bereits 32 Bytes. Multipliziert mit 100.000 Elementen erhalten wir eine Speichernutzung von 3,05 MB.

Zend MM Allocator

C verwaltet im Gegensatz zu PHP den Speicher nicht für Sie. Sie müssen selbst den Überblick über Ihre Zuweisungen behalten. Zu diesem Zweck verwendet PHP einen speziell für seine Bedürfnisse optimierten benutzerdefinierten Speichermanager: den Zend Memory Manager. Zend MM basiert auf Doug Leas Malloc und fügt einige PHP-spezifische Optimierungen und Funktionen hinzu (z. B. Speicherbeschränkungen, Bereinigung nach jeder Anfrage usw.).

Was uns hier wichtig ist, ist, dass MM für jede darüber vorgenommene Zuweisung einen Zuweisungskopf hinzufügt. Die Definition lautet wie folgt:

typedef struct _zend_mm_block {
    zend_mm_block_info info;
#if ZEND_DEBUG
    unsigned int magic;
# ifdef ZTS
    THREAD_T thread_id;
# endif
    zend_mm_debug_info debug;
#elif ZEND_MM_HEAP_PROTECTION
    zend_mm_debug_info debug;
#endif
} zend_mm_block;

typedef struct _zend_mm_block_info {
#if ZEND_MM_COOKIES
    size_t _cookie;
#endif
    size_t _size; // size of the allocation
    size_t _prev; // previous block (not sure what exactly this is)
} zend_mm_block_info;
Nach dem Login kopieren

如您所见,这些定义充斥着大量的编译选项检查。如果你用堆保护,多线程,调试和MM cookie来构建PHP,那么如果你用堆保护,多线程,调试和MM cookie来构建PHP,那么如果你用堆保护,多线程,调试和MM cookie来构建PHP,那么分配头文件会更大。

对于本例,我们假设所有这些选项都是禁用的。在这种情况下,只剩下两个size_ts _size和_prev。size_t有8个字节(在64位上),所以分配头的总大小是16个字节——并且在每个分配上都添加了这个头。

现在我们需要再次调整zval大小。实际上,它不是32字节,而是48字节,这是由分配头决定的。乘以100000个元素是4。58 MB,实际值是13。97 MB,所以我们已经得到了大约三分之一的面积。

Buckets

到目前为止,我们只考虑单个值。但是PHP中的数组结构也会占用大量空间:“数组”在这里实际上是一个不合适的术语。PHP数组实际上是散列表/字典。那么哈希表是如何工作的呢?基本上,对于每个键,都会生成一个散列,该散列用作“real”C数组的偏移量。由于哈希值可能会冲突,具有相同哈希值的所有元素都存储在链表中。当访问一个元素时,PHP首先计算散列,查找正确的bucket并遍历链接列表,逐个元素比较确切的键。bucket的定义如下:

typedef struct bucket {
    ulong h;                  // The hash (or for int keys the key)
    uint nKeyLength;          // The length of the key (for string keys)
    void *pData;              // The actual data
    void *pDataPtr;           // ??? What's this ???
    struct bucket *pListNext; // PHP arrays are ordered. This gives the next element in that order
    struct bucket *pListLast; // and this gives the previous element
    struct bucket *pNext;     // The next element in this (doubly) linked list
    struct bucket *pLast;     // The previous element in this (doubly) linked list
    const char *arKey;        // The key (for string keys)
} Bucket;
Nach dem Login kopieren

正如您所看到的,需要存储大量数据才能获得PHP使用的抽象数组数据结构(PHP数组同时是数组、字典和链表,这当然需要大量信息)。单个组件的大小为无符号long为8字节,无符号int为4字节,指针为7乘以8字节。总共是68。添加对齐,得到72字节。

像zvals这样的bucket需要在头部分配,因此我们需要再次为分配头添加16个字节,从而得到88个字节。我们还需要在“real”C数组中存储指向这些Bucket的指针(Bucket ** arbucket;)我上面提到过,每个元素增加8个字节。所以总的来说,每个bucket需要96字节的存储空间。

如果每个值都需要一个bucket,那么bucket是96字节,zval是48字节,总共144字节。对于100000个元素,也就是14400000字节,即13.73 MB。

神秘的解决。

等等,还有0.24 MB !

最后的0.24 MB是由于未初始化的存储bucket造成的:理想情况下,存储bucket的实际C数组的大小应该与存储的数组元素的数量大致相同。通过这种方式,冲突最少(除非希望浪费大量内存)。但是PHP显然不能在每次添加元素时重新分配整个数组——这将非常缓慢。相反,如果内部bucket数组达到限制,PHP总是将其大小加倍。所以数组的大小总是2的幂。

在我们的例子中是2 ^ 17 = 131072。但是我们只需要100000个bucket,所以我们留下31072个bucket没有使用。这些bucket不会被分配(因此我们不需要花费全部的96字节),但是bucket指针(存储在内部桶数组中的那个)的内存仍然需要分配。所以我们另外使用8字节(一个指针)* 31072个元素。这是248576字节或0.23 MB,与丢失的内存匹配。(当然,这里仍然缺少一些字节,但是我不想在这里介绍。比如哈希表结构本身,变量等等)

神秘真的解决了。

这告诉我们什么?

PHP不是c,这就是所有这些告诉我们的。您不能期望像PHP这样的超级动态语言具有与C语言相同的高效内存使用。你不能。

但是,如果您确实想节省内存,可以考虑使用SplFixedArray处理大型静态数组。

看看这个修改后的脚本:

$startMemory = memory_get_usage();
$array = new SplFixedArray(100000);
for ($i = 0; $i < 100000; ++$i) {
    $array[$i] = $i;
}
echo memory_get_usage() - $startMemory, &#39; bytes&#39;;
Nach dem Login kopieren

它基本上做的是相同的事情,但是如果运行它,您会注意到它只使用了“5600640字节”。这是每个元素56字节,因此比普通数组使用的每个元素144字节要少得多。这是因为一个固定的数组不需要bucket结构:所以它只需要每个元素一个zval(48字节)和一个指针(8字节),从而得到观察到的56字节。

Das obige ist der detaillierte Inhalt vonWie groß sind PHP-Arrays und -Werte?. 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