与 PHP 5.6 相比,我在 Php 7 中面临更多的内存消耗

I am facing more memory consumption in Php 7 compare to PHP 5.6

当我做基准测试时,我发现 PHP 7 使用的内存比 PHP 5.6 多。

所以,我做了一个测试。我 运行 一个脚本只包含:

  $a=10;

及以下是我在没有任何模块的情况下使用 PHP CLI 时使用的内存的结果 (php -n)

php 5.6 = 222600 Bytes
php 7.0 = 350448 Bytes

* PHP 5.6.23 (cli) (built: Jun 22 2016 12:13:15)
Copyright (c) 1997-2016 The PHP Group
Zend Engine v2.6.0, Copyright (c) 1998-2016 Zend Technologies 

* PHP 7.0.9 (cli) (built: Jul 20 2016 10:47:41) ( NTS )
Copyright (c) 1997-2016 The PHP Group
Zend Engine v3.0.0, Copyright (c) 1998-2016 Zend Technologies

环境是

谁能解释为什么我得到这个结果?


附加测试

按照@gordon 的建议使用此代码,

$i=0;
while ($i++ < 100000) ;

php 5.6: 227408 字节

php 7.0:386640 字节

我用这段代码确定了内存使用情况:

echo PHP_EOL;
echo "Memory Usage :".memory_get_usage();
echo PHP_EOL;
echo "Real Memory Usage :".memory_get_usage(true);
echo PHP_EOL;
echo "Real Peak Memory Usage :".memory_get_peak_usage(true);
echo PHP_EOL;
echo "Peak Memory Usage :".memory_get_peak_usage();

Php 5.6 与 Php 7.0.

相比需要更少的字节

您的测试显示 PHP 7.0 中的内存使用量更多,因为测试代码非常简单。

PHP 由于彻底重写了内部 ZEND 引擎(解释器核心)

,7.0 比 PHP 5.6 使用更少的内存(并且更快)

正如 Gordon 评论的那样,PHP 7.0 中的新功能和改进很可能需要 "bootstrap",这在测试时会导致负面结果关于小段代码。

让我们尝试更复杂的事情:构建一个包含 10.000 个整数的数组,然后使用 Quicksort 算法对其进行排序。

这是我得到的结果:

PHP 7.0

Memory Usage: 1432752
Real Memory Usage: 4194304
Real Peak Memory Usage: 4194304
Peak Memory Usage: 3152360


PHP 5.6

Memory Usage: 2756744
Real Memory Usage: 4980736
Real Peak Memory Usage: 6029312
Peak Memory Usage: 5710464

而且仍然是一个简单的 20 行快速排序与具有数千行代码、许多 类 声明、许多实例的现实世界应用程序相去甚远...

我在 http://phptester.net

上 运行 进行了测试

下面是代码

<?php
function quick_sort($array)
{
    $length = count($array);
    $pivot = $array[0];
    $left = $right = array();
    for($i = 1; $i < count($array); $i++)
    {
        if($array[$i] < $pivot)
        {
            $left[] = $array[$i];
        }
        else
        {
            $right[] = $array[$i];
        }
    }
    return array_merge(quick_sort($left), array($pivot), quick_sort($right));
}

$unsorted = array();
for($i=0;$i<10000;$i++)
{
    $unsorted[] = rand(1,1000000);
}

$sorted = quick_sort($unsorted);

$lf = "<br/>";

echo $lf;
echo "Memory Usage: ".memory_get_usage();
echo $lf;
echo "Real Memory Usage: ".memory_get_usage(true);
echo $lf;
echo "Real Peak Memory Usage: ".memory_get_peak_usage(true);
echo $lf;
echo "Peak Memory Usage: ".memory_get_peak_usage();
echo $lf;

PHP 中快速排序算法的功劳:http://andrewbaxter.net/quicksort.php

要理解您的问题的答案 - 您需要了解 PHP5 和 PHP7 如何分配内存。

PHP5 分配内存 "By Request" 假设它是 Zend Engine 结构。

PHP7这边做了一些优化,所以在内存分配上"by the chunks"

  • 启动时分配大块内存
  • 在应用程序内分配时,它会分配小块以避免碎片化

这种差异可以很好地提高性能(因为引擎不需要在每次需要时都在运行时分配内存并节省一些碎片时间),但它会增加 "very small" 程序的内存消耗,尺寸小于 "chunk size".

是的,PHP7 在大型程序上非常节省内存。

您可以在下面的图片中查看所有这些差异:

使用基准构建的图形: 1.php

<?php

ini_set('memory_limit', '5G');
$a=range(1,$argv[1]);

echo PHP_EOL;
echo "Memory Usage :".memory_get_usage();
echo PHP_EOL;
echo "Real Memory Usage :".memory_get_usage(true);
echo PHP_EOL;
echo "Real Peak Memory Usage :".memory_get_peak_usage(true);
echo PHP_EOL;
echo "Peak Memory Usage :".memory_get_peak_usage();
echo PHP_EOL;

bench.sh

