1

我在 PHP 中使用了一个 80k x 20 的整数值矩阵(数组),内存不足。有解决办法吗?

背景

我有一个收集数据并将其存储到数据库的 PHP 应用程序。数据收集在不同的域 (>20k)。变量的数量在整个域中有所不同(主要是无限的),所以我必须在我的 MySQL 数据库中存储逗号分隔的列表(在版本 5 之前)。这表现相当不错。

在某个时间点,用户需要下载数据。下载功能必须执行一些标准化,因此它需要每个变量的中位数(不是平均值!)(实际上是变量子集的中位数)。通常我可以轻松地从数据库中读取数据,explode() 逗号分隔的数据并将中位数相关数据存储到数组 [var] [row] 中。比我可以排序()数组,我得到了中位数。

但是,有一个域没有 100 或 1000 条数据记录(行),而是 80K。给定 20 个中值相关变量,这是 160 万个整数值(32 位)或 51 MB 原始整数数据(可能是两倍,因为我正在使用 64 位 Linux 机器)。到目前为止,一切都很好——但是数组结构有一些开销,所以它变得比 128 MB 大得多。这是我的 PHP 内存不足的地方。

我不想做的事

当然,我可以增加每个 PHP 脚本的内存限制。由于各种原因,我想避免这种情况。

还有一些算法不需要存储 n 值来计算中位数,但会对 n/2 (+x) 感到满意,但将内存负载降低到 50%+X 可能不足以解决问题。

我还可以计算每个变量的中值变量。但这需要我从数据库中加载 80K 行数据 20 次,并一次又一次地执行explode()。这将大大增加脚本运行时间。

[编辑] 数据库当前未标准化(对每个数据行使用 CSV 数据)。出于性能原因,这是有意和必要的。因此我不喜欢规范化数据库,因为这会导致一个包含 100M 条目和巨大索引的表。

我想做什么

我们说的是不超过 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>
}
4

3 回答 3

1
  • 一个数组至少消耗104 个字节,因此数组越少,内存使用量就越少。

    切换到一维数组将节省一些内存。只需使用类似计算的索引$times[$item*$n_items + $sub_item]而不是$times[$item][$sub_item].

  • 完成此操作后,很容易切换到SplFixedArrays,这将几乎消除标准数组的内存开销(标准数组每个元素最多消耗90 个字节)。

  • 您甚至可以通过将所有整数打包在一个字符串中来消除 zval 开销

于 2013-06-12T18:36:31.000 回答
1

在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>
}
于 2013-06-12T19:26:35.073 回答
0

您可能会遇到内存限制,因为数据至少为 6.5M

需要看你的 php.ini 文件。原来默认8M

http://www.php.net/manual/en/ini.core.php#ini.memory-limit

于 2013-06-12T19:34:07.447 回答