Analyzing PHP foreach Memory Behavior and Array Internals
This article examines how PHP's foreach construct consumes memory under different reference modes, explains the internal structure of PHP arrays, buckets, and zvals, and provides practical recommendations for choosing reference or value iteration to minimize memory usage.
Problem
PHP developers frequently iterate arrays with foreach , but the memory implications of using reference versus non‑reference loops are not always clear.
Variable Scope
When iterating by reference, the loop variable remains bound to the last element after the loop, so it should be unset() to avoid unintended side effects.
Memory Consumption
Creating a 1 M element integer array on a 64‑bit system consumes about 144 MB, far more than the expected 8 MB, because each element occupies a Bucket (≈72 bytes) plus a zval (≈24 bytes).
Array Memory Structure
PHP arrays are hash tables. Key fields include nTableSize , nNumOfElements , pInternalPointer , pListHead / pListTail , and arBuckets . Each Bucket stores metadata and a pointer to the actual value.
Bucket Definition
typedef struct bucket {
ulong h; // hash value
uint nKeyLength; // key length (for strings)
void *pData; // pointer to data
void *pDataPtr; // pointer when data is itself a pointer
struct bucket *pListNext;
struct bucket *pListLast;
struct bucket *pNext;
struct bucket *pLast;
const char *arKey; // key string
} Bucket;zval and zvalue
The zvalue union holds the actual value (long, double, string, hash table, object). A zval adds reference counting and type information.
typedef union _zvalue_value {
long lval;
double dval;
struct { char *val; int len; } str;
HashTable *ht;
zend_object_value obj;
} zvalue_value;
struct _zval_struct {
zvalue_value value;
zend_uint refcount__gc;
zend_uchar type;
zend_uchar is_ref__gc;
};Foreach Memory Behavior
Two scenarios are considered: read‑only and read‑write loops. In read‑only loops, memory usage remains constant because only the iterator object and loop variable are allocated.
In read‑write loops, the first iteration triggers a copy of the array’s bucket structure, and each subsequent write adds the size of a new zval (≈24 bytes). After the loop finishes, the temporary buckets are released.
When the array has a reference count greater than one ( refcount > 1 ) and is_ref = 0 , iterating by value copies only the bucket part; iterating by reference copies both buckets and creates a new zval for each element.
When the array is a true reference ( is_ref = 1 ), any form of iteration—by value or by reference—does not increase memory because the underlying structure is shared.
Summary
For read‑only traversal of arrays that may be referenced elsewhere, use value iteration to avoid extra memory. For write‑heavy traversal, use reference iteration to prevent additional zval allocations, and always unset() the loop variable after use.
Beike Product & Technology
As Beike's official product and technology account, we are committed to building a platform for sharing Beike's product and technology insights, targeting internet/O2O developers and product professionals. We share high-quality original articles, tech salon events, and recruitment information weekly. Welcome to follow us.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.