php 逐个添加元素与一次添加所有数据时的数组性能

php array performance when adding elements one by one vs when adding all of the data at once

为了简单起见,我将举一个简单的例子,我有一个 $array 和一些我想添加到这个数组中的键和值..主要从性能角度来看更好的是:

  1. 将一条语句中的所有这些键值添加到数组中。或者
  2. 一个一个做也无妨

1

$array = [
    $key1 => $value1, 
    $key2 => $value2
];

$array[$key1] = $value1;
$array[$key2] = $value2;

如果它是一个可疑的瓶颈(也许是数百万项?)我会直接去做一些性能测试。看到这个小脚本,比较一次 array[key = , , ] 的多个键更新和连续语句中的多个单独的键更新 array[key] = 当更新 1.000.000 次时:

$time_start = microtime(true);
$a1 = array();
for ($i = 0; $i < 1000000; ++$i) {
  $a1 = [
    'key1' => $i, 
    'key2' => $i + 1
  ];
}
$time_end = microtime(true);
printf('Took %f seconds for inline array[key = ]<br>', $time_end - $time_start);
 
$time_start = microtime(true);
$a2 = array();
for ($i = 0; $i < 1000000; ++$i) {
  $a2['key1'] = $i;
  $a2['key2'] = $i + 1;
}
$time_end = microtime(true);
printf('Took %f seconds for array[key] = <br>', $time_end - $time_start);

这给了我(每个 运行 上的图片都差不多):

Took 0.195255 seconds for inline array[key = ]
Took 0.204276 seconds for array[key] =

因此,这真的无关紧要 - 您不必担心明显的差异 - 但在一个语句中更新多个键似乎要快一点。大多数时候,不一定总是如此。

这也正是我们所期待的!从逻辑上考虑:在一条语句中更新数组键比通过连续多条语句更新相同的数组键效率稍微高一些,原因很简单,内存中的数组被访问的次数更少。

如果你有一把 keys/values,绝对没有区别。如果您处理具有 100K+ 成员的数组,它确实会产生影响。让我们先构建一些数据:

$r = [];
for($i = 1; $i <= 100000; $i++) {
    $r[] = $i; // for numerically indexed array
    // $r["k_{$i}"] = $i; // for associative array
    // array_push($r, $i); // with function call
}

这将生成一个包含 100000 个成员 one-by-one 的数组。当添加数字(自动)索引时,此循环在我的笔记本电脑上需要 ~0.0025 秒,内存使用量约为 6.8MB。如果我使用 array_push,函数开销需要 ~0.0065 sec。当 $i 添加一个命名键时,需要 ~0.015 秒,内存使用量约为 12.8MB。然后,命名键的定义速度较慢。

但是,如果将 0.015 秒缩短到 0.012 秒,会有什么不同吗? 或者使用 ^10 音量,将 0.15 秒缩短到 0.12 秒,甚至 0.075 秒?并不真地。如果您拥有超过 100 万名成员,它才会真正开始变得引人注目。 您实际使用该数据量将花费更长的时间,并且应该是您优化工作的主要重点。


更新:我准备了三个文件,一个是上面的100K整数一组;另一个单独定义了 100K 个整数;并序列化为 JSON。我加载了它们并记录了时间。事实证明,存在差异,其中定义“in one set”的速度快 50%,而且更多 memory-efficient。此外,如果数据是从 JSON 反序列化的,它比包含“本机数组”3 倍 快。

  • “一组”:0.075 sec9.9MB
  • “单独”:0.150 sec15.8MB
  • “来自 JSON”:0.025 sec9.9MB
  • “来自 MySQL”:0.110 sec13.8MB*

然后:如果您以原生 PHP 格式定义大型数组,请一次性定义它们,而不是 bit-by-bit。如果您从文件加载批量数组数据,json_decode(file_get_contents('data.json'), true) 加载 JSON 明显快于 include 'data.php'; 使用本机 PHP 数组定义。您的里程可能会随着更复杂的数据结构而变化,但我不希望基本性能模式发生变化。供参考:Source data at BitBucket.

一个奇怪的观察:在我们上面的循环中从头开始生成数据实际上比 loading/parsing 从具有 ready-made 数组的文件生成数据快得多!

MySQL:Key-value 对是从 two-column table 和 PDO 中提取到与样本数据匹配的数组中 fetchAll(PDO::FETCH_UNIQUE|PDO::FETCH_COLUMN).


最佳实践:定义数据时,如果它是您需要处理的东西,而不是未读取或手动编辑的“原始 export/import”数据:构造您的数组 使您的代码易于维护 。我个人认为保持简单数组“包含”更“干净”:

$data = [
    'length' => 100,
    'width' => 200,
    'foobar' => 'possibly'
];

有时您的数组需要“引用自身”并且“bit-by-bit”格式是必要的:

$data['length'] = 100;
$data['width'] = 200;
$data['square'] = $data['length'] * $data['width'];

如果您构建多维数组,我发现将每个“根”数据集分开会“更干净”:

$data = [];
$data['shapes'] = ['square', 'triangle', 'octagon'];
$data['sizes'] = [100, 200, 300, 400];
$data['colors'] = ['red', 'green', 'blue'];

最后一点,到目前为止,PHP 数组的更多限制性能因素内存使用(参见: array hashtable internals),这与您构建数组的方式无关。如果您在数组中有大量数据集,请确保您不会保留它们的不必要的修改副本,这些副本超出了它们的相关范围。否则你的内存使用率会飙升。


在 Win10 / PHP 8.1.1 / MariaDB 10.3.11 @ Thinkpad L380 上测试。