В моем скрипте PHP мне нужно создать массив из целых чисел> 600k. К сожалению, для моего webservers memory_limit
установлено значение 32M, поэтому при инициализации массива сценарий прерывается сообщением
Неустранимая ошибка: допустимый размер памяти 33554432 байт исчерпан (попытался выделить 71 байт) в /home/www/myaccount/html/mem_test.php в строке 8
Я знаю, что PHP не сохраняет значения массива как простые целые числа, а скорее как zvalues, которые намного больше, чем простое целочисленное значение (8 байтов на моей 64-битной системе). Я написал небольшой скрипт, чтобы оценить, сколько памяти использует каждая запись в массиве, и получается, что это примерно 128 байт. 128 !!! Мне нужно было бы> 73M только для хранения массива. К сожалению, веб-сервер не находится под моим контролем, поэтому я не могу увеличить memory_limit
.
Мой вопрос в том, есть ли в PHP возможность создать массив-подобную структуру, которая использует меньше памяти. Мне не нужна эта структура, чтобы быть ассоциативной (достаточно простого доступа к индексу). Также не требуется динамическое изменение размера – я точно знаю, насколько велика будет массив. Кроме того, все элементы будут одного типа. Также как старый добрый C-массив.
Редактирование: поэтому решение deceze работает готово с 32-битными целыми числами. Но даже если вы используете 64-битную систему, пакет () , похоже, не поддерживает 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)))); }
Наибольшая эффективность памяти, которую вы получите, – это, вероятно, сохранение всего в строке, упакованной в двоичном формате и использование ручного индексации.
$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
.
PHP Judy Array будет использовать значительно меньше памяти, чем стандартный PHP-массив, и SplFixedArray.
Я цитирую: «Массив с 1 миллионом записей с использованием обычной структуры данных массива PHP занимает 200 МБ. SplFixedArray использует около 90 мегабайт. Judy использует 8 мегабайт. Компромисс в производительности, Джуди занимает удвоенное время обычной реализации php-массива».
Вы можете использовать объект, если это возможно. Они часто используют меньше памяти, чем массивы. Также SplFixedArray – хороший вариант.
Но это действительно зависит от того, что вам нужно сделать. Если вам нужна функция для возврата массива и использования PHP 5.5. Вы можете использовать выход генератора для потоковой передачи массива.
Вы можете попробовать использовать SplFixedArray , он быстрее и занимает меньше памяти (комментарий к доктору говорит ~ 30% меньше). Испытайте здесь и здесь .
Я бы сохранил его в строке с фиксированными смещениями и использовал substr для получения необходимого. Быстрая запись / быстрое чтение, меньшая память, возможно, немного неэлегантная, но отлично работает. До тех пор, пока вы можете позволить себе intval (…) читать, конечно.
600K – это много элементов. Если вы открыты к альтернативным методам, я лично буду использовать для этого базу данных. Затем используйте стандартный синтаксис sql / nosql select, чтобы вытащить все. Возможно, memcache или redis, если у вас есть простой хост для этого, например garantiadata.com. Возможно, APC.
В зависимости от того, как вы генерируете целые числа, вы можете потенциально использовать генераторы PHP , предполагая, что вы проходите массив и делаете что-то с отдельными значениями.
Я взял ответ @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"); }