如何确定变量的内存占用量(大小)?


102

PHP(或PHP扩展)中是否有函数来查找给定变量使用多少内存?sizeof只是告诉我元素/属性的数量。

memory_get_usage这有助于我获得整个脚本使用的内存大小。有没有办法对单个变量执行此操作?

请注意,这是在开发机器上,因此加载扩展或调试工具是可行的。


编辑-这是5年后的事,有些问题仍未解决:(
Piskvor于

Answers:


46

您可能需要一个内存探查器。我已经收集了一些信息,但是我复制了一些可能对您有所帮助的重要信息。

您可能知道,Xdebug从2. *版本开始就放弃了内存分析支持。请在此处搜索“已删除的功能”字符串:http : //www.xdebug.org/updates.php

删除功能

删除了对内存配置文件的支持,因为它无法正常运行。

其他探查器选项

php-memory-profiler

https://github.com/arnaud-lb/php-memory-profiler。这是我在Ubuntu服务器上完成的操作:

sudo apt-get install libjudy-dev libjudydebian1
sudo pecl install memprof
echo "extension=memprof.so" > /etc/php5/mods-available/memprof.ini
sudo php5enmod memprof
service apache2 restart

然后在我的代码中:

<?php
memprof_enable();
// do your stuff
memprof_dump_callgrind(fopen("/tmp/callgrind.out", "w"));

最后callgrind.outKCachegrind打开文件

使用Google gperftools(推荐!)

首先,通过在此处下载最新的软件包来安装Google gperftoolshttps//code.google.com/p/gperftools/

然后像往常一样:

sudo apt-get update
sudo apt-get install libunwind-dev -y
./configure
make
make install

现在在您的代码中:

memprof_enable();

// do your magic

memprof_dump_pprof(fopen("/tmp/profile.heap", "w"));

然后打开您的终端并启动:

pprof --web /tmp/profile.heap

pprof将在您现有的浏览器会话中创建一个新窗口,如下所示:

使用memprof和gperftools进行PHP内存分析

Xhprof + Xhgui(我认为最好的配置cpu和内存的文件)

使用XhprofXhgui,您还可以分析cpu的使用情况,或者仅分析内存使用情况(如果这是当前的问题)。这是一个非常完整的解决方案,它使您可以完全控制,并且日志可以写在mongo或文件系统中。

有关更多详细信息,请参见此处

黑火

Blackfire是由Symfony2伙计SensioLabs(https://blackfire.io/)进行的PHP分析器

如果您使用puphpet来设置您的虚拟机,您将很高兴知道它的支持;-)

Xdebug和跟踪内存使用情况

XDEBUG2是PHP的扩展。Xdebug允许您记录所有函数调用,包括参数和以不同格式返回值到文件的格式。共有三种输出格式。一个被定义为人类可读的跟踪,另一个则更易于解析,更适合于计算机程序,而最后一个则使用HTML格式化跟踪。您可以使用该设置在两种不同格式之间切换。这里有一个例子

p

forp简单,非侵入式,面向生产的PHP分析器。一些功能是:

  • 时间测量和每个功能分配的内存

  • CPU使用率

  • 函数调用的文件和行号

  • 输出为Google的跟踪事件格式

  • 功能说明

  • 功能分组

  • 函数别名(对匿名函数有用)

DBG

DBG是一个功能齐全的php调试器,它是一个交互式工具,可以帮助您调试php脚本。它可以在生产和/或开发的WEB服务器上运行,并允许您从IDE或控制台本地或远程调试脚本,其功能包括:

  • 远程和本地调试

  • 显式和隐式激活

  • 调用堆栈,包括函数调用,动态和静态方法调用及其参数

  • 在调用堆栈中导航并能够评估相应(嵌套)位置的变量

  • 进入/退出/跳过/运行到光标功能

  • 条件断点

  • 全局断点

  • 记录错误和警告

  • 多个同时会话以进行并行调试

  • 支持GUI和CLI前端

  • 支持IPv6和IPv4网络

  • 调试器传输的所有数据都可以选择受SSL保护


2
正是我一直在寻找的信息,谢谢。
Piskvor于

93

没有直接的方法来获取单个变量的内存使用情况,但是正如Gordon建议的那样,您可以使用memory_get_usage。这将返回分配的内存总量,因此您可以使用一种变通方法并在使用前后测量使用情况,以获取单个变量的使用情况。这有点棘手,但应该可以。

$start_memory = memory_get_usage();
$foo = "Some variable";
echo memory_get_usage() - $start_memory;

请注意,这绝不是一种可靠的方法,您不能确定在分配变量时没有其他事情触及到内存,因此只能将其用作近似值。

实际上,您可以通过在函数内部创建变量的副本并测量所使用的内存来将其转换为函数。还没有测试过,但是原则上,我没有发现任何问题:

function sizeofvar($var) {
    $start_memory = memory_get_usage();
    $tmp = unserialize(serialize($var));
    return memory_get_usage() - $start_memory;
}

14
$tmp = $var将创建一个浅表副本。在修改$ tmp之前,这不会分配更多的内存。
戈登

@戈登,你是对的,我有点忽略了这一点。由于我无法找出在不更改变量类型或大小的情况下修改变量的正确方法,因此将其保留。也许有人可以提出一个正确的主意:)
Tatu Ulmanen 2010年

