PHP中矩阵的内存高效数据结构


Memory-efficient data structure for matrix in PHP

我耗尽了内存,在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