Нужен массив как структура в 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 ответов:
самая эффективная память, которую вы получите, вероятно, будет хранить все в строке, упакованной в двоичный файл, и использовать ручное индексирование.
$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"); }