Protocol of some PHP Memory Stretching Fun

Excerpt from phpinfo output in a browser

The know your language department was having a day out with the gang from the know your runtime department.

The topic of today was to play with the PHP memory limit, let’s grab the laptop’s shell:

$ php -d memory_limit=1g -r='$t=" "; while($t.=$t);'
PHP Fatal error:  Allowed memory size of 1073741824 bytes exhausted /
(tried to allocate 1073741825 bytes) in Command line code on line 1

Voilà, usage of memory raised above the 1g limit. PHP exits as it reaches the limit.

And what happens if the setting is written like 1.5g?

$ php -d memory_limit=1.5g -r='$t=" "; while($t.=$t);'
PHP Fatal error:  Allowed memory size of 1073741824 bytes exhausted /
(tried to allocate 1073741825 bytes) in Command line code on line 1

That obvious counts as 1g. And with 1gb?

$ php -d memory_limit=1gb -r='$t=" "; while($t.=$t);'
PHP Fatal error:  Allowed memory size of 262144 bytes exhausted /
(tried to allocate 523800 bytes) in Unknown on line 0
Could not startup.

Ups, a startup error. Looks like there is a startup default of 262144 bytes (256k) that was triggered here. Obviously writing the limit setting as 1gb is not 1 gigabyte but 1 byte (As the FAQ in the PHP Manual says). And memory limit seems to be checked on allocation. Perhaps some php modules need the memory?

$ php -d memory_limit=786432 -r='$t=" "; while($t.=$t);'
PHP Fatal error:  Allowed memory size of 786432 bytes exhausted /
(tried to allocate 262145 bytes) in Command line code on line 1

Ah, 786432 is the minimum setting here w/o getting a startup error. Let’s test w/o modules:

$ php -n -d memory_limit=0 -r='$t=" "; while($t.=$t);'
Could not startup.

That looks like a check on init, so that looks like it comes after modules would have been loaded. Just an exit, no line 0. That’s what I call a real “Could not startup.” PHP just could not :).

Peeking into the parsing of the limit value further on:

$ php -d memory_limit=512gbkm -r='$t=" "; while($t.=$t);'
PHP Fatal error:  Allowed memory size of 536870912 bytes exhausted /
(tried to allocate 536870913 bytes) in Command line code on line 1

Only the first digits and the last letter is taken into account. I think this is the documented behaviour. There is more memory to consume:

$ php -d memory_limit=2g -r='$t=" "; while($t.=$t);'
PHP Fatal error:  String size overflow in Command line code on line 1

Strings have a limit, too. That’s in the PHP manual somewhere. As I double the strings length by each iteration, executing the command does not show where. This is a 64bit PHP version. Let’s switch the memory consumption strategy to get higher:

$ php -d memory_limit=2g -r='$t[]="     "; while($t[]=$t);'
PHP Fatal error:  Allowed memory size of 2147483648 bytes exhausted /
(tried to allocate 65536 bytes) in Command line code on line 1

Success! Again, usage raised above the memory limit. Let’s go up before it’s time for the first round of sandwiches:

$ php -d memory_limit=3g -r='$t[]="     "; while($t[]=$t);'
PHP Fatal error:  Allowed memory size of 3221225472 bytes exhausted /
(tried to allocate 71 bytes) in Command line code on line 1

And up to 4g:

$ php -d memory_limit=4g -r='$t[]="     "; while($t[]=$t);'
PHP Fatal error:  Allowed memory size of 4294967296 bytes exhausted /
(tried to allocate 71 bytes) in Command line code on line 1

I start to feel how long it takes between the error message and the return to the command prompt. And up to 6g:

$ php -d memory_limit=6g -r='$t[]="     "; while($t[]=$t);'
PHP Fatal error:  Allowed memory size of 6442450944 bytes exhausted /
(tried to allocate 71 bytes) in Command line code on line 1

Now the command prompt does not return any longer. PHP is still in the process list. What’s going on? Htop reveals 80-100% of one of my CPU cores used and 77.6% memory consumption while this system has 8 gb. Well looks like this is getting dirty. PHP didn’t terminate correctly any longer. Should I?

