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

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

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

Мне известно о том, что PHP не сохраняет значения массива как простые целые числа, а скорее как zvalues, которые намного больше, чем простое целочисленное значение (8 байтов на моей 64-битной системе). Я написал небольшой script, чтобы оценить, сколько памяти использует каждая запись массива, и получается, что это довольно точно 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))));
}

Ответ 1

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

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

Ответ 2

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

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

Ответ 3

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

Ответ 4

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

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

Ответ 5

Я бы сохранил его в строке с фиксированными смещениями и использовал substr для получения нужного. Быстрая запись/быстрое чтение, меньшая память, возможно, немного неэлегантная, но отлично работает. До тех пор, пока вы можете позволить себе intval (...) читать, конечно.

Ответ 6

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

Ответ 7

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

Ответ 8

Я взял ответ @deceze и завернул его в класс, который может обрабатывать 32-битные целые числа. Это только append-only, но вы все равно можете использовать его как простой, оптимизированный для памяти массив PHP, Queue или Heap. 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");
}