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

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

  

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

Мне известен тот факт, что PHP не хранит значения массива в виде простых целых чисел, а скорее в виде значений z, которые намного больше, чем обычное целочисленное значение (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))));
}
65 голосов | спросил Alexander Tobias Bockstaller 24 Jpm1000000pmFri, 24 Jan 2014 17:05:41 +040014 2014, 17:05:41

8 ответов


0

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

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

ответил deceze 24 Jpm1000000pmFri, 24 Jan 2014 17:15:19 +040014 2014, 17:15:19
0

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

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

ответил Ryan 24 Jpm1000000pmFri, 24 Jan 2014 17:13:50 +040014 2014, 17:13:50
0

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

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

ответил RJD22 24 Jpm1000000pmFri, 24 Jan 2014 17:12:40 +040014 2014, 17:12:40
0

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

ответил Dysosmus 24 Jpm1000000pmFri, 24 Jan 2014 17:12:14 +040014 2014, 17:12:14
0

Используйте строку - это то, что я бы сделал. Сохраняйте его в строке с фиксированными смещениями (я полагаю, это должны делать 16 или 20 цифр) и используйте substr для получения необходимого значения. Сверкающая быстрая запись /чтение, супер простота и 600 000 целых чисел займет всего ~ 12 млн. Для хранения.

base_convert () - если вам нужно что-то более компактное, но с минимальными усилиями, конвертируйте ваши целые числа в base-36 вместо base-10; в этом случае 14-значный номер будет храниться в 9 буквенно-цифровых символов. Вам нужно будет сделать 2 штуки 64-битных целых, но я уверен, что это не проблема. (Я бы разделил их на 9-значные куски, где преобразование дает вам 6-символьную версию.)

pack () /unpack () - двоичная упаковка - это то же самое, но с большей эффективностью. Используйте его, если ничего не работает; разделите ваши числа, чтобы они соответствовали двум 32-битным фрагментам.

ответил dkellner 24 Jpm1000000pmFri, 24 Jan 2014 17:21:17 +040014 2014, 17:21:17
0

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

ответил Gavin 25 Jam1000000amSat, 25 Jan 2014 09:53:36 +040014 2014, 09:53:36
0

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

ответил Oscar M. 24 Jpm1000000pmFri, 24 Jan 2014 22:31:49 +040014 2014, 22:31:49
0

Я взял ответ @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");
}
ответил humbads 6 FebruaryEurope/MoscowbMon, 06 Feb 2017 17:00:20 +0300000000pmMon, 06 Feb 2017 17:00:20 +030017 2017, 17:00:20

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132