PHP absurd geheugen gebruik bij arrays

Pagina: 1
Acties:

Onderwerpen


Acties:
  • 0 Henk 'm!

  • SvMp
  • Registratie: September 2000
  • Niet online
In een PHP-project waar ik mee bezig ben constateerde ik absurd geheugen gebruik. Dat nodigde uit tot experimenteren.

Daarbij liep ik tegen een absurd geheugengebruik van arrays aan.

Ik heb het volgende experimentele script gemaakt:
PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
echo 'Memory [begin]: '.intval(memory_get_usage()/1024) . ' KB<br>';
$t=array();
for ($i=0; $i<30000; $i++) $t[]='Hoi';
echo 'Memory [na maken array]: '.intval(memory_get_usage()/1024) . ' KB<br>';
unset($t);
echo 'Memory [na unset array]: '.intval(memory_get_usage()/1024) . ' KB<br>';

$t=array();
for ($i=0; $i<30000; $i++) $t[]='Hoi';
$t=serialize($t);
echo 'Memory [memory na serialize array]: '.intval(memory_get_usage()/1024) . ' KB<br>';

$t='';
for ($i=0; $i<300000; $i++) $t.='Hoi';

echo 'Memory [na maken string]: '.intval(memory_get_usage()/1024) . ' KB<br>';
echo 'Hoeveelheid bytes in string (uitgedrukt in KB) ' . intval(strlen($t)/1024) . '<br>';
unset($t);
echo 'Memory [na unset string]: '.intval(memory_get_usage()/1024) . ' KB<br>';


Daaruit blijkt op mijn PC het volgende:
code:
1
2
3
4
5
6
7
8
Memory [begin]: 106 KB
Memory [na maken array]: 7629 KB
Memory [na unset array]: 106 KB
Memory [memory na serialize array]: 623 KB
Memory [na maken string]: 985 KB
Hoeveelheid bytes in string (uitgedrukt in KB) 878
Memory [na unset string]: 106 KB
?>


De vraag. Is dit normaal?

Een array met 30.000 keer de string 'Hoi' blijkt meer dan 7 MB geheugen te nemen.
Er vanuit gaande dat de string 4 bytes groot is incl. zero aan het eind, en de key een integer is van 4 bytes, zou deze string naar verwachting 4 x 30.000 = 120.000 bytes geheugen nemen. Uiteraard is er overhead aangezien PHP realtime interpreteert. Echter meer dan het 50-voudige vind ik wel heel extreem.

Het blijkt echt door de array te komen want vrijgeven dmv. unset levert weer het geheugen gebruik bij aanvang op. Ook de string vervangen door een numerieke waarde geeft hetzelfde verschijnsel.

Verder experimenteren. Array weer maken, serializen en originele array weggooien. Dezelfde data neemt nu stukken minder. Niet praktisch om mee te werken natuurlijk, maar het toont wel iets aan.

Vervolgens een string maken. Deze heb ik zelfs 10 keer zo groot gemaakt, want een array bevat meer data dan alleen de waardes, namelijk de keys, en een string is veel simpeler. Maar zelfs dan is het geheugengebruik van de string vele malen kleiner.

Dit experiment laat nog iets zien: Hoe efficient de string in het geheugen wordt opgeslagen. De toename van het geheugengebruik na het maken van de lange string is amper groter dan de werkelijke lengte van de string.

Is het normaal dat PHP zo inefficient met arrays om gaat? Want als dat zo is, dan moet je vooral grote arrays gaan mijden.

Acties:
  • 0 Henk 'm!

  • Cartman!
  • Registratie: April 2000
  • Niet online
Heb je een 32 of 64 bit systeem? Er zit een flinke bug in PHP waardoor bij grotere arrays het geheugenverbruik verdubbelt op 64bit systemen. Daar kwam ik achter toen ik met arrays ging werken van ~400MB die 'ineens' 800MB werden. Bleek een bug te zijn, nadat de provider de bak op 32bit opnieuw opgebouwd had was het opgelost.

