Kolik paměti zabírají PHP pole a objekty?

V PHP se zdá, že s pamětí je jenom málokdy problém. Pokud nepřekročím limit, který skript může alokovat, je všechno v pořádku. Žádné pauzy garbage collectoru (GC), žádné out of memory error, které zabijí celou aplikaci, žádné momory leaky a žádný tlak na GC, který v marné snaze uvolnit ubývající paměť, spálí všechny cykly CPU .

To všechno jsou důsledky faktu, že PHP je navrženo pro jednoduchý request/response cyklus, kdy skript alokuje paměť a po skončení skriptu se celá najednou uvolní. V mnoha případech PHP ani GC nepotřebuje.

Tento model ovlivnil naprosto všechno v návrhu PHP (když mluvím o PHP mám na mysli výchozí implementaci od Zendu): parser musí být rychlý, protože se skripty parsují pořád dokola, skript se nepřekládá do nativního kódu (JIT), ale vždycky se interpretuje, GC je velice jednoduchý refcount s přidanou detekcí cyklů a neprovádí kompakci.

To všechno funguje výborně, když PHP používám tak jak to bůh zamýšlel, ale velice rychle začnu narážet na různé problémy u dlouho běžících skriptů a frameworků jako například React.PHP: skript může běžet celé týdny nebo měsíce a v takovém případě se vyplatí přeložit horké smyčky do nativního kódu, protože GC neprovádí kompakci, určitý program může fragmentovat paměť, která nikdy nebude uvolněna a nebude ji možné efektivně využít (jako se mi povedlo když jsem testoval tuto zrůdnost)

Ale hlavně člověk začne pozorovat, jak strašlivé množství paměti PHP spotřebuje a že prakticky není možné se z této paměťové smyčky vyvléknout a vytvořit efektivnější datové struktury (ono je to technicky možné, ale za jakou cenu?). O Javě se říká, že je paměťově nenažraná, ale přesto nesahá ani po kolena PHP ve velkolepé neefektivitě.

To je způsobeno dvěma aspekty jazyka/runtime:

  1. všechny hodnoty jsou boxované ve strukturách nazvaných zval (+ každé čtení hodnoty znamená skok na pointer, což s sebou může nést hodně cache miss).
  2. pole a dynamické objekty jsou implementované jako řazené hashmapy

O zvalech a polích si můžete přečíst ve výborném článku How big are PHP arrays (and values) really?. Z něho vyplývá, že pole jsou ztracený případ. Je to z části způsobeno tím, že toho umějí příliš mnoho – pole jsou ve skutečnosti hashmapy zvalů, které si uchovávají pořadí na předchozí a následující prvek. Jenom málokdy potřebuji takové chování, které je často matoucí, protože není zřejmé, jak přesně se má v daných případech tato složitá datová struktura používat. Většinou potřebuji buď pole, nebo množinu nebo mapu, ale jenom málokdy potřebuji všechno najednou.

Udělal jsem proto několik testů, které zjišťují jak efektivně nakládají s pamětí různé datové struktury v PHP (pole, dynamický objekt, třída, SplFixedArray) za různých okolností a v různých verzích PHP (5.3.10, 5.4.9 a 5.5-RC2). Každý test vždy vytvořil datovou strukturu, která obsahuje 100000 elementů. Výsledky jsou zajímavé a ukazují za jakých okolností se vyhnou jakým strukturám, když chci do paměti narvat co nejvíc dat. Je z nich také patrný mírný pokrok mezi PHP 5.3 a 5.4.

Testovací skript můžete najít tady.


Výsledky:

*                     | PHP 5.3.10           | PHP 5.4.9         | PHP 5.5-RC2
*                     | total     | elem     | total    | elem   | total    |  elem
------------------------------------------------------------------------------------
array_fill nulls      |  14155776 |  141.56  | 10485760 | 104.86 | 10485760 | 104.86
array_fill constant   |  14155776 |  141.56  | 10485760 | 104.86 | 10485760 | 104.86
array_fill value      |  14155776 |  141.56  | 10485760 | 104.86 | 10485760 | 104.86
range                 |  22020096 |  220.20  | 15204352 | 152.04 | 15204352 | 152.04
SplFixedArray empty   |   1835008 |   18.35  |  1572864 |  15.73 |  1572864 |  15.73
SplFixedArray nulls   |   9961472 |   99.61  |  6291456 |  62.91 |  6291456 |  62.91
SplFixedArray const   |   9961472 |   99.61  |  6291456 |  62.91 |  6291456 |  62.91
private fields        | 101974016 | 1019.74  | 32768000 | 327.68 | 32768000 | 327.68
public fields default |  99614720 |  996.15  | 32768000 | 327.68 | 32768000 | 327.68
public fields set     | 123469824 | 1234.70  | 47185920 | 471.86 | 47185920 | 471.86
public fields set one | 107479040 | 1074.79  | 37486592 | 374.87 | 37486592 | 374.87
public fields add one | 120324096 | 1203.24  | 89391104 | 893.91 | 89391104 | 893.91
arrays constant keys  | 107741184 | 1077.41  | 72876032 | 728.76 | 72876032 | 728.76
arrays variable keys  | 107741184 | 1077.41  | 75235328 | 752.35 | 75235328 | 752.35
objects               | 123469824 | 1234.70  | 86245376 | 862.45 | 86245376 | 862.45

Jak je vidět, PHP 5.4 a 5.5 jsou na tom naprosto identicky, ale oproti 5.3 vykazují značné zlepšení.

