常规 PHP 数组和 SplFixedArray 之间的性能比较
我们在上一节中遇到的一个关键问题是:为什么要使用 SplFixedArray
而不是 PHP 数组?现在我们就来探索答案。我们遇到的概念是,PHP 数组实际上不是数组,而是哈希映射。让我们运行一个 PHP 5.x 版本的小示例代码,看看 PHP 数组的内存使用情况。
让我们创建一个包含 100,000 个唯一 PHP 整数的数组。由于我运行的是 64 位机器,我预计每个整数占用 8 个字节。因此,数组将消耗大约 800,000 字节内存。代码如下:
$startMemory = memory_get_usage();
$array = range(1,100000);
$endMemory = memory_get_usage();
echo ($endMemory - $startMemory)." bytes";
如果我们在命令提示符下运行这段代码,就会看到 14,649,040 字节的输出结果。是的,没错。内存使用量几乎是我们计划的 18.5 倍。这意味着,对于一个 PHP 数组来说,数组中每个元素的开销为 144 字节(18 * 8 字节)。现在,这额外的 144 字节从何而来,为什么 PHP 会为每个数组元素使用如此多的额外内存?下面解释一下 PHP 数组使用的额外字节:

本图显示了 PHP 数组的内部工作原理。它将数据存储在一个桶中,以避免碰撞并容纳更多数据。为了管理这种动态特性,数组内部实现了双链表和哈希表。最终,数组中的每个元素都会耗费大量额外的内存空间。下面是基于 PHP 数组实现代码(C 代码)的每个元素的内存消耗明细:
32 bit | 64 bit | |
---|---|---|
zval |
16 bytes |
24 bytes |
+ cyclic GC info |
4 bytes |
8 bytes |
+ allocation header |
8 bytes |
16 bytes |
zval (value) total |
28 bytes |
48 bytes |
bucket |
36 bytes |
72 bytes |
+ allocation header |
8 bytes |
16 bytes |
+ pointer |
4 bytes |
8 bytes |
bucket (array element) total |
48 bytes |
96 bytes |
Grand total (bucket+zval) |
76 bytes |
144 bytes |
为了理解 PHP 数组的内部结构,我们需要深入研究 PHP 的内部结构。这超出了本书的范围。推荐阅读: https://nikic.github.io/2011/12/12/How-big-are-PHP-arrays-really-Hint-BIG.html |
在新的 PHP 7 版本中,PHP 数组及其内部构造方式有了很大改进。因此,每个元素的开销从 144 字节减少到了 36 字节。这是一个很大的改进,而且适用于 32 位和 64 位操作系统。下面是一个数组中 100,000 个项目的对比图:
$array = Range(1,100000) | 32 bit | 64 bit |
---|---|---|
PHP 5.6 or below |
7.4 MB |
14 MB |
PHP 7 |
3 MB |
4 MB |
因此,换句话说,在阵列存储方面,PHP 7 对 32 位系统的改进系数为 2.5 倍,对 64 位系统的改进系数为 3.5 倍。这确实是一个很好的改进。但这都是关于 PHP 数组的,那么 SplFixedArray
呢?让我们在 PHP 7 和 PHP 5.x 中使用 SplFixArray
运行相同的示例:
$items = 100000;
$startMemory = memory_get_usage();
$array = new SplFixedArray($items);
for ($i = 0; $i < $items; $i++) {
$array[$i] = $i;
}
$endMemory = memory_get_usage();
$memoryConsumed = ($endMemory - $startMemory) / (1024*1024);
$memoryConsumed = ceil($memoryConsumed);
echo "memory = {$memoryConsumed} MB\n";
我们在这里编写了 SplFixedArray
的内存消耗功能。如果我们把 $array = new SplFixedArray($items);
这一行改成 $array = [];
,就可以运行与 PHP 数组相同的代码了。
由于操作系统、内存大小、调试器开/关等因素不同,基准测试结果可能因机器而异。建议在自己的机器上运行代码,生成类似的基准进行比较。 |
下面是 PHP 数组和 SplFixedArray
在 64 位系统中对一个包含 100,000 个整数的数组的内存消耗比较:
100,000 items | Using PHP array | SplFixedArray |
---|---|---|
PHP 5.6 or below |
14 MB |
6 MB |
PHP 7 |
5 MB |
2 MB |
不仅在内存使用方面,SplFixedArray
的执行速度也比一般的 PHP 数组操作(如访问值、赋值等)更快。
虽然我们像使用数组一样使用带有 |
使用 SplFixedArray 的更多示例
由于 SplFixedArray
具有良好的性能提升指标,我们可以在大多数数据结构和算法中使用它来代替普通的 PHP 数组。现在,我们将探讨在不同场景中使用 SplFixedArray
的更多示例。
转换 PHP 数组为 SplFixedArray
我们已经看到了如何创建具有固定长度的 SplFixedArray
。如果我想在运行时创建一个 SplFixedArray
数组,该怎么办?下面的代码块展示了如何实现:
$array =[1 => 10, 2 => 100, 3 => 1000, 4 => 10000];
$splArray = SplFixedArray::fromArray($array);
print_r($splArray);
在这里,我们使用 SplFixedArray
类的静态方法 fromArray
从已有数组 $array
中构造一个 SplFixedArray
。然后使用 PHP print_r
函数打印数组。输出结果如下:
SplFixedArray Object
(
[0] =>
[1] => 10
[2] => 100
[3] => 1000
[4] => 10000
)
我们可以看到,数组现在已经转换为 SplFixedArray
,并且它保留了与实际数组中完全相同的索引号。由于实际数组中没有定义 0 索引,因此这里的 0 索引被保留为空。但是,如果我们想忽略先前数组中的索引,并为它们分配新的索引,那么就必须将先前代码的第二行改为这样:
$splArray = SplFixedArray::fromArray($array,false);
现在,如果我们再次打印数组,输出结果如下:
SplFixedArray Object
(
[0] => 10
[1] => 100
[2] => 1000
[3] => 10000
)
如果我们想在运行时将一个数组转换为固定数组,最好是在以后不使用时取消设置常规 PHP 数组。如果是一个大数组,这样做可以节省内存。 |
将 SplFixedArray 转换为 PHP 数组
我们可能还需要将 SplFixedArray
转换为普通的 PHP 数组,以便应用 PHP 中的一些预定义数组函数。和前面的例子一样,这也是非常简单的事情:
$items = 5;
$array = new SplFixedArray($items);
for ($i = 0; $i < $items; $i++) {
$array[$i] = $i * 10;
}
$newArray = $array->toArray();
print_r($newArray);
这将产生以下输出:
Array
(
[0] => 0
[1] => 10
[2] => 20
[3] => 30
[4] => 40
)