Acties:
  • 0 Henk 'm!

  • db_inc
  • Registratie: Mei 2010
  • Laatst online: 17-04-2021
Wat mij op viel is dat je geen array index hebt gebruikt..
Hieronder de eerste loop uit je script met 'i' als index..
PHP:
1
for ($i=0; $i<30000; $i++) $t[i]='Hoi';


Het resultaat met de index:
code:
1
2
3
4
5
6
7
Memory [begin]: 58 KB
Memory [na maken array]: 59 KB
Memory [na unset array]: 59 KB
Memory [memory na serialize array]: 59 KB
Memory [na maken string]: 939 KB
Hoeveelheid bytes in string (uitgedrukt in KB) 878
Memory [na unset string]: 60 KB


En zonder:
code:
1
2
3
4
5
6
7
Memory [begin]: 58 KB
Memory [na maken array]: 3937 KB
Memory [na unset array]: 121 KB
Memory [memory na serialize array]: 638 KB
Memory [na maken string]: 1000 KB
Hoeveelheid bytes in string (uitgedrukt in KB) 878
Memory [na unset string]: 121 KB


Succes :) !!

Acties:
  • 0 Henk 'm!

  • HuHu
  • Registratie: Maart 2005
  • Niet online
Bij mij krijg ik er dit uit:

Memory [begin]: 311 KB
Memory [na maken array]: 3252 KB
Memory [na unset array]: 311 KB
Memory [memory na serialize array]: 828 KB
Memory [na maken string]: 1190 KB
Hoeveelheid bytes in string (uitgedrukt in KB) 878
Memory [na unset string]: 311 KB
Windows XP 64-bit, PHP 5.3.0, Apache/2.2.14 (Win32)

Overigens maakt het niet uit of ik Hoi of Hoiiiii in de array laat zetten, pas vanaf 8 tekens wordt hij weer groter. Daarna blijft hij tot en met 15 tekens weer even groot en vanaf 16 neemt het geheugengebruik weer toe. Het lijkt dus wel alsof PHP ruimte reserveert per 8 tekens.

Acties:
  • 0 Henk 'm!

  • HuHu
  • Registratie: Maart 2005
  • Niet online
db_inc schreef op woensdag 12 mei 2010 @ 09:34:
Wat mij op viel is dat je geen array index hebt gebruikt..
Hieronder de eerste loop uit je script met 'i' als index..
PHP:
1
for ($i=0; $i<30000; $i++) $t[i]='Hoi';
Je hebt een fout in je code, er mist een $ voor de i en daardoor blijft de array leeg. Als je die terug plaatst is het weer hetzelfde.

[ Voor 43% gewijzigd door HuHu op 12-05-2010 09:39 ]


Acties:
  • 0 Henk 'm!

  • SvMp
  • Registratie: September 2000
  • Niet online
HuHu schreef op woensdag 12 mei 2010 @ 09:38:
[...]

Je hebt een fout in je code, er mist een $ voor de i en daardoor blijft de array leeg. Als je die terug plaatst is het weer hetzelfde.
Dat verklaart de mooie resultaten van db_inc inderdaad. Het verwonderde mij al, omdat ik het zelf ook al met een index heb geprobeerd. Ik gebruik Ubuntu 9.10 64-bit. PHP 5.2.10-2ubuntu6.4 with Suhosin-Patch 0.9.7 (cli) (built: Jan 6 2010 22:56:44).

Bij HuHu is het geheugengebruik minder extreem, namelijk ongeveer de helft. Ook 64-bit, maar wel PHP 5.3. Het zou mooi zijn als met 5.3 het probleem al voor de helft opgelost is. Ik zeg de helft, want ik vind zelfs dan het geheugengebruik nog fors.

[ Voor 20% gewijzigd door SvMp op 12-05-2010 09:47 ]


Acties:
  • 0 Henk 'm!

Verwijderd