A SIGTERM later, the swapping party can begin, let’s grab all memory the system has – physically 8 gig:

$ php -d memory_limit=8g -r='$t[]="     "; while($t[]=$t);'
Terminated

As the swap was going close to 1 gig while the memory was close to it’s physical boundary, I decided to terminate the PHP process. Okay, don’t do this at home. But well, as we are close to the physical limits, what actually does happen when the -1 (unlimited) setting is given? You only know when you try:

$ php -d memory_limit=-1 -r='$t[]="     "; while($t[]=$t);'
Terminated

The computer did what it was commanded. Fine, means the user needs to take care on it’s own: manual termination was needed again. It felt like the memory consumption got up much faster, but probably I was not that careful like last time. So if you want to put your system in a freeze, the one-liner is above.

This leads to the question how negative values are generally interpreted. What about, let’s say, -2g?

$ php -d memory_limit=-2g -r='$t[]="     "; while($t[]=$t);'
^C

This is obviously like -1, unlimited. Looks like that any negative number just count as unlimited.

So time to switch to a 32bit system and look what’s going over there.

32bit galore: Windows XP

Ah, there is some olde windows XP system around the corner. And there must be an older ubuntu sys somewhere. Let’s poke that windows box a bit:

> php -n -d memory_limit=2g -r "$t=' '; while($t.=$t);"

Fatal error: Out of memory (allocated 537657344) /
(tried to allocate 1073741825 bytes) in Command line code on line 1
zend_mm_heap corrupted

Upsi. I think I should reduce the steps in memory consumption. The Ubuntu 9.10 laptop plays the File-system checks game in the meanwhile – obviously not booted for some hundred days so far. But there is no rush, as we can go on on win32:

> php -n -d memory_limit=2048m -r "$t=' '; while($t.=str_repeat(' ', 1024));"

Fatal error: Out of memory (allocated 346292224) /
(tried to allocate 345767938 bytes) in Command line code on line 1
zend_mm_heap corrupted

That didn’t make much of a difference, the numbers are a bit lower which might be a sign for what the problem might be. Probably the string limit on a 32bit windows system or some other system memory limit. So let’s use an array instead as it was so successful already, maybe this helps on windows to go around the (guessed) string issue as well:

> php -n -d memory_limit=2g -r "while($t[]=str_repeat(' ', 1048576));"

Fatal error: Out of memory (allocated 1987575808) /
(tried to allocate 1048577 bytes) in Command line code on line 1

Array FTW. This makes sense. The 2g limit makes PHP kicking out when reached. Hmm, this is a 32bit system, but why exactly did the PHP manual warned about an overflow? For the log:

> php -n -r "echo PHP_INT_MAX;"
2147483647

So let’s go over the boundaries, 3g is a suitable value:

> php -n -d memory_limit=3g -r "while($t[]=str_repeat(' ', 1048576));"

Fatal error: Out of memory (allocated 1987575808) /
(tried to allocate 1048577 bytes) in Command line code on line 1

That’s the same as the 2g limit which means, it can not go higher on that 32bit system. Let’s try -3g:

> php -n -d memory_limit=-3g -r "while($t[]=str_repeat(' ', 1048576));"

Fatal error: Allowed memory size of 1073741824 bytes exhausted /
(tried to allocate 1048577 bytes) in Command line code on line 1

That’s the same as 1g. Integer roundtrip FTW.

When running the same tests with the value expressed bytes (3g = 3221225472) there is no roundtrip but a cap on top. Both the positive and negative value:

> php -n -d memory_limit=3221225472 -r "while($t[]=str_repeat(' ', 1048576));"

Fatal error: Out of memory (allocated 1987575808) /
(tried to allocate 1048577 bytes) in Command line code on line 1

> php -n -d memory_limit=-3221225472 -r "while($t[]=str_repeat(' ', 1048576));"

