我耗尽了内存,在PHP中使用80k x 20的整数值矩阵(数组)。有解决办法吗?
我有一个PHP应用程序,它收集数据并将其存储到数据库中。数据收集在不同的域(>20k)。变量的数量因域而异(基本上是无限的),所以我必须在MySQL数据库中存储逗号分隔的列表(版本5之前)。这执行得相当好。
在某个时间点,用户需要下载数据。下载特性必须执行一些归一化,因此它需要每个变量的中位数(不是平均值!)(实际上是变量子集的中位数)。通常我可以很容易地从数据库中读取数据,用逗号分隔数据,并将中间相关数据存储到数组[var][row]中。然后我可以对数组进行排序并得到中值。
但是,有一个域没有100或1000个数据记录(行),而是80K。给定20个中位数相关变量,这是160万个整数值(32位)或51 MB原始整数数据(可能是两倍,因为我在64位Linux机器上工作)。到目前为止,一切都很好,但是数组结构有一些开销,所以它变得比128 MB大得多。这就是我的PHP耗尽内存的地方。
我不想做的
当然,我可以增加每个PHP脚本的内存限制。由于种种原因,我希望避免这样做。
也有一些算法不需要存储n个值来计算中位数,但会很满意n/2 (+x),但将内存负载减少到50%+ x可能不足以解决问题。
我也可以计算每个变量的中位数。但这需要我从数据库加载80K行数据20次,并一次又一次地执行explosion()。这将极大地增加脚本运行时间。
[EDIT]数据库目前没有规范化(每一行数据使用CSV数据)。出于性能考虑,这是有意为之,也是必要的。因此,我不喜欢规范化数据库,因为这将导致一个表有100万个条目和一个巨大的索引。
我想做什么
我们说的是不超过51 MB的原始32位整数值。有没有办法把开销减少到几个百分点?甚至在64位机器上?
我知道自PHP 5.0.0以来可用的SPL扩展,但我还没有找到如何使用此扩展节省内存的解决方案。谁能给我一个提示-通过SPL或使用其他解决方案(理想情况下在PHP中默认可用)?
样例代码private function retrieveReferences() {
$query = $this->getResultsQuery(true);
$times = array();
$tp = -1; // Length of $times - 1
while ($row = $query->fetchArray()) {
$timeSrc = explode(',', $row['times']);
// Store the times per page
foreach ($timeSrc as $p=>$s) {
// Should be faster than checking isset $times[$p] all the time
while ($p > $tp) {
$times[] = array();
$tp = count($times) - 1;
}
$times[$p][] = (int)$s;
}
}
// Compute median for each $times[$p]
// <snip>
}
-
一个数组至少消耗104字节,所以数组越少,内存使用越少。
切换到一维数组将节省一些内存。只需使用类似
$times[$item*$n_items + $sub_item]
而不是$times[$item][$sub_item]
的索引来访问元素。 -
一旦你这样做了,很容易切换到
SplFixedArrays
,这将几乎消除标准数组的内存开销(一个标准数组消耗高达90字节每个元素)。 -
您甚至可以通过将所有整数打包在字符串
中来消除zval开销。
在http://we-love-php.blogspot.de/2012/06/php-memory-consumption-with-arrays.html上找到的解决方案实际上非常有用。我认为这是值得的,张贴在这里:
PHP中最有效的数组类型是字符串。
实际上在一个字符串中存储大量的整数值(每个整数4个字节或字符)可以节省80%以上的内存。当然也有很多缺点:你需要对整数进行编码/解码,排序需要自定义函数等等。因此,这个解决方案只有在内存非常重要的情况下才有意义。
在我的例子中,16位整数就足够了,所以我可以用2个字符编码每个整数。下面是我使用的示例代码:
private function retrieveReferences() {
$query = $this->getResultsQuery(true);
$timesSE = array();
$tp = -1; // Length of $times - 1
while ($row = $query->fetchArray()) {
$timeSrc = explode(',', $row['times']);
// Store the times per page
foreach ($timeSrc as $p=>$s) {
// Should be faster than checking isset $times[$p] all the time
while ($p > $tp) {
$timesSE[] = '';
$tp = count($timesSE) - 1;
}
// Save to limit to 16bit (in this specific case)
$i = (int)$s;
if ($i > 0xFFFF) {
$i = 0xFFFF;
}
$timesSE[$p].= chr(($i & 0xFF00) >> 8).chr($i & 0x00FF);
}
}
// Compute median for each $times[$p]
// <snip>
}
您可能会遇到内存限制,因为数据将至少为6.5M
需要查看你的php.ini文件。原来默认为8M
看到http://www.php.net/manual/en/ini.core.php ini.memory-limit