Mirin webspace

Nejbohatší život má ten, kdo žije s minimem nároků

24. 7. 2008 PHP

Problém kruhových referencí

Určitě je vám známo, že PHP runtime je vybaven podobně jako řada dalších jazyků garbage collectorem, který se stará o automatickou správu paměti, takže vývojáři PHP aplikace odpadá starost o to, jak a kdy PHP runtime provede uvolnění paměti pro tu kterou proměnnou. Až do nedávna existovala minimálně jedna výjimka a tou jsou právě kruhové reference. Článek je volným překladem blogpostu Paul M. Jonese, hlavního vývojáře PHP frameworku Solar.

Kruhové reference se vyskytují v různých typech úloh a vývojáři se s nimi setkávají poměrně často, zejména při vytváření stromových hierarchií typu rodič-potomek, kdy rodič má referenci na potomka a potomek na rodiče.

class Foo {
    function __construct()
    {
        $this->bar = new Bar($this);
    }
}
 
class Bar {
    function __construct($foo = null)
    {
        $this->foo = $foo;
    }
}
 
while (true) {
    $foo = new Foo();
    unset($foo);
    echo number_format(memory_get_usage()) . "\n";
}

Pokud si pustíte tento skript v konzoli, tak budete jen chvíli koukat, a pak dostanete asi něco takovéto

	33,551,616
	33,551,976
	33,552,336
	33,552,696
	PHP Fatal error:  Allowed memory size of 33554432 bytes exhausted
	(tried to allocate 16 bytes) in memleak.php on line 17

pěkný je také případ, který uvádí Derick Rethans

$tree = array( 'one' );
$tree[] = $tree;
unset($tree)

Teď trochu odbočka, pokud jste někdy psali nějakou aplikaci v C/C++ a narazili jste na kruhové reference, tak Vám jistě nebylo úplně lehce, člověk se musí dost snažit, aby mu při dealokování a rušení různých nodů v té výše zmíněné hierarchii neutekla nějaká paměť. V PHP je to v podstatě každému fuk, protože nějaký ten leak při requestu na web aplikaci se za chvíli ztratí, ale nedejbože abychom začali psát nějaké testy, případně command line/desktop aplikace, to pak pěkně narazíme.

Problém je v garbage collectoru v PHP, ten si s kruhovými referencemi prostě neporadí, zjednodušeně při unsetu nám tam ta jedna kruhová reference zůstane a runtime paměť neuvolní, nicméně žádná proměnná, která by jí využívala už neexistuje a leak je na světě. Co tedy s tím?

Userland řešení

Musíme si to prostě pohlídat sami, zjednodušeně řečeno trochu návrat k C/C++, před unsetem musíme z proměnné, kterou unsetujeme vyházet ty problémové kruhové reference.

class Foo {
    function __construct()
    {
        $this->bar = new Bar($this);
    }
    function __destruct()
    {
        unset($this->bar);
    }
}
 
class Bar {
    function __construct($foo = null)
    {
        $this->foo = $foo;
    }
}
 
while (true) {
    $foo = new Foo();
    $foo->__destruct();
    unset($foo);
    echo number_format(memory_get_usage()) . "\n";
}

Do třídy Foo jsme přidali destructor a ten pak voláme před unsetem. Pak je to v pořádku, spotřebovávaná paměť se ustálí na konečné hodnotě a skript bude v konzoli běžet do nekonečna.

Internals řešení

Existuje ještě možnost druhá, upraví se garbage collector tak, aby hlídal kruhové reference. Toho se ujal v Google Summer of Code z roku 2007 student David Wang, který napsal patch pro garbage collector. Při hlasování o tom, zda se patch dostane do PHP 5.3 se však mnoho hlasů pro jeho zařazení nesešlo. Toto vylepšení garbage collectoru má totiž jednu zásadní nevýhodu, snižuje totiž celkový výkon celého PHP runtime tak nějak ve všech směrech. O to větší bylo překvapení, že patch se nakonec v 5.3 větvi objevil.

Do budoucna tedy trápení s kruhovými referencemi odpadá, nicméně pozici PHP enginu jako jednoho z nejpomalejších mezi implementacemi programovacích jazyků zatím pravděpodobně nic neohrozí.


Komentáře (0)

Komentáře jsou uzavřeny.