2015-01-11 9 views
15

Próbuję dowiedzieć się, w jaki sposób PHP ładuje tablice do pamięci i kiedy przekazywanie tablicy zużywa pamięć.Jak działa zarządzanie wykorzystaniem pamięci macierzy PHP?

Więc mam ten mały kawałek kodu z rzędu: zauważ, że tablica wejście jest mniej ważny w tym przykładzie:

<?php 

echo $this->getMemoryUsage(); 
$arr = $query->result_array(); // array of arrays from codeigniter 
echo $this->getMemoryUsage(); 

ten zużywa dokładnie 250 kB pamięci, oznacza to, że tablica jest w przybliżeniu 250 kB w rozmiarze, około.

Więc wpadłem następujący kod:

<?php 

echo $this->getMemoryUsage(); 
$arr = $query->result_array(); // array of arrays from codeigniter 

$arr[0]['id'] = 'changing this value'; 

$foo = $arr; 
$foo[2]['id'] = 'changing this value again'; 

$bar = $foo; 
$bar[4]['id'] = 'changing this value again and again'; 

$far = $bar; 
$far[5]['id'] = 'changing this value again and again and again'; 

echo $this->getMemoryUsage(); 

Według tego, co czytałem i powiedziano mi, PHP nie faktycznie skopiować tablicę, tylko odwołuje się do oryginalnej tablicy, ale raz zmiana jest wykonane PHP musi skopiować całą tablicę.

Wyobraźcie sobie moje zdziwienie, gdy powyższy kod zużywa dokładnie 500 KB pamięci RAM.

Czy ktoś może wyjaśnić, co tu się dzieje?

Dla jasności wszystkie te indeksy (0-5 i id) już istnieją w oryginalnej tablicy, po prostu modyfikuję wartość. Oryginalna wartość jest liczbą całkowitą.

EDIT

Wystarczy wyczyścić zaangażowania $ this-> wynik(); Oto kolejny test mam przeprowadzać:

echo $this->getMemoryUsage(); 
    $arr = $query->result_array(); // array of arrays from codeigniter 
//$arr[0]['id'] = 'changing this value'; 

    $foo = $arr; 
    $foo[2]['id'] = 'changing this value again'; 

    //$bar = $foo; 
    //$bar[4]['id'] = 'changing this value again and again'; 
    // 
    //$far = $bar; 
    //$far[4]['id'] = 'changing this value again and again and again'; 

    echo $this->getMemoryUsage(); 

Tym razem wynik jest dokładnie 250 kB - Podobnie jak w pierwotnym badaniu bez jakichkolwiek zmian

EDIT # 2

Zgodnie z życzeniem, ja” Wcześniej prowadził kod tutaj na mojej konfiguracji, aby upewnić się, że wyniki są zgodne: http://pastebin.com/cYNg4cg7

są to wyniki:

OŚWIADCZENIE: 4608 kB
KOŃCOWA: 8904 kB
DIFF deklarację: 4296 kB

Więc nawet jeśli zgłoszenie było 4608 i tablica została uchwalona i zmienił się 4 razy, to wciąż tylko mniej niż podwojona pamięć ślad stopy.

EDIT # 3

ja prowadził zmiany pamięci po każdej alokacji:

deklarację: 5144 kB
rozdysponowania A0 dodania: 144 kB
przydzielania A1 dodania: 1768 kB
przydział dodanego A2: 1768 kB
przydzielenie A3 dodane: 1768 kB
FINAŁ: 10744 kB
DIFF DO DEKLARACJI: 5600 kB

Każda następna operacja po pierwszym koszcie jest dokładnie taka sama, co wydaje się wskazywać dokładnie ten sam rozmiar, który jest kopiowany. Wydaje się, że to wspiera odpowiedź Austina. Jedyną rzeczą, która się nie sumuje, jest rozmiar przydzielony, ale to inne pytanie.

Wygląda na to, że Austin jest na piłce, przyjmuję to, jeśli nie przyjdzie żadna inna odpowiedź.

+0

Bardzo trudne pytanie, może być zainteresowany w następującym artykule przeczytałem kilka dni temu: https: // Nikić .github.io/2011/12/12/How-big-are-PHP-arrays-really-Hint-BIG.html – Fleshgrinder

+0

Przeczytałem ten artykuł kilka tygodni temu, to szczerze mówiąc n, ale nie wyjaśnia, jak dokładnie działa kopiowanie. – Patrick

+2

Wiem, pomyślałem, że ci się spodoba. Nie mogę odpowiedzieć na twoje pytanie i nie mogę podać linku, który mógłby odpowiedzieć na twoje pytanie. Zamiast tego zagrałem w Twoje pytanie, aby móc je śledzić i przyznać nagrodę, jeśli nie udzielono odpowiedzi, ponieważ bardzo chciałbym to wiedzieć. :) – Fleshgrinder

Odpowiedz

4

Oto co myślę jest grane:

tablice PHP są kopiowania przy zapisie jak mówisz, ale każdy poziom wielowymiarowej tablicy jest oddzielnie kopiowania przy zapisie. PHP jest bardzo sprytny, jeśli chodzi o ponowne wykorzystanie części wielowymiarowej tablicy, a nie tylko całej rzeczy. (Jest to podobne do niektórych systemach, które obsługują migawek jak ZFS).