FreeBSD 8.0 amd64 met Apache 2.2.15 & PHP 5.3.2 (uit de ports collection):
code:
1
2
3
4
5
6
7
Memory [begin]: 315 KB
Memory [na maken array]: 4311 KB
Memory [na unset array]: 315 KB
Memory [memory na serialize array]: 832 KB
Memory [na maken string]: 1194 KB
Hoeveelheid bytes in string (uitgedrukt in KB) 878
Memory [na unset string]: 315 KB

Acties:
  • 0 Henk 'm!

  • SvMp
  • Registratie: September 2000
  • Niet online
N.a.v. bovenstaande test in FreeBSD, ook het script maar even op mijn eigen server thuis getest.
FreeBSD 7.2 32-bit, PHP 5.3.2 (build Apr 23 2010 12:31:16)

code:
1
2
3
4
5
6
7
Memory [begin]: 313 KB
Memory [na maken array]: 3019 KB
Memory [na unset array]: 313 KB
Memory [memory na serialize array]: 829 KB
Memory [na maken string]: 1192 KB
Hoeveelheid bytes in string (uitgedrukt in KB) 878
Memory [na unset string]: 313 KB


Ook een sterke verbetering. Er lijkt i.i.g. het nodig verbeterd te zijn met de komst van PHP 5.3.
Toch een groot verschil tussen de test van Kamose en van mij. Zou het te maken hebben met 32-bit vs. 64-bit, of is het wellicht een instelling van PHP? Het verschil treedt alleen op bij de array, daarbuiten zijn de verschillen marginaal.

[ Voor 18% gewijzigd door SvMp op 12-05-2010 09:59 ]


Acties:
  • 0 Henk 'm!

  • hostname
  • Registratie: April 2009
  • Laatst online: 16-09 09:13
PHP 5.3.2 op Debian Lenny amd64 (= 64-bit):
code:
1
2
3
4
5
6
7
Memory [begin]: 631 KB
Memory [na maken array]: 5809 KB
Memory [na unset array]: 631 KB
Memory [memory na serialize array]: 1148 KB
Memory [na maken string]: 1510 KB
Hoeveelheid bytes in string (uitgedrukt in KB) 878
Memory [na unset string]: 631 KB


Wat mij vooral opvalt is de hoeveelheid begin geheugen.. Bij de TS is dat 130KB, bij mij 631KB. Waar komen die vandaan?

Acties:
  • 0 Henk 'm!

  • ACM
  • Registratie: Januari 2000
  • Niet online

ACM

Software Architect

Werkt hier

SvMp schreef op woensdag 12 mei 2010 @ 09:10:
Een array met 30.000 keer de string 'Hoi' blijkt meer dan 7 MB geheugen te nemen.
Er vanuit gaande dat de string 4 bytes groot is incl. zero aan het eind, en de key een integer is van 4 bytes, zou deze string naar verwachting 4 x 30.000 = 120.000 bytes geheugen nemen. Uiteraard is er overhead aangezien PHP realtime interpreteert. Echter meer dan het 50-voudige vind ik wel heel extreem.
Je vergeet nog even de overhead van de zval-constructie per string (en de array-keys wellicht) zelf en de overhead van de array/hashmap-constructie. Juist met korte strings is die eerste natuurlijk relatief groot.
Is het normaal dat PHP zo inefficient met arrays om gaat? Want als dat zo is, dan moet je vooral grote arrays gaan mijden.
Ik heb het zelf inderdaad ook wel eens op de "hard way" ontdekt. Iets van 80MB (on-disk grootte in een database) aan hashmap-based integer-integer -> float mapping werd meer dan 1GB ram-gebruik :X
db_inc schreef op woensdag 12 mei 2010 @ 09:34:

PHP:
1
for ($i=0; $i<30000; $i++) $t[i]='Hoi';
Als je die code letterlijk gebruikt hebt... dan heb je enkel getest dat je 30.000x de positie 'i' hebt kunnen overschrijven met dezelfde string ;)