7
怎么样$tmp = unserialize(serialize($var))?这将结合上面的Aistina方法。
戈登

3
同样,由于$var已经是传递给函数的内容的浅表副本或引用,因此您不需要$tmp,但可以将其重新分配给$var。这会将内部参考从保存$tmp$var
戈登

是不是有一些取消引用更优雅的方式$tmp$var
托马什Zato -恢复莫妮卡

24

不,那里没有。但是您可以serialize($var)检查strlen结果的近似值。


这是一种更好的方法,因为它避免了整个GC的工作。
Gleno

12
这是一个可怕的近似。在PHP的阵列的每一项是〜80个字节,但strlen(serialize(array(1,2,3)))是30
gsnedders

2
@ Aistina,-1。您正在衡量错误的内容。变量和序列化变量是完全不同的两件事,它们将给出完全不同的结果。
Pacerier

1
不仅如此,它还会在某些不可序列化的数据结构(例如循环引用)上完全失败。
duskwuff -inactive

20

在回答塔图·乌尔曼尼斯时,回答:

请注意,它$start_memory本身将占用内存(PHP_INT_SIZE * 8)。

因此,整个功能应变为:

function sizeofvar($var) {
    $start_memory = memory_get_usage();
    $var = unserialize(serialize($var));
    return memory_get_usage() - $start_memory - PHP_INT_SIZE * 8;
}

很抱歉将此添加为额外的答案,但我尚无法对答案发表评论。

更新:* 8不确定。它可能显然取决于php版本,可能取决于64/32位。


4
你能解释为什么* 8吗?谢谢!
sierrasdetandil

@sierrasdetandil似乎$ start_memory并不只占用PHP_INT_SIZE字节,而是PHP_INT_SIZE*8。:您可以尝试通过调用这个函数,它应该返回0function sizeofvar() { $start_memory = memory_get_usage(); return memory_get_usage() - $start_memory - PHP_INT_SIZE*8; }

8似乎不是恒定的。在您的开发系统(PHP 5.6.19)上执行注释功能后,它返回-16。同样,有趣的是,从中php -a,调用函数的两行给出了各种不同的值。
Paul DelRe

@PaulDelRe是的,可能这取决于版本/ 64位。

