Upgrade to Pro — share decks privately, control downloads, hide ads and more …

PHP Internals - Zend Memory Manager

PHP Internals - Zend Memory Manager

Ce TALK, explore en détail le Zend Memory Manager (ZMM) de PHP. Il explique son architecture, inspirée de JMALLOC et TCMalloc, et son fonctionnement basé sur des allocations par runs et buckets pour optimiser la gestion de la mémoire, notamment pour les petites allocations fréquentes.

Ce TALK aborde des concepts fondamentaux comme les bits, les octets, la Heap, l'alignement, le padding et la fragmentation. Il décrit le processus d'allocation pour différentes tailles, le rôle des chunks, des pages et des slots, ainsi que les structures de données internes et les stratégies pour minimiser le gaspillage de mémoire.

L'objectif est d'offrir une compréhension approfondie du ZMM pour optimiser les performances des applications PHP et diagnostiquer les problèmes liés à la mémoire.

Perussel Nicolas

March 27, 2025
Tweet

More Decks by Perussel Nicolas

Other Decks in Programming

Transcript

  1. Tout le monde m’appelle mamoot Je pratique le PHP depuis

    plus de 20ans Je suis spécialisé avec J’aime : • Nintendo et principalement Mario • Le Rhum (c’est récent) • La peinture sur figurine type Warhammer • La musique Nicolas Perussel CTO PHP [INTERNE] • Schémas Présentations
  2. Le ZMM est inspiré de JMALLOC (Jason Evans en 2005,

    utilisé dans FreeBSD, Mozilla et Rust). Le ZMM repose sur un système de RUNS (on parle de bucketized allocation) pour les petites allocations. Chaque run contient des slots de même taille, regroupés dans des buckets pour optimiser la réutilisation. L’implémentation de PHP s’adapte aux besoins spécifiques d’un langage interprété avec un GC et des allocations persistantes. ZMM - Généralités Dmitry Stogov Zeev Suraski Andi Gutmans
  3. Le ZMM est implémenté principalement dans zend_alloc.c et gère toute

    l'allocation mémoire interne de PHP. Il a été conçu pour : • Optimiser l’allocation et la libération de mémoire de manière dynamique • Être performant dans le cadre de requêtes web • Eviter les fuites mémoires Sans lui, vous devriez faire tout cela manuellement ! (malloc, calloc, realloc, aligned_alloc, free etc…) ZMM - Généralités
  4. Avant de commencer Bits, Bytes et Octets - Les fondamentaux

    7 Bit (b) • Unité fondamentale de l'information en informatique • Représente un état binaire: 0 ou 1 • Le plus petit élément de stockage dans un ordinateur • Utilisé dans les mesures de vitesse de transmission (Mb/s, Gb/s) Byte (B) / Octet • Groupe de 8 bits • Permet de représenter 256 valeurs différentes (2^8) • Terme "byte" en anglais, "octet" en français (même chose) • Unité de base pour mesurer la taille des fichiers et la mémoire Remarque importante • En français, "byte" et "octet" sont synonymes et désignent exactement la même unité • En anglais, seul le terme "byte" est utilisé
  5. HEAP – 1/2 10 Au sommet de l'architecture se trouve

    la Heap (zend_mm_heap), qui est la structure de contrôle principale du ZMM. La Heap est responsable de la coordination de toutes les allocations et libérations de mémoire dans PHP. Elle maintient : • Des tableaux de buckets pour les petites allocations • Des références vers tous les chunks alloués • Un suivi des grandes allocations via mmap • Des statistiques d'utilisation mémoire • Des flags de configuration • Configure et limite l’usage mémoire selon les paramètres (comme memory_limit) La Heap peut fonctionner avec l'allocateur par défaut ou avec un allocateur personnalisé (pour les environnements spéciaux).
  6. HEAP – 2/2 11 Une Heap custom permet d’utiliser un

    allocateur de mémoire alternatif à celui par défaut du Zend Memory Manager. Cela peut présenter plusieurs avantages : • Optimisation ou débogage : Un allocateur personnalisé peut intégrer des mécanismes de suivi ou de débogage plus poussés, permettant d’identifier plus facilement les fuites ou la corruption de mémoire. • Adaptation à des environnements spécifiques : Dans certains contextes (par exemple, des environnements embarqués ou des systèmes avec des contraintes particulières), il peut être nécessaire d’utiliser un modèle d’allocation différent, optimisé pour des critères précis (performance, isolation, etc.). • Contrôle fin : En utilisant un heap custom, PHP peut offrir aux développeurs une plus grande flexibilité et un meilleur contrôle sur la gestion de la mémoire, par exemple en choisissant des stratégies d’allocation/désallocation adaptées à leurs besoins. Ex : désactiver le Zend Memory Manager et à utiliser directement l’allocateur système (malloc/free) Cela fera en sorte que toutes les allocations passent par l’allocateur standard du système d’exploitation, ce qui peut être utile pour le débogage ou dans certains environnements de production spécifiques.
  7. ALLOCATION 12 1. Petites allocations (≤ 3KB) • Gérées via

    le système de buckets • Optimisées pour les allocations fréquentes comme les chaînes, tableaux simples • Représentent la majorité des allocations en PHP 2. Allocations moyennes (> 3KB et < 2MB) • Utilisent directement des pages entières ou multiples pages contiguës • Pas de découpage en slots • Utilisées pour les tableaux plus grands, objets complexes 3. Grandes allocations (≥ 2MB) • Allouées directement avec mmap() ou équivalent • Contournent complètement le système de chunks et pages • Utilisées pour les très grandes structures de données • Gérées par une liste séparée dans le Heap 4. Les allocations persistantes PHP distingue aussi les allocations par leur durée de vie: • Allocations standard : libérées à la fin de la requête • Allocations persistantes : survivent entre les requêtes Les allocations persistantes sont utilisées pour : • L'OPCache (code précompilé) • Les tables de symboles partagées • Les caches internes Elles sont marquées avec le flag GC_PERSISTENT et sont suivies séparément par le Garbage Collector. Les allocations persistantes utilisent généralement des fonctions comme : • pemalloc(size_t size, zend_bool persistent) • *perealloc(void ptr, size_t size, zend_bool persistent) • pecalloc(size_t num, size_t size, zend_bool persistent) ATTENTION AUX FUITES MEMOIRES !
  8. CHUNKS & PAGES 13 Les chunks sont de grandes zones

    mémoire (2MB) que PHP obtient du système. Chaque chunk est divisé en pages de 4KB (512 pages par chunk). Ces pages peuvent être de trois types: 1. Run Pages : Découpées en slots de taille fixe pour alimenter un bucket spécifique • Chaque run page contient plusieurs slots identiques (ex: 256 slots de 16 octets) • Inclut des métadonnées comme le bitmap des slots libres 2. Medium Pages : Dédiées aux allocations moyennes • Une allocation moyenne peut prendre plusieurs pages contiguës • Pas de découpage en slots plus petits • Alignées sur des frontières de page pour faciliter la libération 3. Meta Pages : Stockent les métadonnées du chunk • Contiennent les bitmaps de suivi des pages libres/utilisées • Maintiennent des statistiques et informations sur le chunk • Incluent des structures pour la gestion des pages Pourquoi utiliser des chunks ? Pour optimiser la gestion de la mémoire : 1.Réduire la fragmentation 2.Accélérer l'allocation 3.Simplifier le suivi de la mémoire
  9. Les frontières de Page 14 Une frontière de page est

    simplement l'adresse de début d'une page mémoire. Dans le ZMM, les pages font généralement 4KB (4096 octets) et sont alignées sur des adresses qui sont des multiples de 4KB. (Par exemple, les adresses 0x1000, 0x2000, 0x3000, etc., sont des frontières de pages.) Les frontières de pages sont importantes pour plusieurs raisons • Optimisation des performances : Les accès mémoire alignés sur des frontières naturelles sont généralement plus rapides. • Gestion des allocations moyennes : Les allocations moyennes (> 3KB et < 2MB) sont gérées en termes de pages entières ou multiples. Ces allocations commencent toujours sur une frontière de page. • Facilité de libération : Quand de la mémoire est libérée, le ZMM peut facilement identifier à quelle page elle appartient en examinant les bits d'adresse. • Support du système d'exploitation : Les systèmes d'exploitation modernes gèrent également la mémoire en pages (souvent 4KB), donc cet alignement collabore avec la MMU (Memory Management Unit) du système.
  10. ALIGNEMENT & PADDING - 1/3 16 L'alignement mémoire fait référence

    à la contrainte selon laquelle certaines données doivent être stockées à des adresses mémoire qui sont des multiples d'une valeur spécifique. Cette valeur, souvent appelée "exigence d'alignement", dépend du type de données et de l'architecture du processeur. Les processeurs modernes sont conçus pour accéder à la mémoire par "mots" (une unité naturelle de données pour un processeur, comme 4 ou 8 octets) => nous y reviendrons plus tard ! Lorsqu'une donnée est alignée, le processeur peut la récupérer en un seul accès mémoire. Si elle n'est pas alignée, il peut nécessiter deux accès mémoire ou plus, ce qui ralentit considérablement les performances. Ex : si l’on souhaite lire un entier de 4 octets stocké à l'adresse 6 et si le processeur lit la mémoire par blocs de 4 octets (aux adresses 0, 4, 8...), il devra effectuer deux lectures : • Une lecture à l'adresse 4 pour obtenir les 2 premiers octets • Une lecture à l'adresse 8 pour obtenir les 2 derniers octets • Puis combiner ces informations En revanche, si l'entier était aligné à l'adresse 8, une seule lecture suffirait.
  11. ALIGNEMENT & PADDING – 2/3 17 Exigences d'alignement typiques Sur

    la plupart des architectures modernes, les exigences d'alignement sont les suivantes : • char (1 octet) : aligné sur 1 octet (aucune contrainte) • short (2 octets) : aligné sur 2 octets (adresses paires) • int/float (4 octets) : aligné sur 4 octets (adresses multiples de 4) • long/double/pointeurs (8 octets sur 64 bits) : aligné sur 8 octets (adresses multiples de 8) Sur de nombreuses architectures, cette structure n'occupera pas 6 octets (1+4+1) mais 12 octets en raison du padding ajouté pour maintenir l'alignement ! Alignement des structures
  12. ALIGNEMENT & PADDING - 3/3 18 Les allocateurs mémoire comme

    le ZMM doivent tenir compte de l'alignement pour plusieurs raisons : 1. Garantir l'alignement des allocations Quand on demande un bloc de mémoire, l'allocateur doit s'assurer que ce bloc commence à une adresse correctement alignée. C'est pourquoi le ZMM aligne toutes ses allocations sur 8 octets. 2. Optimiser pour les accès mémoire En maintenant un alignement cohérent (par exemple, toujours 8 octets), l'allocateur simplifie la gestion de la mémoire et garantit des performances optimales pour tous les types de données couramment utilisés. 3. Équilibrer les performances et l'utilisation de la mémoire Un alignement plus strict (par exemple, 16 octets) améliorerait les performances pour certaines opérations SIMD (instructions qui traitent plusieurs données en parallèle), mais augmenterait le gaspillage mémoire dû au padding. Les allocateurs choisissent un compromis raisonnable. Le ZMM est donc aligné sur 8 octets
  13. ALIGNEMENT & PADDING 19 Padding dans les structures PHP Les

    structures de données PHP elles-mêmes intègrent également des considérations d'alignement. La structure est conçue pour faire exactement 16 octets sur les architectures 64 bits, optimisant ainsi l'alignement et la densité mémoire.
  14. FRAGMENTATION 20 Fragmentation interne • Définition : Espace gaspillé à

    l'intérieur des blocs alloués (padding) • Causes dans le ZMM : • Alignement obligatoire sur 8 octets • Système de buckets prédéfinis (tailles fixes) • Exemple : Demande de 10 octets → allocation de 16 octets (6 octets gaspillés) • Solution du ZMM : • Distribution optimisée des tailles de buckets • Incréments plus petits pour les tailles fréquentes (8, 16, 24...) • Incréments plus grands pour les grandes tailles où le gaspillage relatif est moindre Fragmentation externe • Définition : Espace libre total suffisant mais divisé en blocs inutilisables • Causes potentielles : Cycles d'allocation/libération de différentes tailles • Solutions du ZMM : • Système de RUNs : pages dédiées à une seule taille de bloc • Réutilisation immédiate des blocs libérés (LIFO) • Libération complète des pages vides • Séparation des petites, moyennes et grandes allocations Stratégies d'anti-fragmentation • Bucketing intelligent : 30 tailles prédéfinies couvrant 8 à 3072 octets (on va y revenir) • Gestion par pages : Blocs de même taille regroupés physiquement • Free-lists optimisées : Chaînage efficace des blocs libres • Libération de pages : Récupération des pages entièrement vides • Request cleanup : Libération complète à la fin de chaque requête HTTP Impact sur les performances • La fragmentation interne dans le ZMM reste généralement < 25% • Le modèle d'exécution de PHP (requêtes courtes) évite l'accumulation de fragmentation externe • Le compromis fragmentation/performance est calibré pour les charges typiques des applications web • L'allocation par buckets réduit considérablement le coût des allocations répétées
  15. Small Allocation Let’s go ! 23 Il se passe quoi

    dans le Zend Memory Manager lorsque je valorise une variable ?
  16. Small Allocation Overview complète 24 CHUNKS L’OS fournit de la

    mémoire en blocs de grande taille (typiquement 2 MB). • Le Header (Page 0) contient des informations de gestion (métadonnées, free_map, etc.). • Le reste du chunk est divisé en Pages (environ 512 pages de 4 KB chacune). PAGES Les pages servent de support aux RUNs (groupes contigus) qui sont utilisés pour les petites allocations. RUNs / BUCKETS Chaque RUN correspond à un bucket dédié à une taille de bloc prédéfinie (par exemple, 8, 16, 32 octets, etc.). • Les tableaux comme bin_elements, bin_data_size et bin_pages définissent, pour chaque bucket, la taille des blocs, le nombre de blocs par RUN, etc. ALLOCATION Lorsqu’une petite allocation est demandée, le gestionnaire parcourt le bucket correspondant et retourne l’un des blocs pré-alloués.
  17. Small Allocation Allons à la bibliothèque Imaginons une analogie avec

    une bibliothèque pour rendre ça plus concret : • La HEAP serait la bibliothèque entière • Les BUCKETS seraient comme des étagères spécialisées (une pour les livres de 16 pages, une pour ceux de 24 pages, etc.) • Les PAGES seraient comme des rayonnages de 4 mètres • Les SLOTS seraient les emplacements individuels sur ces rayonnages Quand tu demandes à conserver un livre de 16 pages, le bibliothécaire (le ZMM) : 1. Va directement à l'étagère des livres de 16 pages (le bon bucket) 2. Regarde s'il y a un emplacement libre sur un rayonnage existant 3. Si tous les rayonnages sont pleins, il en installe un nouveau de 4 mètres 4. Il divise ce nouveau rayonnage en 256 emplacements égaux
  18. Small Allocation Comprendre la notion de « bucket » 28

    La macro _BIN_DATA_ELEMENTS renvoie simplement la valeur du paramètre elements suivie d’une virgule. Autrement dit, elle extrait la donnée correspondant au nombre d’éléments pour un bucket donné. Ici, le tableau bin_elements est initialisé avec le résultat de l’expansion de la macro ZEND_MM_BINS_INFO lorsqu’on lui passe _BIN_DATA_ELEMENTS en argument, ainsi que deux autres paramètres (ici, notés x et y). La macro ZEND_MM_BINS_INFO (zend_alloc_sizes.h) est conçue pour itérer sur l’ensemble des buckets prédéfinis pour les petites allocations et appeler la macro passée (ici _BIN_DATA_ELEMENTS) avec les paramètres appropriés pour chaque bucket. En conséquence, bin_elements devient un tableau contenant pour chaque bucket le nombre d’éléments qu’il peut contenir. Ici, pour le bucket correspondant à des blocs de 8 octets, la valeur est 512 (c’est-à-dire que dans un RUN, 512 blocs de 8 octets peuvent être gérés), pour le bucket de 16 octets, la valeur est 256, et pour celui de 32 octets, elle est 128… Ces valeurs sont calculées en fonction de la taille de page (typiquement 4096 octets) et des frais généraux liés à la gestion du RUN (ce qui peut amener à des arrondis ou ajustements). Zend/zend_alloc.c#360
  19. Small Allocation Processus de sélection des buckets -> slots 31

    1. Sélection du BUCKET • La taille demandée est convertie en indice de bucket via la fonction zend_mm_small_size_to_bin • Cela permet de regrouper les allocations par classes de taille, facilitant ainsi le choix de la page. 2. Sélection de la PAGE • Chaque bucket contient plusieurs pages. • Le gestionnaire recherche dans le bucket une page qui possède un RUN (zone contiguë subdivisée en slots) avec des slots libres. • Le suivi est effectué grâce à des structures internes : • free_map : bitmap indiquant, bit à bit, l’état de chaque slot. • free_slots : compteur du nombre total de slots libres. 3. Allocation du SLOT • Une fois la page (et son RUN) sélectionnée, le gestionnaire identifie rapidement un slot libre en consultant le free_map. • Le slot est ensuite réservé pour l’allocation. • Ce mécanisme permet une allocation rapide et limite la fragmentation.
  20. La taille compte pour un bucket ! 1/5 32 8

    ÷ 8 = 1 Booléens, petits entiers, références simples Index 0 8 octets 16 ÷ 8 = 2 Petites chaînes (1- 8 caractères), doubles Index 1 16 octets 24 ÷ 8 = 3 Chaînes moyennes (9-16 caractères) Index 2 24 octets 32 ÷ 8 = 4 Chaînes moyennes, petits objets Index 3 32 octets 40 ÷ 8 = 5 Petits tableaux, objets simples Index 4 40 octets 48 ÷ 8 = 6 Tableaux, objets plus complexes Index 5 48 octets 56 ÷ 8 =7 Tableaux avec plusieurs éléments Index 6 56 octets 64 ÷ 8 = 8 Structures complexes, chaînes plus longues Index 7 64 octets Pour les petites tailles (8-64 octets) Incrément 8
  21. La taille compte pour un bucket ! 2/5 33 Pour

    les tailles moyennes (80-256 octets) Tableaux croissants, objets avec propriétés Index 8 80 octets Structures plus complexes Index 9 96 octets Chaînes longues, tableaux moyens Index 10 112 octets Objets avec beaucoup de propriétés Index 11 128 octets Tableaux à plusieurs dimensions Index 12 160 octets Objets complexes, chaînes longues Index 13 192 octets Structures de données imbriquées Index 14 224 octets Objets complexes, tableaux associatifs Index 15 256 octets Incrément 16 Incrément 32
  22. La taille compte pour un bucket ! 3/4 34 Tableaux

    multidimensionne ls Index 18 448 octets Structures complexes, grands tableaux Index 19 512 octets Grands objets avec multiples propriétés Index 20 640 octets Objets complexes, modèles de données Index 21 768 octets Grands tableaux multi-niveaux Index 22 896 octets Structures de données volumineuses Index 23 1024 octets Collections complexes Index 24 1280 octets Très grands tableaux Index 25 1536 octets Objets avec état complexe Index 26 1792 octets Grands tableaux de données Index 27 2048 octets Objets très volumineux Index 28 2560 octets Limite supérieure des petites allocations Index 29 3072 octets Pour les grandes tailles (320-3072 octets) Structures avec beaucoup d'éléments Index 16 320 octets Collections de données moyennes Index 17 384 octets Incrément 64 Incrément 128 Incrément 256
  23. La taille compte pour un bucket ! 4/5 35 Quelques

    observations importantes sur cette distribution : 1. Progression des tailles. La distribution suit plusieurs modèles : • De l'index 0 à 7 : incrément de 8 octets (+8) • De l'index 7 à 15 : incrément de 16 octets (+16), puis 32 octets (+32) • Au-delà de l'index 15 : les incréments deviennent plus grands (64, 128, 256 octets) 2. Logique de conception : • Les petites tailles ont des incréments plus petits pour minimiser le gaspillage dû au padding • Les grandes tailles ont des incréments plus grands car le gaspillage relatif est moins significatif • Toutes les tailles sont des multiples de 8 pour maintenir l'alignement optimal 3. Impact sur les pages : • Une page de 4KB (4096 octets) peut contenir : • 512 slots pour le bucket 0 (8 octets) • 256 slots pour le bucket 1 (16 octets) • 170 slots pour le bucket 2 (24 octets) • Et ainsi de suite jusqu'à seulement 1 slot pour le bucket 29 (3072 octets) Par exemple, pour une taille de 18 octets : (18 + 7) >> 3 = 25 >> 3 = 3 => Donc, bucket index 3, qui correspond à 32 octets Cette structure soigneusement conçue permet au ZMM de gérer efficacement une grande variété d'allocations tout en maintenant un bon équilibre entre : • Minimiser le gaspillage de mémoire • Optimiser la vitesse d'allocation/libération • Garantir l'alignement pour des performances optimales • Faciliter la réutilisation des blocs libérés Il est intéressant de noter que cette distribution des tailles a été affinée au fil des versions de PHP, en fonction de l'analyse des modèles d'allocation typiques dans les applications PHP réelles. Calcul de l’index
  24. La taille compte pour un bucket ! 5/5 36 Exemple

    de calcul de l’index d’une variable de taille 118 octets Etape 1 La taille demandée est de 118 octets, mais nous devons y ajouter le header de 8 octets que le ZMM attache à chaque allocation : • Taille brute = 118 octets • Taille avec en-tête = 118 + 8 = 126 octets Etape 2 Maintenant, nous devons arrondir cette taille au multiple de 8 supérieurs pour respecter l'alignement mémoire : • 126 ÷ 8 = 15.75 • Arrondi au multiple de 8 supérieurs = 128 octets Etape 3 Puisque 128 octets se situe dans la plage moyenne (80-256 octets), nous n'utilisons pas simplement la formule (taille ÷ 8) - 1 qui fonctionne pour les petites tailles. Pour cette plage, nous pouvons utiliser la formule approximative : index = 7 + (log2(taille - 1) - 5) * 2 Calculons : • log2(128 - 1) = log2(127) ≈ 6.99 • 7 + (6.99 - 5) * 2 ≈ 7 + 3.98 ≈ 10.98 ≈ 11 En pratique, le ZMM utiliserait sa table de correspondance précalculée, où nous pouvons vérifier que 128 octets correspondent exactement au bucket d'index 11. Cette allocation entraînera une fragmentation interne de 128 - 126 = 2 octets, ce qui représente moins de 2% de gaspillage mémoire, un compromis tout à fait acceptable en termes de performance. Objets avec beaucoup de propriétés Index 11 128 octets
  25. ZMM Internal – Calcul de l’index 37 Allocation des petites

    tailles ( <= 64 octets) Allocation moyennes et grandes Zend/zend_alloc.c#1248
  26. ZMM Internal – Calcul de l’index 38 Cette fonction calcule

    la position du bit le plus significatif (en comptant à partir de 1) pour un entier donné. En d'autres termes, elle retourne floor(log2(size)) + 1. Le code utilise plusieurs implémentations selon la plateforme ou les compilateurs disponibles Zend/zend_alloc.c#1217
  27. Small Allocation Run de blocs 39 Un « run »,

    c’est comme une série contigüe de blocs mémoire de même taille, tous alignés à la suite les uns des autres. En termes plus techniques, un run est une portion d'une page mémoire qui a été divisée en plusieurs blocs de taille identique (correspondant à la taille d'un bucket spécifique). Ces blocs sont disposés côte à côte en mémoire, ce qui permet un accès très efficace. 1. Quand le ZMM a besoin d'alimenter un bucket (disons le bucket de 16 octets), il ne va pas allouer un bloc à la fois, mais plutôt une page entière (typiquement 4KB). 2. Cette page est ensuite divisée en "run" - une série de blocs de 16 octets chacun. Avec une page de 4KB, cela donnerait environ 256 blocs de 16 octets (4096 ÷ 16 = 256). 3. Tous ces blocs sont initialement marqués comme libres et ajoutés à la chaîne du bucket approprié. 4. La page elle-même est marquée comme étant dédiée à ce bucket particulier, et on garde une référence à quel bucket elle appartient. L'avantage principal de cette approche est la localité mémoire : • Tous les blocs d'un run sont physiquement proches en mémoire • Cela améliore les performances à cause du cache CPU • La gestion est simplifiée puisque tous les blocs d'un run ont la même taille
  28. Small Allocation – Slot occupé 40 Structure du Header Small

    Alloc Pour les petites allocations (≤ 3KB), le header fait 8 octets et contient : Le champ Size (32 bits) : • Stocke la taille totale du bloc (header + données) • Les bits de poids faible peuvent aussi contenir des flags • Par exemple : si le dernier bit est à 1, cela indique que le bloc est utilisé 3 bits de flags: • GC_PERSISTENT : indique si l'objet doit survivre entre les requêtes • GC_COLLECTABLE : indique si l'objet peut être collecté par le GC • IN_ARENA : indique si l'objet est dans une arène mémoire Le champ GC Info (16 bits) : • Contient des informations pour le ramasse-miettes • Compte les références à l'objet • Indique si l'objet peut être collecté Le champ Type (16 bits) : • Identifie le type de données PHP stocké • Permet de savoir comment traiter la mémoire • Exemples : IS_STRING, IS_ARRAY, IS_OBJECT SIZE (29 bits) + FLAGS (3 bits) GC Info (16 bits) TYPE (16 bits) mot 1 sur 32 bits mot 1 sur 32 bits Un "mot" (word en anglais) est une unité fondamentale de la mémoire dans les ordinateurs. La taille d'un mot dépend de l'architecture du processeur : • Sur une architecture 32 bits, un mot fait 32 bits (4 octets) • Sur une architecture 64 bits, un mot fait 64 bits (8 octets) Le processeur est optimisé pour manipuler des données de la taille d'un mot. Par exemple, sur une architecture 32 bits, le processeur lit naturellement 32 bits à la fois, donc organiser les données en mots de 32 bits permet des accès mémoire plus efficaces. Vocabulaire
  29. Small Allocation – Slot Libre 41 Un slot libre est

    structuré ainsi : • En-tête (8 octets) : contient des informations de base comme la taille du slot • Zone next_free (8 octets) : contient un pointeur vers le prochain slot libre L'astuce ingénieuse du ZMM est que, puisque le slot est libre, la zone de données n'est pas utilisée. Elle peut donc être réutilisée pour stocker le pointeur vers le prochain slot libre, ce qui économise de la mémoire. Mécanisme de chaînage des slots libres 1. Le pointeur free_slot pointe vers le premier slot libre dans le RUN 2. Chaque slot libre utilise sa zone de données pour stocker l'adresse du prochain slot libre 3. Le dernier slot libre de la chaîne pointe vers NULL Ce mécanisme permet : • De trouver très rapidement un slot libre (accès direct via free_slot) • D'ajouter rapidement un slot libéré à la chaîne (insertion en tête) • De ne pas gaspiller de mémoire supplémentaire pour gérer la liste des slots libres Quand un slot est libéré, il est simplement ajouté au début de cette chaîne. Quand un slot est alloué, il est retiré du début de la chaîne. Ces opérations sont très rapides (O(1)), ce qui contribue grandement aux performances de PHP. Zend/zend_alloc.c#1278
  30. Small Allocation 42 1. PHP demande 5 octets pour «

    Hello » 2. Le ZMM ajoute 8 octets pour le header 3. Il aligne le total sur 8 octets 4. Il initialise le header avec : • Size = 16 (8 + 5 + padding) • GC Info = 1 (une référence) • Type = IS_STRING HEADER 8 octets DATA 5 octets PADDING 3 octets Allocation small (alignée sur 8 octets) 16 octets
  31. Small Allocation - Mais en vrai… 1/2 43 1. Création

    de la zval pour « Hello » zval str_val; Cette zval occupe 16 octets sur une architecture 64 bits (8 octets pour value + 4 octets pour u1 + 4 octets pour u2). 2. Allocation mémoire pour la chaîne « Hello » • Taille de 'Hello' = 5 octets + 1 octet nul terminal = 6 octets • Structure d'en-tête de chaîne (zend_string) ≈ 16 octets (comprend la longueur, le hachage, etc.) • Taille totale nécessaire = environ 22 octets Le ZMM arrondit cette taille à 24 octets (prochain multiple de 8) et ajoute 8 octets pour son propre en-tête, ce qui donne une allocation de 32 octets. Cette allocation provient du bucket d'index 3 (32 ÷ 8 = 4, donc index 3 car l'indexation commence à 0). Zend/zend_types.h
  32. Small Allocation - Mais en vrai… 2/2 44 3. Initialisation

    de la chaine 4. Initialisation de la zval 5. Association de la zval à la variable $a Cela alloue également de la mémoire pour l'entrée de la table de hachage, ce qui implique une autre allocation (généralement autour de 32 octets également). Optimisations potentielles Pour une chaîne aussi courte que "Hello", PHP pourrait utiliser plusieurs optimisations : • Interned strings : Si la chaîne "Hello" a déjà été utilisée ailleurs, PHP pourrait réutiliser l'instance existante au lieu d'en allouer une nouvelle. • Small string optimization : Dans certaines versions de PHP, les très petites chaînes (comme "Hello") peuvent être stockées directement dans la structure zend_string sans allocation séparée. Total approximatif : ~80 octets sont utilisés pour stocker seulement 5 caractères ! Cette apparente inefficacité est le coût de la flexibilité dynamique de PHP : la variable $a pourrait ensuite être réaffectée à n'importe quel autre type (entier, tableau, objet) et le système de type dynamique doit pouvoir gérer cela.
  33. Small Allocation Ce qu’il faut retenir (en gros) 45 Synthèse

    de la hiérarchie • HEAP : Le réservoir global de mémoire. • CHUNKS : Les grands blocs contigus extraits de la HEAP (2M) • PAGES : Les subdivisions fixes d'un chunk, souvent alignées sur la taille de page du système (4KB) • BUCKETS : Les regroupements de pages en fonction des classes de tailles des blocs qu'elles contiennent. • RUNS : Des zones à l'intérieur d'une page (ou de plusieurs pages) où la mémoire est découpée en slots de taille fixe. • SLOTS : Les plus petites unités d'allocation, utilisées pour satisfaire les petites demandes mémoire.
  34. Pourquoi s’embêter avec ça ? 48 Pour optimiser la gestion

    de la mémoire dans vos applications Le ZMM est responsable de toute l'allocation et la libération de mémoire dans PHP. Lorsque tu crées des variables, des tableaux, ou des objets, c'est le ZMM qui gère l'espace mémoire nécessaire. Si tu comprends comment il fonctionne, tu peux écrire du code qui utilise la mémoire de façon plus efficiente. Pour être un vrai programmeur ! Pour diagnostiquer et résoudre les problèmes de performance Par exemple, comprendre que PHP utilise un "request lifecycle" où toute la mémoire est généralement libérée à la fin de chaque requête HTTP t’aide à identifier pourquoi certains patterns comme les workers persistants (avec PHP-FPM) peuvent se comporter différemment de ce tu attends. Pour comprendre les limitations et les comportements de PHP • Pourquoi les variables perdent-elles leur valeur entre les requêtes HTTP ? • Pourquoi certains objets volumineux ne sont-ils pas immédiatement libérés même après un unset() ? • Comment fonctionne l'option de configuration memory_limit et pourquoi elle ne fonctionne pas toujours comme on s'y attend ? Pour gérer efficacement les applications à grande échelle • Configurer correctement PHP-FPM et ses paramètres comme pm.max_children • Déterminer la taille optimale pour memory_limit • Comprendre quand utiliser des workers persistants vs. redémarrer régulièrement les processus • Savoir quand des outils comme APCu ou Redis sont nécessaires pour partager des données entre requêtes