[ Voor 3% gewijzigd door ACM op 12-05-2010 12:05 ]


Acties:
  • 0 Henk 'm!

  • SvMp
  • Registratie: September 2000
  • Niet online
hostname schreef op woensdag 12 mei 2010 @ 11:26:
Wat mij vooral opvalt is de hoeveelheid begin geheugen.. Bij de TS is dat 130KB, bij mij 631KB. Waar komen die vandaan?
Extensions? Ik heb hier @work amper extensions, nl. MySQL en Memcache. Op mijn thuis-server heb ik veel meer, en gebruikt in het begin ook al stukken meer.
ACM schreef op woensdag 12 mei 2010 @ 12:02:
Je vergeet nog even de overhead van de zval-constructie per string (en de array-keys wellicht) zelf en de overhead van de array/hashmap-constructie. Juist met korte strings is die eerste natuurlijk relatief groot.
Daar heb ik inderdaad niet aan gedacht. Wel heb ik het experiment ook gedaan met numerieke waardes, de array werd simpelweg gevuld met de waarde 1. Gebruikte iets minder geheugen, maar toch nog 5988 KB.

[ Voor 6% gewijzigd door SvMp op 12-05-2010 12:10 ]


Acties:
  • 0 Henk 'm!

  • FragFrog
  • Registratie: September 2001
  • Laatst online: 01:46
Memory [begin]: 54 KB
Memory [na maken array]: 2760 KB
Ga ik echter niet de hele tijd nieuwe strings lopen bakken, ergo:
PHP:
1
2
3
$string = 'Hoi';
$t=array(); 
for ($i=0; $i<30000; $i++) $t[]=$string;

Wordt dit:
Memory [begin]: 54 KB
Memory [na maken array]: 1588 KB
Versie 5.2.6 onder (32 bit) windows 2003 + Apache 2.2.15 :) Anderhalf meg vind ik eigenlijk nog wel meevallen, je moet je realiseren dat er behoorlijk wat extra geheugen nodig is per string object variabele en je 30.000 van die krengen hebt - heeft op zich geen donder met arrays te maken en alles met PHP's weak typed kern die ervoor zorgt dat een variabele zo ongeveer alles kan bevatten wat je als programmeur maar kan bedenken - daar hangt een prijskaartje aan :+

//edit
Een mooie illustratie:
PHP:
1
2
3
$string = 'Hoi';
$t=array(); 
for ($i=0; $i<30000; $i++) $t[] = & $string;

Je zou verwachten dat, aangezien er geen data opgeslagen wordt maar enkel references de array nu een stuk kleiner is, nietwaar?
Memory [begin]: 54 KB
Memory [na maken array]: 1588 KB
Inderdaad, niet waar :+

[ Voor 55% gewijzigd door FragFrog op 12-05-2010 13:25 ]

[ Site ] [ twitch ] [ jijbuis ]


Acties:
  • 0 Henk 'm!

Verwijderd

Zonder index:

Memory [begin]: 56 KB
Memory [na maken array]: 2645 KB
Memory [na unset array]: 56 KB
Memory [memory na serialize array]: 573 KB
Memory [na maken string]: 935 KB
Hoeveelheid bytes in string (uitgedrukt in KB) 878
Memory [na unset string]: 56 KB

PHP Version 5.2.11

Acties:
  • 0 Henk 'm!

  • RobIII
  • Registratie: December 2001
  • Niet online

RobIII

Admin Devschuur®

^ Romeinse Ⅲ ja!

(overleden)
Verwijderd schreef op woensdag 12 mei 2010 @ 13:44:
Zonder index:
*snip*

PHP Version 5.2.11
Allemaal leuk en aardig, maar zullen we stoppen allemaal onze "stats" te posten? Erg veel voegt het zo niet toe ;)

[ Voor 33% gewijzigd door RobIII op 12-05-2010 13:49 ]