Z prvních třech řádků je vidět, že pokud vytvořím pole přes array_fill s hodnotou null, konstantou nebo $proměnnou, zabírají stejné množství paměti. Když vytvořím stejné pole přes range, zabere přibližně o 50% více místa. Jde o to, že se hodnota nevytváří pořád dokola, ale sdílí se ten samý zval (refcount). Když se pak nějaká takto zalinkovaná hodnota změní, vytvoří se samostatná kopie.

Zajímavé chování ukazuje SplFixedArray, které není interně reprezentováno jako hash tabulka, ale jako ploché pole pointerů na zvaly (jde o dynamické pole). Když SplFixedArray vytvořím aniž bych do něj něco přiřadil, zabírá velice málo místa. Dá se předpokládat, že je plné null pointerů. Ale když do něj cokoli přiřadím, obsahuje pointery na zvaly, včetně případu kdy přiřazuji null. Jak je vidět SplFixedArray oproti poli v tomto konkrétním případě potřebuje jenom cca 42% paměti.

Další testy prověřují objekty a třídy. Instance tříd s privátními i soukromými atributy zabírají stejné množství paměti. Ale když tyto atributy přepíšu, spotřeba paměti se zvětší a vypadá to, že roste úměrně s počtem přepsaných atributů. Za pozornost stojí, že objekt se třemi intovými atributy spotřebuje přes 300 bajtů paměti včetně nákladů pole ve kterém jsou uloženy (v Javě by to bylo 48 bajtů).

Následují dynamické struktury: pole a objekty bez deklarovaných atributů. Jak je vidět, pole potřebují více paměti než instance tříd, přibližně dvakrát. Plně dynamické objekty nebo instance tříd, kterým jsem přiřadil dynamický atribut pak potřebují ještě o něco víc. Důvod je popsán tady. Jde o to, že jména deklarovaných atributů jsou uloženy v definici třídy, ale jména všech dynamických atributů/klíčů musejí být uloženy v každé instanci objektu nebo pole jako klíče hash tabulky (a pochopitelně delší jména zaberou víc paměti). A jak vidět, tak stačí, když do instance třídy zapíšeme jeden dynamický atribut a struktura objektu se automaticky deoptimalizuje na hash-tabulku.

Co z toho tedy vyplývá:

  1. když potřebujete lineární strukturu, používejte SplFixedArray i když bude z větší částí plná null hodnot
  2. objekty, které deklarují vlastní proměnné zabírají nejméně místa, pak pole a nakonec plně dynamické objekty
  3. nepřiřazujte dynamické atributy do instancí tříd, to víc jak zdvojnásobí jejich spotřebu paměti

Flattr this!

This entry was posted in Paměť, PHP. Bookmark the permalink.

3 Responses to Kolik paměti zabírají PHP pole a objekty?

  1. Jakub Vrána says:

    Vynikající článek, díky za sdílení. Jen s prvním bodem závěru nesouhlasím, protože ne vždy záleží pouze na paměti. Spíš by se dalo říct, že na paměti naopak záleží málokdy a častěji záleží na čase. A když do tvého testovacího skriptu přidám měření času, tak SplFixedArray vychází (v závislosti na velikosti pole) asi o 100% pomalejší než array. Takže v běžných situacích zůstanu u klasického pole.

  2. Lamicz says:

    "Instance tříd s privátními i soukromými atributy " – není to to samé? :)

  3. Vit Kouba says:

    Život to je změna, to co je dneska v módě se učit, to zítra vyjde z módy, a ten kdo se to dlouho učil, plýtval jako idiot svým drahocenným časem, co přijde za deset let do módy, to nikdo dneska neví, a všechny předpovědi jsou na hovno.
    Nějaká firma nejspíše; Google, Microsoft, Apple, Adobe, Ubuntu, atd. přijde s revolucí v programování a najednou bude programování tak jednoduché, že to zvládne malé dítě bez problémů.
    Stačí jenom vytvořit systém ze všech současných systémů, co zde jsou k dispozici.
    Je to jenom evoluční experiment, který překoná současný složitý problém, a potom budou programátoři nuceni se naučit něco jiného, aby nebyli nezaměstnaní.
    Programování to je jenom plánování práce pro nějaký systém a otestování toho, jestli se to dobře naplánovalo.
    Podle mě má budoucnost virtuální svět, nebude to trvat dlouho a vše se bude virtualizovat a nebudeme chodit z bytu do školy a zaměstnání, protože budeme pracovat jenom ve virtuálním světě, mimo naše byty se budou pohybovat roboti, a roboti nám nakoupí a vyvenčí psa, potom nám uvaří a uklidí byt.
    Zanikne; vyhledávač Google, Facebook, YouTube, Seznam, atd. bude zde jenom jeden globální virtuální svět, který se nám stane drogou, bez které nebudeme moci existovat.
    Padnou jazykové a kulturní bariéry, budete komunikovat s lidmi, se kterými jste dříve komunikovat nemohli a nechtěli.
    Zaniknou dokonce peníze a vlastnictví, a místo nich zde bude jenom bodování a každý se bude snažit, aby jej ostatní dobře hodnotili a dávali mu kladné body za to, co on dělá ve virtuálním světě.
    Přelomem bude zavádění čipů do mozku, mít v mozku několik let starý čip, bude něco, jako když dneska někdo vyndá starý mobil, a tím se ve společnosti okamžitě znemožní.

Leave a Reply

Your email address will not be published. Required fields are marked *