在我的PHP脚本中,我需要创建一个> 600k整数的数组。不幸的是,我的网络服务器memory_limit
设置为32M,因此在初始化数组时脚本将以消息中止
致命错误: /home/www/myaccount/html/mem_test.php 在线<上允许的内存大小为33554432字节(尝试分配71个字节) 8
我知道PHP不会将数组值存储为普通整数,而是存储为比普通整数值(我的64位系统上的8个字节)大得多的z值。我写了一个小脚本来估计每个数组条目使用多少内存,事实证明,它恰好是128字节。 128!我需要&gt; 73M才能存储数组。不幸的是,网络服务器不受我的控制,所以我无法增加memory_limit
。
我的问题是,PHP是否有可能创建一个使用更少内存的类似数组的结构。我不需要这种结构是关联的(普通的索引访问就足够了)。它也不需要动态调整大小 - 我确切知道数组的大小。此外,所有元素都是相同的类型。就像一个很好的旧C阵列。
修改 所以deceze的解决方案与32位整数一起开箱即用。但即使您使用的是64位系统,pack()似乎也不支持64位整数。为了在我的数组中使用64位整数,我应用了一些位操作。也许下面的片段对某人有帮助:
function push_back(&$storage, $value)
{
// split the 64-bit value into two 32-bit chunks, then pass these to pack().
$storage .= pack('ll', ($value>>32), $value);
}
function get(&$storage, $idx)
{
// read two 32-bit chunks from $storage and glue them back together.
return (current(unpack('l', substr($storage, $idx * 8, 4)))<<32 |
current(unpack('l', substr($storage, $idx * 8+4, 4))));
}
答案 0 :(得分:59)
您获得的内存效率最高的可能是将所有内容存储在字符串中,以二进制方式打包,并使用手动索引。
$storage = '';
$storage .= pack('l', 42);
// ...
// get 10th entry
$int = current(unpack('l', substr($storage, 9 * 4, 4)));
如果“阵列”初始化可以一举完成并且您只是从结构中读取,那么这是可行的。如果你需要大量附加到字符串,这将变得非常低效。即便如此,也可以使用资源句柄来完成:
$storage = fopen('php://memory', 'r+');
fwrite($storage, pack('l', 42));
...
这非常有效。然后,您可以将此缓冲区读回变量并将其用作字符串,或者您可以继续使用资源fseek
。
答案 1 :(得分:28)
PHP Judy Array将使用比标准PHP数组少得多的内存,以及SplFixedArray。
我引用“使用常规PHP数组数据结构的100万个条目的数组需要200MB.SplFixedArray使用大约90兆字节.Judy使用8兆。权衡性能,Judy花费大约两倍于常规php数组实现的时间。”
答案 2 :(得分:11)
您可以尝试使用SplFixedArray,速度更快,内存更少(文档评论说减少约30%)。测试here和here。
答案 3 :(得分:11)
如果可能,您可以使用对象。这些通常比数组使用更少的内存。 SplFixedArray也是一个不错的选择。
但这实际上取决于您需要执行的实现。如果你需要一个函数来返回一个数组并使用PHP 5.5。您可以使用generator yield来回流数组。
答案 4 :(得分:5)
使用字符串 - 这就是我要做的。将它存储在固定偏移量的字符串中(我猜是16或20位应该这样做吗?)并使用substr来获得所需的偏移量。超快速写入/读取,超级简单,600,000个整数只需要大约12M即可存储。
base_convert() - 如果您需要更紧凑但更省力的东西,请将整数转换为base-36而不是base-10;在这种情况下,一个14位数字将存储在9个字母数字字符中。你需要制作2个64位的整数,但我确信这不是问题。 (我将它们拆分为9位数块,转换为你提供6-char版本。)
pack()/ unpack() - 二进制打包是一回事,效率更高一些。如果没有其他工作,请使用它;拆分你的数字,使它们适合两个32位的部分。
答案 5 :(得分:4)
600K是很多元素。如果您对替代方法持开放态度,我个人会使用数据库。然后使用标准的sql / nosql选择语法来解决问题。如果您有一个简单的主机,可能是memcache或redis,例如garantiadata.com。也许是APC。
答案 6 :(得分:1)
根据生成整数的方式,您可以使用PHP's generators,假设您正在遍历数组并使用单个值执行某些操作。
答案 7 :(得分:0)
我接受了@deceze的回答并将其包装在一个可以处理32位整数的类中。它只是附加的,但你仍然可以将它用作一个简单的,内存优化的PHP数组,队列或堆。 AppendItem和ItemAt都是O(1),它没有内存开销。我添加了currentPosition / currentSize以避免不必要的fseek函数调用。如果您需要限制内存使用量并自动切换到临时文件,请改用php://temp。
class MemoryOptimizedArray
{
private $_storage;
private $_currentPosition;
private $_currentSize;
const BYTES_PER_ENTRY = 4;
function __construct()
{
$this->_storage = fopen('php://memory', 'rw+');
$this->_currentPosition = 0;
$this->_currentSize = 0;
}
function __destruct()
{
fclose($this->_storage);
}
function AppendItem($value)
{
if($this->_currentPosition != $this->_currentSize)
{
fseek($this->_storage, SEEK_END);
}
fwrite($this->_storage, pack('l', $value));
$this->_currentSize += self::BYTES_PER_ENTRY;
$this->_currentPosition = $this->_currentSize;
}
function ItemAt($index)
{
$itemPosition = $index * self::BYTES_PER_ENTRY;
if($this->_currentPosition != $itemPosition)
{
fseek($this->_storage, $itemPosition);
}
$binaryData = fread($this->_storage, self::BYTES_PER_ENTRY);
$this->_currentPosition = $itemPosition + self::BYTES_PER_ENTRY;
$unpackedElements = unpack('l', $binaryData);
return $unpackedElements[1];
}
}
$arr = new MemoryOptimizedArray();
for($i = 0; $i < 3; $i++)
{
$v = rand(-2000000000,2000000000);
$arr->AddToEnd($v);
print("added $v\n");
}
for($i = 0; $i < 3; $i++)
{
print($arr->ItemAt($i)."\n");
}
for($i = 2; $i >=0; $i--)
{
print($arr->ItemAt($i)."\n");
}