现在,致命错误发生在unserialize()调用处。那没有帮助!如果一个变量太大,则用完内存,对该变量调用一个函数将消耗更多的内存。:(
john ktejik

4

看到:

请注意,尽管如此,这不会为您提供特定变量的内存使用量。但是您可以在分配变量之前和之后对这些函数进行调用,然后比较这些值。那应该使您对所使用的内存有一个了解。

您也可以看看PECL扩展Memtrack,尽管该文档有点缺乏(如果不是说的话),实际上根本不存在。


是。它可以间接用于回答问题。
Notinlist 2010年

3

您可以选择计算回调返回值的内存差异。这是PHP 5.3+中可用的更优雅的解决方案。

function calculateFootprint($callback) {
    $startMemory = memory_get_usage();
    $result = call_user_func($callback);
    return memory_get_usage() - $startMemory;
}

$memoryFootprint = calculateFootprint(
    function() {
        return range(1, 1000000);
    }
);

echo ($memoryFootprint / (1024 * 1024)) . ' MB' . PHP_EOL;

3

您无法追溯地计算变量的确切占用空间,因为两个变量可以共享内存中的相同分配空间

让我们尝试在两个数组之间共享内存,我们看到分配第二个数组的开销是第一个数组的一半。当我们取消第一个设置时,第二个几乎仍使用所有内存。

echo memory_get_usage()."\n"; // <-- 433200
$c=range(1,100);
echo memory_get_usage()."\n"; // <-- 444348 (+11148)
$d=array_slice($c, 1);
echo memory_get_usage()."\n"; // <-- 451040 (+6692)
unset($c);
echo memory_get_usage()."\n"; // <-- 444232 (-6808)
unset($d);
echo memory_get_usage()."\n"; // <-- 433200 (-11032)

因此我们不能得出第二个数组使用一半内存的结论,因为当我们取消设置第一个数组时,它将变为假。

要获得有关如何在PHP中分配内存以及如何使用内存的完整信息,建议您阅读以下文章:PHP数组(和值)到底有多大?(提示:大!)

PHP文档中的“ 引用计数基础 ”也有很多有关内存使用的信息,并且引用计数到共享数据段。

这里公开的不同解决方案很适合用于近似计算,但是没有一个解决方案可以处理PHP内存的微妙管理。

  1. 计算新分配的空间

如果要在分配后重新分配空间,则必须memory_get_usage()在分配之前和之后使用,因为将其与副本一起使用确实会使您对实物有错误的认识。

// open output buffer
echo "Result: ";
// call every function once
range(1,1); memory_get_usage();

echo memory_get_usage()."\n";
$c=range(1,100);
echo memory_get_usage()."\n";

请记住,如果要存储first的结果,则memory_get_usage()该变量必须在之前已经存在,并且memory_get_usage()必须在之前的时间被调用,并且每个其他函数也必须存在。

如果要像上面的示例一样进行回显,则必须已打开输出缓冲区,以避免打开输出缓冲区所需的记帐内存。

  1. 计算所需空间

如果要依赖一个函数来计算存储变量副本所需的空间,则以下代码将进行不同的优化:

<?php
function getMemorySize($value) {
    // existing variable with integer value so that the next line
    // does not add memory consumption when initiating $start variable
    $start=1;
    $start=memory_get_usage();
    // json functions return less bytes consumptions than serialize
    $tmp=json_decode(json_encode($value));
    return memory_get_usage() - $start;
}

// open the output buffer, and calls the function one first time
echo ".\n";
getMemorySize(NULL);

// test inside a function in order to not care about memory used
// by the addition of the variable name to the $_GLOBAL array
function test() {
    // call the function name once 
    range(1,1);

    // we will compare the two values (see comment above about initialization of $start)
    $start=1;
    $start=memory_get_usage();
    $c=range(1,100);
    echo memory_get_usage()-$start."\n";
    echo getMemorySize($c)."\n";
}
test();

// same result, this works fine.
// 11044
// 11044

请注意,变量名称的大小在分配的内存中很重要。

  1. 检查您的代码!

变量的基本大小由PHP源代码中使用的内部C结构定义。在数字的情况下,此大小不会波动。对于字符串,它将添加字符串的长度。

typedef union _zvalue_value {
    long lval;                  /* long value */
    double dval;                /* double value */
    struct {
        char *val;
        int len;
    } str;
    HashTable *ht;              /* hash table value */
    zend_object_value obj;
} zvalue_value;

如果我们不考虑变量名的初始化,那么我们已经知道变量使用了多少(在数字和字符串的情况下):

如果是数字,则为44个字节

+ 24个字节(字符串)

+字符串的长度(包括最后的NUL字符)

(这些数字可能会有所不同,具体取决于PHP版本)

由于内存对齐,您必须四舍五入为4个字节的倍数。如果该变量位于全局空间中(而不是在函数内部),则还将分配另外64个字节。

因此,如果要使用此页面内的代码之一,则必须考虑一些简单的测试用例(字符串或数字),以确保结果与这些数据相匹配,并考虑到本文中的每种指示($ _GLOBAL数组,第一个函数调用,输出缓冲区,...)


1
...甚至还不及进入zvalueis_ref和写入时复制的内部。谢谢。
Piskvor于

1
多亏了您,我错过了PHP手册中的该页面。我添加了链接以完成答案(但是我想您已经阅读过该链接)。
亚当

2

我有一个类似的问题,我使用的解决方案是将变量写入文件,然后在其上运行filesize()。大致像这样(未经测试的代码):

function getVariableSize ( $foo ) 
{
    $tmpfile = "temp-" . microtime(true) . ".txt";
    file_put_contents($tmpfile, $foo);
    $size = filesize($tmpfile);
    unlink($tmpfile);
    return $size;
}

该解决方案并不是很快,因为它涉及磁盘IO,但是它应该给您的东西比memory_get_usage技巧更精确。这仅取决于您需要多少精度。


应该注意的是,该解决方案仅适用于字符串和字符串的一维数组,使用起来strlen会更容易。
亚当


1
function mesure($var){
    $start = memory_get_usage();
    if(is_string($var)){
        $newValue = $var . '';
    }elseif(is_numeric($var)){
        $newValue = $var + 0;
    }elseif(is_object($var)){
        $newValue = clone $var;
    }elseif(is_array($var)){
        $newValue = array_flip($var, []);
    }
    return memory_get_usage() - $start;
}

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.