There are only two hard problems in distributed systems: 2. Exactly-once delivery 1. Guaranteed order of messages 2. Exactly-once delivery.

Je eigen tweaker.me redirect

Over mij


Acties:
  • 0 Henk 'm!

  • hostname
  • Registratie: April 2009
  • Laatst online: 16-09 09:13
SvMp schreef op woensdag 12 mei 2010 @ 12:09:
[...]

Extensions? Ik heb hier @work amper extensions, nl. MySQL en Memcache. Op mijn thuis-server heb ik veel meer, en gebruikt in het begin ook al stukken meer.
Heb het ook getest zonder extensions, en het beginverbruik ligt slechts een 30KB lager. Heb overigens ook alleen maar Xdebug en gearman extra.
FragFrog schreef op woensdag 12 mei 2010 @ 13:14:
[...]
//edit
Een mooie illustratie:
PHP:
1
2
3
$string = 'Hoi';
$t=array(); 
for ($i=0; $i<30000; $i++) $t[] = & $string;

Je zou verwachten dat, aangezien er geen data opgeslagen wordt maar enkel references de array nu een stuk kleiner is, nietwaar?
[...]Inderdaad, niet waar :+
Ja, maar in dit geval blijft het wel constant, onafhankelijk van de lengte van de string:
PHP:
1
2
3
4
5
6
7
8
9
10
11
12
<?php
$long = str_repeat('a longer string', 200);
$short = str_repeat('shorter', 25);
echo "Begin: " . round(memory_get_usage() / 1024) . " KB\n";
$t = array();
for($i = 0; $i < 30000; $i++) $t[] = &$long;
echo "Reference long: " . round(memory_get_usage() / 1024) . " KB\n";
unset($t);
echo "Clean: " . round(memory_get_usage() / 1024) . " KB\n";
$t = array();
for($i = 0; $i < 30000; $i++) $t[] = &$short;
echo "Reference short: " . round(memory_get_usage() / 1024) . " KB\n";

geeft:
Begin: 615 KB
Reference long: 3449 KB
Clean: 615 KB
Reference short: 3449 KB
Kennelijk neemt de reference evenveel geheugen in als de de korte string van.

Acties:
  • 0 Henk 'm!

  • Kalentum
  • Registratie: Juni 2004
  • Laatst online: 12:21
Het geheugengebruik ligt niet aan wat je in de array stopt maar aan de array zelf:
http://bugs.php.net/bug.php?id=41053
Hier staat per entry zo'n 62 bytes. Dat is misschien in recentere versies wat lager geworden.

Intern worden er referenties naar waarden opgeslagen.
PHP:
1
2
3
4
5
6
$s1 = 'foo';
$s2 = 'bar';

for ($i=0; $i<30000; $i++) $t[]= 'foo'; // Levert 3016 KB op
for ($i=0; $i<30000; $i++) { $t[]= $s1;} // Levert 1727 Kb op
for ($i=0; $i<30000; $i+=2) { $t[]= $s1;$t[] = $s2;} // Levert 1739 KB


En die interne referenties worden verbroken als je iets wijzigt:
PHP:
1
2
3
4
5
6
7
8
$t = array();
$s1 = 'foo';
for ($i=0; $i<30000; $i++) { $t[]= $s1;}
echo $t[23]."\n"; // foo
echo $t[24]."\n"; // foo
$t[24] = 'bar'; 
echo $t[23]."\n";//bar
echo $t[24]."\n";//foo (dus 1 foo is nu bar)

Maar niet als je in php een referentie gebruikt
PHP:
1
2
3
4
5
6
7
8
$t = array();
$s1 = 'foo';
for ($i=0; $i<30000; $i++) { $t[]= & $s1;}
echo $t[23]."\n"; // foo
echo $t[24]."\n"; // foo
$t[24] = 'bar'; 
echo $t[23]."\n";//bar
echo $t[24]."\n";//bar (alle foo zijn nu bar)

