PHP和百万数组婴儿


PHP and the million array baby

假设您有以下整数数组:

array(1, 2, 1, 0, 0, 1, 2, 4, 3, 2, [...] );

整数最多有一百万个条目;只是它们不是硬编码的,而是预先生成并存储在JSON格式的文件中(大小约为2MB)。这些整数的顺序很重要,我不能每次都随机生成它,因为它应该是一致的,并且在相同的索引上总是有相同的值。

如果这个文件后来在PHP中读回来(例如使用file_get_contents+json_decode),只需700到900ms就可以取回数组——"好吧"我想,"这可能是合理的,因为json_decode必须解析大约200万个字符,让我们缓存它"。APC将其缓存在一个大约需要68MB的条目中,这可能是正常的,zval很大然而,从APC取回这个阵列也需要一些不错的600毫秒,在我看来这仍然太多了。

编辑:APC会序列化/取消序列化来存储和检索内容,而使用百万项数组是一个漫长而繁重的过程

所以问题是:

  • 如果我打算在PHP中加载一个一百万条目的数组,无论是数据存储还是方法,我应该预料到这种延迟吗?据我所知,APC存储zval本身,因此理论上从APC检索它应该尽可能快(没有解析,没有转换,没有磁盘访问)

  • 为什么APC对看似简单的事情如此缓慢

  • 有没有什么有效的方法可以使用PHP在内存中完全加载一百万个条目的数组?假设RAM的使用不是问题。

  • 如果我只访问基于索引的这个数组的切片(例如,将块从索引15加载到索引76),而从未将整个数组实际存储在内存中(是的,我知道这是一种合理的方法,但我想了解所有方面),那么对于整个数组来说,什么是最有效的数据存储系统?显然不是RDBM;我在考虑redis,但我很乐意听到其他想法。

假设整数都是0-15。然后你可以存储每个字节2个:

<?php
$data = '';
for ($i = 0; $i < 500000; ++$i)
  $data .= chr(mt_rand(0, 255));
echo serialize($data);

运行:php ints.php > ints.ser

现在您有一个文件,其中包含500000字节的字符串,其中包含1000000个从0到15的随机整数。

加载:

<?php
$data = unserialize(file_get_contents('ints.ser'));
function get_data_at($data, $i)
{
  $data = ord($data[$i >> 1]);
  return ($i & 1) ? $data & 0xf : $data >> 4;
}
for ($i = 0; $i < 1000; ++$i)
  echo get_data_at($data, $i), "'n";

我的机器装载时间大约是0.002秒。

当然,这可能不直接适用于您的情况,但它将比由一百万个条目组成的臃肿的PHP数组快得多。坦率地说,在PHP中拥有这么大的数组从来都不是合适的解决方案。

我也不是说这是一个合适的解决方案,但如果它符合你的参数,它肯定是可行的。

请注意,如果您的数组中有0-255范围内的整数,则可以取消打包,只将数据访问为ord($data[$i])。在这种情况下,您的字符串将是1M字节长。

最后,根据file_get_contents()的文档,php将对该文件进行内存映射。如果是这样,您的最佳性能将是将原始字节转储到文件中,并像一样使用它

$ints = file_get_contents('ints.raw');
echo ord($ints[25]);

这假设ints.raw正好是一百万字节长。

APC存储序列化的数据,因此在从APC加载回时必须取消序列化。这就是你的开销所在。

加载它最有效的方法是以PHP和include()的形式写入文件,但对于包含一百万个元素的数组,您永远不会有任何级别的效率。。。它需要大量的内存,加载也需要时间。这就是数据库被发明的原因,那么你对数据库有什么问题呢?

编辑

如果您想加快序列化/反序列化的速度,请查看igbinary扩展

我不能每次都随机生成它,因为它应该是一致的,并且在相同的索引上总是有相同的值。

你读过伪随机数吗?有一种叫做种子的小东西可以解决这个问题。

还要对您的期权和索赔进行基准测试。您是否对file_get_contents与json_decode进行了计时?这里需要在存储成本和访问成本之间进行权衡。例如,如果你的数字是0..9(或0..255),那么将它们存储在2Mb字符串中并使用访问函数可能会更容易。2Mb将更快地加载,无论是从FS还是APC。

正如Mark所说,这就是创建数据库的原因——允许您根据常规使用模式有效地搜索(和操作,但您可能不需要)数据。它也可能比使用数组实现自己的搜索更快。我猜我们谈论的是每次访问数组时,大约有2-300MB的数据(在序列化之前)被序列化和未序列化。

如果您想加快速度,请尝试分别分配数组的每个元素——您可能会用函数调用开销来换取序列化所花费的时间。您也可以使用自己的扩展进行扩展,将数据集封装在一个小型检索接口中。

我猜你不能直接存储zval的原因是因为它们包含内部状态,而且你不能直接将变量符号表指向上一个表。