Нужен массив как структура в PHP с минимальным использованием памяти


в моем PHP скрипте мне нужно создать массив > 600k целых чисел. К сожалению мои веб-серверы memory_limit имеет значение 32M, поэтому при инициализации массива скрипт прерывается сообщением

фатальная ошибка: разрешенный объем памяти 33554432 байт исчерпан (пытался выделить 71 байт) в / home/www/myaccount/html / mem_test.php on line 8

Я знаю о том, что PHP не хранит значения массива как простые целые числа, но скорее как zvalues, которые намного больше, чем простое целочисленное значение (8 байт на моей 64-битной системе). Я написал небольшой скрипт, чтобы оценить, сколько памяти использует каждая запись массива, и оказывается, что это довольно точно 128 байт. 128!!! Мне нужно >73M просто для хранения массива. К сожалению, сервер не под моим контролем, поэтому я не могу увеличить memory_limit.

мой вопрос, есть ли возможность в PHP создать массив-структура, которая использует меньше память. Мне не нужно, чтобы эта структура была ассоциативной (достаточно простого доступа к индексу). Он также не должен иметь динамического изменения размера - я точно знаю, насколько большой массив будет. Кроме того, все элементы будут одного типа. Прямо как старый добрый C-массив.


Edit: Таким образом, решение 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))));
}
8 65

8 ответов:

самая эффективная память, которую вы получите, вероятно, будет хранить все в строке, упакованной в двоичный файл, и использовать ручное индексирование.

$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.

A PHP Judy Array будет использовать значительно меньше памяти, чем стандартный массив PHP, и SplFixedArray.

я цитирую " массив с 1 миллионом записей, использующих обычную структуру данных массива PHP, занимает 200 МБ. SplFixedArray использует около 90 мегабайт. Джуди использует 8 мегабайт. Компромисс заключается в производительности, Джуди занимает примерно в два раза больше времени обычной реализации массива php."

вы можете использовать объект, если это возможно. Они часто используют меньше памяти, чем массивы. Также SplFixedArray - это хороший вариант.

но это действительно зависит от реализации, что вам нужно сделать. Если вам нужна функция для возврата массива и вы используете PHP 5.5. Вы могли бы использовать выход генератора чтобы передать массив обратно.

вы можете попробовать использовать SplFixedArray, это быстрее и занимает меньше памяти (комментарий doc говорят ~30% меньше). Тест здесь и здесь.

используйте строку

600K-это много элементов. Если вы открыты для альтернативных методов, я лично буду использовать базу данных для этого. Затем используйте стандартный синтаксис sql/nosql select, чтобы вытащить вещи. Возможно, memcache или redis, если у вас есть простой хост для этого, например garantiadata.com может быть, БТР.

в зависимости от того, как вы генерируете целые числа, вы можете потенциально использовать генераторы PHP, предполагая, что вы пересекаете массив и делаете что-то с отдельными значениями.

Я взял ответ @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");
}