przykład:, że mamy tę tablicę

$x = array('foo' => array(1, 2, 3), 'bar' => array(4, 5, 6)); 

ten jest przechowywany w pamięci, nie w postaci pojedynczego fragmentu, ale jako osobne kawałki tutaj oznakowane A, B, C i $x:

array(1, 2, 3) //A 
array(4, 5, 6) //B 
array('foo' => {pointer to A}, 'bar' => {pointer to B}) //C 
{pointer to C} //$x 

teraz pozwala wykonać kopię $x:

$y = $x; 

ten zużywa bardzo mało dodatkowej pamięci, ponieważ wszystko, co ma zrobić, to utworzyć inny wskaźnik do C:

array(1, 2, 3) //A 
array(4, 5, 6) //B 
array('foo' => {pointer to A}, 'bar' => {pointer to B}) //C 
{pointer to C} //$x 
{pointer to C} //$y 

Teraz pozwala zmiana $y:

$y['foo'][0] = 10; 

Oto, co nie zdarza :

array(1, 2, 3) //A 
array(10, 2, 3) //A2 
array(4, 5, 6) //B 
array(4, 5, 6) //B2 
array('foo' => {pointer to A}, 'bar' => {pointer to B}) //C 
array('foo' => {pointer to A2}, 'bar' => {pointer to B2}) //C2 
{pointer to C} //$x 
{pointer to C2} //$y 

Zauważ, że B i B2 są identyczne. Nie ma potrzeby, aby utrzymać to samo dwa razy, więc to, co faktycznie dzieje się to w ten sposób:

array(1, 2, 3) //A 
array(10, 2, 3) //A2 
array(4, 5, 6) //B 
array('foo' => {pointer to A}, 'bar' => {pointer to B}) //C 
array('foo' => {pointer to A2}, 'bar' => {pointer to B}) //C2 
{pointer to C} //$x 
{pointer to C2} //$y 

W tym prostym przypadku zaletą jest bardzo mała, ale sobie wyobrazić, że zamiast trzech liczb, tablica 'bar' zawierały tysiące numerów . W efekcie oszczędzasz ogromne ilości pamięci.

W odniesieniu do oryginalnego kodu, spróbuj wydrukować użycie pamięci nie tylko na początku i na końcu, ale także po każdym nowym przypisaniu do tablicy.Zobaczysz, że użycie pamięci zwiększa się tylko o ułamek tego, co zajmuje oryginalna tablica po każdym kroku. Dzieje się tak, ponieważ kopiowana jest tylko część tablicy, a nie całość. W szczególności, tablica pierwszego poziomu i konkretna zmienna podrzędna, którą zmienisz, zostaną skopiowane, ale pozostałe podrzędne tablice nie zostaną skopiowane.

Fakt, że ostateczna ilość użytej pamięci jest dwa razy większa od kwoty początkowej, wydaje się być zbiegiem okoliczności ze względu na szczególną konfigurację kodu i liczbę kopii tworzonej macierzy.

(W rzeczywistości PHP może nawet lepiej niż to, co opisuję tutaj (prawdopodobnie zachowa tylko jedną kopię 'foo' i 'bar' itd.), Ale w większości przypadków sprowadza się do tego samego rodzaju sztuczki.)

Jeśli chcesz bardziej dramatyczny pokaz tego, zrób coś takiego:

$base = memory_get_usage(); 
$x = array('small' => array('this is small'), 'big' => array()); 
for ($i = 0; $i < 1000000; $i++) { 
    $x['big'][] = $i; 
} 
echo (memory_get_usage() - $base).PHP_EOL; //a lot of memory 
$y = $x; 
$y['small'][0] = 'now a bit bigger'; 
echo (memory_get_usage() - $base).PHP_EOL; //a bit more memory 
$z = $x; 
$z['big'][0] = 2; 
echo (memory_get_usage() - $base).PHP_EOL; //a LOT more memory 
+0

Hej, myślałem o czymś podobnym, ale się nie sumuje, Używając innego przypadku testowego, który został już odtworzony, alokacja pamięci podwaja się po 4 identycznych akcjach (zobacz ostatnią zmianę w pytaniu). 4 identyczne akcje na tablicach o tym samym rozmiarze powinny mieć efekt liniowy. Jeśli wykonamy dokładnie tę samą czynność 4 razy, jednorazowy czas powinien kosztować 1/4 kosztu. Zauważ, że w testach ciągle zmieniam różne indeksy, jeśli skopiujemy jeden, skopiujemy wszystkie. Będę edytować moje pytanie z nowymi wartościami pamięci tak szybko, jak tylko będę mógł. – Patrick

+2

@Patrick Pierwsze działanie nie kosztuje tyle, ponieważ nie musisz zachować starej wartości, ponieważ żadna zmienna jej nie używa. Od drugiego do czwartego działania należy wykonać kopię, ponieważ oryginał jest nadal w użyciu.Wydrukuj użycie pamięci po każdym przypisaniu, aby to zobaczyć. – Austin

+0

Po kolejnych testach twoja odpowiedź jest rzeczywiście poprawna, Superb! :) – Patrick