PHP如何衡量网站程序内存用量- memory_get_usage

How to measure memory usage in PHP script

 前言

一支好的程式除了程式行为要正确以外,也要考量资源是否是有效率的被使用,例如内存使用量是否正常以及程式执行时间是否合理等,都是该被列入考量的。因此,如何衡量程式所使用的资源量,也是开发人员必须的技能之一。

PHP主要有两个内建函式可以用来取得程式的内存用量,它们分别是memory_get_usage及memory_​get_​peak_​usage。

本篇将会介绍memory_get_usage的用法,以及简述何谓real usage。

 说明

memory_get_usage()
首先先看看memory_get_usage的使用方式,根据PHP Manual的定义:

int memory_get_usage ([ bool $real_usage = FALSE ] )

此方法将回传当下被分配到你的PHP脚本的内存量,单位是位元组(Byte)。

范例

我们想要衡量一个简单的小程式:

当一个阵列有1000个element,且每个element都是1时,会占用多少内存?

$start = memory_get_usage();

$temp = array();
for ($i = 0; $i < 1000; $i++) {
$temp[] = 1;
}

$end = memory_get_usage();

echo "程式開始時的内存用量: {$start} Bytes".PHP_EOL;
echo "程式結束時的内存用量: {$end} Bytes".PHP_EOL;
echo "我的程式使用了多少内存: ".($end - $start)." Bytes".PHP_EOL;

输出

程式開始時的内存用量: 396768 Bytes
程式結束時的内存用量: 433688 Bytes
我的程式使用了多少内存: 36920 Bytes

要得到内存使用量很简单,只要计算程式执行前后,内存总用量的差值即可,也就是$end - $start,这多出来的内存用量,很自然的就是程式执行过后,$temp所占用的内存量。因此从输出中,我们可以知道一个有1000个element的阵列,也就是$temp这个变数,共占用了36,920个位元组。

这边或许有人会有疑问,为什么程式一开始的内存用量不是0,而是396,768位元组呢?原因很简单,因为虽然我们只有执行一小段程式,但是PHP要能够执行,需要许多预先载入的函式库或是一些全域变数,所以这396,768个位元组至少包含了那些预先载入的程式所占用的部分啰!

memory_get_usage()与$real_usage
从定义中,我们知道memory_get_usage有个参数$real_usage可以使用,只是预设值是false。

那么,什么是$real_usage呢?

事实上,PHP从作业系统中取得内存,并不是目前需要多少就拿多少,而是会预先先取得一大区块,再交由PHP内部自行管理这些区块,这些预先取得的大区块的总和就是real usage,这些区块的总和,对于作业系统来说,才是我们的PHP程式真正的内存占用量。但是这些预先取得的大区块,不见得会被PHP真的使用完,我们的PHP对于内存的实际使用量internal usage,不见得会等于real usage。每个区块就像一个货柜一样,PHP程式只是先跟作业系统要了一个大货柜,然后再慢慢把东西塞进去货柜里面。

因此当$real_usage设定为:

true代表PHP程式对于整个作业系统实际的内存占用量。(real usage)
false代表PHP程式内部的变数实际上真正使用的内存使用量。(internal usage)
我们可以将前例的memory_get_usage第一个参数传入true进行观察:

$start = memory_get_usage(true);

$temp = array();
for ($i = 0; $i < 1000; $i++) {
$temp[] = 1;
}

$end = memory_get_usage(true);

echo "程式開始時的内存佔用量: {$start} Bytes".PHP_EOL;
echo "程式結束時的内存佔用量: {$end} Bytes".PHP_EOL;
echo "我的程式多佔用了多少内存: ".($end - $start)." Bytes".PHP_EOL;

输出:

程式開始時的内存佔用量: 2097152 Bytes
程式結束時的内存佔用量: 2097152 Bytes
我的程式多佔用了多少内存: 0 Bytes

我们可以看到,程式一开始就从作业系统中占用了2,097,152个位元组,程式结束后依旧是占用了2,097,152个位元组,所以整个程式的生命周期中,实际上就是占用了2,097,152个位元组,没有增加!

而从第一个例子中我们也知道,1000个element的阵列只会占用了36,920位元组,完全用不完一开始跟作业系统要的2,097,152位元组的内存,因此才不需要再跟作业系统要求更多内存。

另外,由于memory_get_usage回传的单位是Byte,若要取得KB量,可以除以1024,若要取得MB量,则可以除以1024 * 1024。

memory_limit
知道了如何衡量内存用量,那我们就可以来限制内存用量了。

我们经常会在php.ini中限制PHP程式内存用量的memory_limit

memory_limit = 128M

或透过ini_set()来设定

ini_set('memory_limit', '256M');
值得一提的是,这边所限制的内存是real_usage哦!

我们可以从这个范例来看:将内存限制为4MB,并测试写入1000000个Datetime物件。

ini_set('memory_limit', '4M');

echo '[i]internal/real'.PHP_EOL;
$temp = array();
for ($i = 0; $i < 1000000; $i++) {
$temp[] = new Datetime();
echo "[$i]".memory_get_usage().'/'.memory_get_usage(true).PHP_EOL;
}

输出

[i]internal/real
[0]404688/2097152
[1]405008/2097152
[2]405328/2097152

... 略 ...

[4092]1873552/2097152
[4093]1873872/2097152
[4094]1874192/2097152 <--- 内存要用完了
[4095]1907280/4194304 <--- 跟作業系統多要了一大區塊
[4096]2038672/4194304
[4097]2038992/4194304

... 略 ...

[9256]4017552/4194304
[9257]4017872/4194304
[9258]4018192/4194304 <--- 内存要用完了

Fatal error: Allowed memory size of 4194304 bytes exhausted
(tried to allocate 4096 bytes)

一开始我们可以看到internal内存用量随着新的Datetime物件不断被实体化出来而增加,但是real则不会。

当程式执行到i = 4094时,PHP会发现内存快要用完了,原本跟作业系统要的区块将不够用,所以跟作业系统新要了一大区块,而因为内存上限是4MB,所以此时可以成功要到新的内存区块。

但是当程式执行到i = 9258时,又遇到内存要用完的窘境,此时PHP试图要去跟作业系统要求内存则发现因为已经超过memory_limit = 4MB的限制,无法要求成功。

结语
是否使用real usage,端看开发人员目前的需求,若想要精准地知道目前程式执行片段,需要多少内存量(塞了多少货物),那么就得使用internal usage,若想要知道系统分配了多少内存给这支程式(拿了几个货柜),那就使用real usage吧。

参与评论