Reference Counting Basics

Från Wiki.linux.se
Hoppa till navigering Hoppa till sök

Grunderna i referensräkning

En PHP-variabel lagras i en behållare som kallas en "zval". En zval-behållare innehåller, förutom variabelns typ och värde, två ytterligare bitar information. Den första kallas "is_ref" och är ett booleskt värde som indikerar om variabeln är en del av en "referensuppsättning" eller inte. Med denna bit vet PHP-motorn hur man skiljer mellan normala variabler och referenser. Eftersom PHP tillåter referenser på användarnivå, som skapas med `&`-operatorn, har en zval-behållare också en intern referensräkningsmekanism för att optimera minnesanvändningen. Den andra biten av ytterligare information, kallad "refcount", innehåller hur många variabelnamn (även kallade symboler) som pekar på denna enda zval-behållare. Alla symboler lagras i en symboltabell, av vilken det finns en per scope. Det finns ett scope för huvudskriptet (dvs det som begärs genom webbläsaren), såväl som ett för varje funktion eller metod.

En zval-behållare skapas när en ny variabel skapas med ett konstant värde, såsom:

Exempel #1 Skapa en ny zval-behållare

<?php
$a = "new string";
?>

I detta fall skapas det nya symbolnamnet `$a` i det aktuella scopet, och en ny variabelbehållare skapas med typen sträng och värdet `"new string"`. Biten "is_ref" är som standard satt till `false` eftersom ingen användarreferens har skapats. "refcount" är satt till `1` eftersom det bara finns en symbol som använder denna variabelbehållare. Notera att referenser (dvs när "is_ref" är `true`) med "refcount" `1` behandlas som om de inte är referenser (dvs som om "is_ref" var `false`). Om du har » Xdebug installerat kan du visa denna information genom att anropa `xdebug_debug_zval()`.

Exempel #2 Visa zval-information

<?php
$a = "new string";
xdebug_debug_zval('a');
?>

Ovanstående exempel kommer att ge följande utskrift:

a: (refcount=1, is_ref=0)='new string'

Att tilldela denna variabel till ett annat variabelnamn kommer att öka refcount.

Exempel #3 Öka refcount för en zval

<?php
$a = "new string";
$b = $a;
xdebug_debug_zval('a');
?>

Ovanstående exempel kommer att ge följande utskrift:

a: (refcount=2, is_ref=0)='new string'

Refcount är `2` här, eftersom samma variabelbehållare är länkad med både `$a` och `$b`. PHP är tillräckligt smart för att inte kopiera den faktiska variabelbehållaren när det inte är nödvändigt. Variabelbehållare förstörs när "refcount" når noll. "Refcount" minskas med ett när någon symbol länkad till variabelbehållaren lämnar scopet (t.ex. när funktionen avslutas) eller när en symbol avallokeras (t.ex. genom att anropa `unset()`). Följande exempel visar detta:

Exempel #4 Minska refcount för en zval

<?php
$a = "new string";
$c = $b = $a;
xdebug_debug_zval('a');
$b = 42;
xdebug_debug_zval('a');
unset($c);
xdebug_debug_zval('a');
?>

Ovanstående exempel kommer att ge följande utskrift:

a: (refcount=3, is_ref=0)='new string'
a: (refcount=2, is_ref=0)='new string'
a: (refcount=1, is_ref=0)='new string'

Om vi nu anropar `unset($a);` kommer variabelbehållaren, inklusive typen och värdet, att tas bort från minnet.

Sammansatta typer

Saker och ting blir lite mer komplicerade med sammansatta typer som arrayer och objekt. Till skillnad från skalära värden lagrar arrayer och objekt sina egenskaper i en egen symboltabell. Detta innebär att följande exempel skapar tre zval-behållare:

Exempel #5 Skapa en array zval

<?php
$a = array('meaning' => 'life', 'number' => 42);
xdebug_debug_zval('a');
?>

Ovanstående exempel kommer att ge något liknande:

a: (refcount=1, is_ref=0)=array (
   'meaning' => (refcount=1, is_ref=0)='life',
   'number' => (refcount=1, is_ref=0)=42
)

Eller grafiskt

  • Bild: Zvals för en enkel array*

De tre zval-behållarna är: `$a`, `'meaning'` och `'number'`. Liknande regler gäller för att öka och minska "refcount". Nedan lägger vi till ett annat element i arrayen och sätter dess värde till innehållet i ett redan existerande element:

Exempel #6 Lägga till ett redan existerande element till en array

<?php
$a = array('meaning' => 'life', 'number' => 42);
$a['life'] = $a['meaning'];
xdebug_debug_zval('a');
?>

Ovanstående exempel kommer att ge något liknande:

a: (refcount=1, is_ref=0)=array (
   'meaning' => (refcount=2, is_ref=0)='life',
   'number' => (refcount=1, is_ref=0)=42,
   'life' => (refcount=2, is_ref=0)='life'
)

Eller grafiskt

  • Bild: Zvals för en enkel array med en referens*

Från ovanstående Xdebug-utdata ser vi att både det gamla och det nya arrayelementet nu pekar på en zval-behållare vars "refcount" är `2`. Även om Xdebugs utdata visar två zval-behållare med värdet `'life'`, är de samma. Funktionen `xdebug_debug_zval()` visar inte detta, men du kan se det genom att också visa minnespekaren.

Att ta bort ett element från arrayen är som att ta bort en symbol från ett scope. Genom att göra det minskar "refcount" för en behållare som ett arrayelement pekar på. Återigen, när "refcount" når noll, tas variabelbehållaren bort från minnet. Återigen, ett exempel för att visa detta:

Exempel #7 Ta bort ett element från en array

<?php
$a = array('meaning' => 'life', 'number' => 42);
$a['life'] = $a['meaning'];
unset($a['meaning'], $a['number']);
xdebug_debug_zval('a');
?>

Ovanstående exempel kommer att ge något liknande:

a: (refcount=1, is_ref=0)=array (
   'life' => (refcount=1, is_ref=0)='life'
)

Nu blir saker intressanta om vi lägger till arrayen själv som ett element i arrayen, vilket vi gör i nästa exempel, där vi också smyger in en referensoperator, eftersom PHP annars skulle skapa en kopia:

Exempel #8 Lägga till arrayen själv som ett element i sig själv

<?php
$a = array('one');
$a[] =& $a;
xdebug_debug_zval('a');
?>

Ovanstående exempel kommer att ge något liknande:

a: (refcount=2, is_ref=1)=array (
   0 => (refcount=1, is_ref=0)='one',
   1 => (refcount=2, is_ref=1)=...
)

Eller grafiskt

  • Bild: Zvals för en array med en cirkulär referens*

Du kan se att arrayvariabeln (`$a`) liksom det andra elementet (`1`) nu pekar på en variabelbehållare som har en "refcount" på `2`. "..." i ovanstående utskrift visar att det finns rekursion inblandad, vilket naturligtvis i detta fall betyder att "..." pekar tillbaka på den ursprungliga arrayen.

Precis som tidigare, när man avallokerar en variabel tas symbolen bort, och referensräknaren för variabelbehållaren den pekar på minskas med ett. Så, om vi anropar `unset($a);` efter att ha kört ovanstående kod, minskas referensräknaren för variabelbehållaren som `$a` och elementet `1` pekar på med ett, från `2` till `1`. Detta kan representeras som:

Exempel #9 Avallokera $a

(refcount=1, is_ref=1)=array (
   0 => (refcount=1, is_ref=0)='one',
   1 => (refcount=1, is_ref=1)=...
)

Eller grafiskt

  • Bild: Zvals efter borttagning av array med en cirkulär referens som visar minnesläckan*

Problem med städning

Även om det inte längre finns någon symbol i något scope som pekar på denna struktur, kan den inte städas upp eftersom arrayelementet `1` fortfarande pekar på denna samma array. Eftersom det inte finns någon extern symbol som pekar på den, finns det inget sätt för en användare att städa upp denna struktur; därmed får du en minnesläcka. Lyckligtvis kommer PHP att städa upp denna datastruktur i slutet av förfrågan, men innan dess tar den upp värdefullt utrymme i minnet. Denna situation uppstår ofta om du implementerar parseringsalgoritmer eller andra saker där du har ett barn som pekar tillbaka på ett "föräldra"-element. Samma situation kan också hända med objekt naturligtvis, där det faktiskt är mer sannolikt att det inträffar, eftersom objekt alltid implicit används via referens.

Detta kanske inte är ett problem om det bara händer en eller två gånger, men om det finns tusentals eller till och med miljontals av dessa minnesförluster, börjar detta naturligtvis bli ett problem. Detta är särskilt problematiskt i långvariga skript, såsom demoner där förfrågan i princip aldrig slutar, eller i stora uppsättningar av enhetstester. Det senare orsakade problem vid körning av enhetstesterna för Template-komponenten i eZ Components-biblioteket. I vissa fall skulle det kräva över 2 GB minne, vilket testservern inte riktigt hade.

Sidslut

Orginalhemsidan på Engelska :https://www.php.net/manual/en/features.gc.refcounting-basics.php

PHP

Funktioner


Det här är en maskinöversättning av PHP-manualen till svenska. Om du hittar fel är vi tacksamma om du rapporterar dem via formuläret som finns på https://www.linux.se/kontaka-linux-se/

Tack till Datorhjälp.se som har sponsrat Linux.se med webserver.