Fatal error: Out of memory (allocated 1987575808) /
(tried to allocate 1048577 bytes) in Command line code on line 1

So on the Windows XP system, despite it has more RAM to offer, PHP caps the memory limit at two gigabytes.

32 bit galore: Unbuntu 9.10

In the meanwhile the beloved but olde ubuntu box could install PHP after refreshing the package managers cache. Let’s try the same:

$ php -d memory_limit=2g -r 'while($t[]=str_repeat(" ", 1048576));'
Fatal error: Allowed memory size of -2147483648 bytes exhausted /
(tried to allocate 1048577 bytes) in Command line code on line 1

There you are, the negative memory size. Same PHP_INT_MAX value on ubuntu i686 for a PHP 5.2.10-2ubunutu6.10. The windows version above is some PHP 5.3.6 or so. So probably that makes a difference.

That means on ubuntu it is rolling over at the highest positive integer falling into the highest negative integer number (-2147483648) and then up high back to 0 (4g). Do you remember the times cheating gold in classic games? Never go over 7FFF. Signed integer roundtrip. Some bit marks the sign.

The PHP 5.3 binary on the windows system shows some other behaviour. It’s a cap on top, it won’t go over 2147483647 into the negative range but stays at the positive maximum. A string to integer conversion has the same behaviour:

> php -r "echo (int) '2147483648';"
2147483647

The result on the ubuntu system is the same for casting the string to int btw. So could be OS-specific for the memory limit, or could be the PHP 5.3 version, but sandwiches are in the pipe, that’s for another day to find out.

Whitespaces and memory_limit

There was another thing I wanted to find out more about: What about having spaces around the ini value? Back on the comfortable system I started on. It’s the candidate to run some commands always with this PHP code:

-r 'echo "[", ini_get("memory_limit"), "]";  while($t[]=str_repeat(" ", 1048576));'

, but with various memory_limit values containing white-spaces:

$ php -d memory_limit='32   m' -r ...
[32   m]PHP Fatal error:  Allowed memory size of 33554432 bytes exhausted /
(tried to allocate 1048577 bytes) in Command line code on line 1
$ php -d memory_limit='      32   m'  -r ...
[      32   m]PHP Fatal error:  Allowed memory size of 33554432 bytes exhausted /
(tried to allocate 1048577 bytes) in Command line code on line 1
$ php -d memory_limit='      32   m    '  -r ...
PHP Fatal error:  Allowed memory size of 262144 bytes exhausted /
(tried to allocate 523800 bytes) in Unknown on line 0
Could not startup.

So white-spaces can be in front of the digits and between the digits and the shorthand character, but not after. Is there enough room to leave some message?

$ php -d memory_limit='      64m is too low for you coder?  /
**** to get more memory update your hosting plan, call 0800 66 /
tiger host now! ****  m'  -r ...
[      64m is too low for you coder?  **** to get more memory /
update your hosting plan, call 0800 66 tiger host now! ****  m] /
PHP Fatal error:  Allowed memory size of 67108864 bytes exhausted /
(tried to allocate 1048577 bytes) in Command line code on line 1

The values set it successfully to 64 megabytes and having a message to display right away when a user displays it’s php ini values. So I was so curious how this would look like, I needed to edit my php.ini right away and request phpinfo() via my beloved apache server.

Leaving a message to your users via phpinfo()

Now that’s what I call a feature. Here is the excerpt from the php.ini that generated the screen-shot on top and bottom of the post:

; Maximum amount of memory a script may consume (128MB)
; http://www.php.net/manual/en/ini.core.php#ini.memory-limit
memory_limit = "

                  16 Megabyte is too low?

    ###########################
  # Just call us for an memory upgrade now! #
############################



m"

I think that’s a nice ending for a post. As usual, you find lots of infos in the PHP Manual. Only if you know your language you have the whole fun – and that’s inter-operable.

Excerpt from phpinfo output in a browser


Resources

This entry was posted in PHP Development, Pressed, Reports, The Know Your Language Department, Uncategorized and tagged , , , , , . Bookmark the permalink.

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.