Acties:
  • 0 Henk 'm!

  • FragFrog
  • Registratie: September 2001
  • Laatst online: 01:46
rutgerw schreef op woensdag 12 mei 2010 @ 13:53:
Het geheugengebruik ligt niet aan wat je in de array stopt maar aan de array zelf:
http://bugs.php.net/bug.php?id=41053
Wederom, volgens mij heeft dat niets (specifiek) te maken met arrays en alles met het feit dat je domweg voor elke variabele (en elk array element is een variabele) weer die overhead krijgt.

Ter illustratie:
PHP:
1
2
$string = 'Hoi';
for ($i=0; $i<30000; $i++) $$i = $string;

Resulteert in 1744 KB verbruik (ten opzichte van 1588 KB in het array voorbeeld). Ergo, ook zonder een array te gebruiken resulteert 30.000 variabelen opslaan in grofweg anderhalf meg geheugenverbruik, of ~ 58 bytes / variabele.

Uit je link:
[2007-04-11 13:29 UTC] stas@php.net Each element requires a value structure (zval) which takes 16 bytes. Also requires a hash bucket - which takes 36 bytes. That gives 52 bytes per value. Memory allocation headers take another 8 bytes*2 - which gives 68 bytes. Pretty close to what you have.
Ziet er naar uit dat ze't zelfs nog wat verbeterd hebben recentelijk :)

[ Voor 20% gewijzigd door FragFrog op 12-05-2010 14:18 ]

[ Site ] [ twitch ] [ jijbuis ]


Acties:
  • 0 Henk 'm!

  • frickY
  • Registratie: Juli 2001
  • Laatst online: 09:36
rutgerw schreef op woensdag 12 mei 2010 @ 13:53:
En die interne referenties worden verbroken als je iets wijzigt:
PHP:
1
2
3
4
5
6
7
8
$t = array();
$s1 = 'foo';
for ($i=0; $i<30000; $i++) { $t[]= $s1;}
echo $t[23]."\n"; // foo
echo $t[24]."\n"; // foo
$t[24] = 'bar'; 
echo $t[23]."\n";//bar
echo $t[24]."\n";//foo (dus 1 foo is nu bar)
Dat is de copy-on-write optimalisatie.
Als je de array kopieert naar een andere variabele zal het geheugengebruik ook vrijwel hetzelfde blijven in plaats van te verdubbelen, totdat je één van de arrays gaat wijzigen.

[ Voor 3% gewijzigd door frickY op 12-05-2010 16:18 ]


Acties:
  • 0 Henk 'm!

  • epic007
  • Registratie: Februari 2004
  • Laatst online: 25-08 11:27
Volgens de documentatie kan je aan memory_get_usage ook de de waarde true meegeven om het werkelijke geheugen op te vragen.
Iemand dat al geprobeerd ?

PHP:
1
int memory_get_usage ([ bool $real_usage = false ] )

Acties:
  • 0 Henk 'm!

  • nickb90
  • Registratie: Mei 2007
  • Laatst online: 13-09 23:30
@epic007

Jou code werkt niet:
Parse error: syntax error, unexpected T_STRING

bij de volgende code
PHP:
1
2
3
4
5
6
int memory_get_usage ([ bool $real_usage = false ] );

$long = str_repeat('a longer string', 200); 
$short = str_repeat('shorter', 25); 
..
..

Acties:
  • 0 Henk 'm!

  • Creepy
  • Registratie: Juni 2001
  • Laatst online: 12:16

Creepy

Tactical Espionage Splatterer

Nogal logisch, hij geeft ook geen kant en klare bruikbare PHP code. Als je de moeite had genomen om even op het linkje in z'n post te klikken dan had je wel kant en klare code gehad. No offence maar als je niks hebt toe te voegen post dan ook aub niet.

"I had a problem, I solved it with regular expressions. Now I have two problems". That's shows a lack of appreciation for regular expressions: "I know have _star_ problems" --Kevlin Henney

Pagina: 1