// Small programs
(for i in $(seq 0 5 5000);do php5 dev/Tools/mem/1.php $i|cut -f 2 -d:|sed -r 's/^$/;/g'|sed -r 's/([0-9]+)$/,/g'|tr -d '\n'; echo $i; done)|tr -d '\n'|sed -r 's/$/]/g'|sed -r 's/^;/[/g'>php5.m
(for i in $(seq 0 5 5000);do php dev/Tools/mem/1.php $i|cut -f 2 -d:|sed -r 's/^$/;/g'|sed -r 's/([0-9]+)$/,/g'|tr -d '\n'; echo $i; done)|tr -d '\n'|sed -r 's/$/]/g'|sed -r 's/^;/[/g'>php7.m
//Large Programs
(for i in $(seq 0 50 100000);do php5 dev/Tools/mem/1.php $i|cut -f 2 -d:|sed -r 's/^$/;/g'|sed -r 's/([0-9]+)$/,/g'|tr -d '\n'; echo $i; done)|tr -d '\n'|sed -r 's/$/]/g'|sed -r 's/^;/[/g'>php5.m    
(for i in $(seq 0 50 100000);do php dev/Tools/mem/1.php $i|cut -f 2 -d:|sed -r 's/^$/;/g'|sed -r 's/([0-9]+)$/,/g'|tr -d '\n'; echo $i; done)|tr -d '\n'|sed -r 's/$/]/g'|sed -r 's/^;/[/g'>php7.m

八度抽屉

php7;php7=ans;
php5;php5=ans;
plot(php5(:,5)',[php5(:,1:4)';php7(:,1:4)']');
legend("PHP5 mgu", "PHP5 rmu", "PHP5 rpmu", "PHP5 pmu","PHP7 mgu", "PHP7 rmu", "PHP7 rpmu", "PHP7 pmu");

阅读更多

  1. 官方 PHP7/PHP-NG 介绍: https://drive.google.com/file/d/0B3UKOMH_4lgBUTdjUGxIZ3l1Ukk/view
  2. 官方PHP7/PHP-NG内部改动说明: https://wiki.php.net/phpng-int
  3. 官方扩展迁移指南: https://wiki.php.net/phpng-upgrading
  4. 来自@NikiC 的好文章: http://nikic.github.io/2015/05/05/Internal-value-representation-in-PHP-7-part-1.htmlhttp://nikic.github.io/2015/06/19/Internal-value-representation-in-PHP-7-part-2
  5. PHP5 内部细节:http://www.phpinternalsbook.com/
  6. Badoo PHP5->PHP7 成功案例详情:https://techblog.badoo.com/blog/2016/03/14/how-badoo-saved-one-million-dollars-switching-to-php7/

预先我想说的是,如果您在实际代码中看到 PHP 7 中报告的内存使用量较高,最可能的原因是 PHP 7 将 mysqlnd 缓冲查询的内存使用情况报告为部分内存占用。在 PHP 5 中没有报告此内存使用情况(但当然仍然使用内存)。对于大型查询,这可能会产生很大的不同。

现在到你的实际情况,这基本上是请求启动后 PHP 的内存使用情况。 已经解释了为什么 "real" 内存使用存在差异,这是内存使用指标,它报告 PHP 的分配器从内核的系统分配器请求了多少内存。正如 MobDev 指出的那样 PHP 7 将以更大的块 (2MB) 分配内存,并且在缓存分配的块方面也更加积极。

然而,这并不能解释 "non-real" 内存使用中的差异,因为内存使用没有考虑这些分配器细节。使用内存分析器可以很容易地检查内存是否准确,例如通过 运行 PHP 到 USE_ZEND_ALLOC=0 valgrind --tool=massifUSE_ZEND_ALLOC=0 部分指示 PHP 不要使用它自己的分配器。

首先,这将向您展示实际内存使用情况与 PHP 报告的内存使用情况有很大差异。 Massif 将显示 PHP 5.6 的 3.2MB 使用量和 PHP 7 的 2.3MB 的使用量。原因是 PHP 只报告通过它自己的分配器 (ZMM) 的内存,而许多结构跨多个请求生存不分配使用它。

通过系统分配器的最大分配(因此未在内存使用情况中报告)是:

                       | PHP 5.6 | PHP 7
interned string buffer | 1 MB    | 150 KB + strings
GC buffer              | 320 KB  | 320 KB
internal classes/funcs | >1.3 MB | >0.5 MB

"internal classes/funcs"这个数字是一个粗略的下限,因为这里涉及到很多难以统计的小分配。一个主要区别是可见的,即 PHP 7 不使用固定的内部字符串缓冲区(列出的大小是我看到的哈希表缓冲区的大小,不包括字符串本身的大小)。

但是,这仍然没有回答实际报告的内存使用情况的问题。在这种情况下,最大的分配是:

             | PHP 5.6 | PHP 7
VM stack     | 130 KB  | 256 KB
Object store | 64 KB   | (8 KB)
CG arena     | ---     | 64 KB

这里有一些不同之处。最主要的是 PHP 7 使用更大的 VM 页面大小(大约两倍大)。此外 PHP 7 使用 arena 来存储某些结构(如用户函数),其默认大小为 64KB。另一方面,对象存储缓冲区的大小在 PHP 7.

中要小得多

所以基本上 TL;DR 答案是 PHP 7 使用更大的 VM 堆栈页面大小。