PHP и миллион детей

Представьте, что у вас есть следующий массив целых чисел:

array(1, 2, 1, 0, 0, 1, 2, 4, 3, 2, [...] ); 

Целые числа идут до одного миллиона записей; только вместо жесткого кодирования они были предварительно сгенерированы и сохранены в форматированном файле JSON (размером приблизительно 2 МБ). Порядок этих целых чисел имеет значение, я не могу случайным образом генерировать его каждый раз, потому что он должен быть последовательным и всегда иметь одни и те же значения в одних и тех же индексах.

Если этот файл снова читается на PHP (например, с помощью file_get_contents + json_decode ), он принимает от 700 до 900 мс только для того, чтобы вернуть массив – «Хорошо», – подумал я, «вероятно, разумно, поскольку json_decode должен анализировать около 2 миллионов символов, давайте кешировать его ". APC кэширует его в записи, которая занимает около 68 МБ, возможно, нормальная, zvals большие. Извлечение, однако, этот массив из APC также занимает несколько хороших 600 мс, что слишком сильно в моих глазах.

Изменить: APC выполняет сериализацию / unserialize для хранения и извлечения контента, который с миллионом массива элементов является длительным и тяжелым процессом.

Итак, вопросы:

  • Должен ли я ожидать эту задержку, если я намереваюсь загрузить миллионный массив записей, независимо от хранилища данных или метода, в PHP? Насколько я понимаю, APC хранит сам zval, поэтому теоретически получение его из APC должно быть таким быстрым, каким оно может быть возможно (без синтаксического анализа, без преобразования и без доступа к диску)

  • Почему APC так медленно для чего-то, казалось бы, просто?

  • Есть ли эффективный способ загрузить миллион записей массива полностью в память с помощью PHP? предполагая, что использование ОЗУ не является проблемой.

  • Если бы мне приходилось получать доступ только к срезам этого массива на основе индексов (например, загружать кусок из индекса 15 в индекс 76) и никогда не иметь весь массив в памяти (да, я понимаю, что это разумный способ сделать это, но я хотел знать все стороны), что было бы самой эффективной системой хранения данных для полного массива? Очевидно, что не RDBM; Я думаю redis, но я был бы рад услышать другие идеи.

Скажем, целые числа – 0-15. Затем вы можете сохранить 2 байта:

 <?php $data = ''; for ($i = 0; $i < 500000; ++$i) $data .= chr(mt_rand(0, 255)); echo serialize($data); 

Для запуска: php ints.php > ints.ser

Теперь у вас есть файл с 500000 байтовой строкой, содержащей 1 000 000 случайных чисел от 0 до 15.

Загружать:

 <?php $data = unserialize(file_get_contents('ints.ser')); function get_data_at($data, $i) { $data = ord($data[$i >> 1]); return ($i & 1) ? $data & 0xf : $data >> 4; } for ($i = 0; $i < 1000; ++$i) echo get_data_at($data, $i), "\n"; 

Время загрузки на моей машине составляет около 0,002 секунды.

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

Я не говорю, что это тоже подходящее решение, но оно, безусловно, работоспособно, если оно соответствует вашим параметрам.

Обратите внимание, что если ваш массив имел целые числа в диапазоне 0-255, вы могли бы избавиться от упаковки и просто получить доступ к данным как ord($data[$i]) . В этом случае ваша строка будет длиной 1 Мбайт.

Наконец, согласно документации file_get_contents() , php будет хранить карту памяти. Если это так, ваша лучшая производительность будет заключаться в том, чтобы сбрасывать необработанные байты в файл и использовать его так:

 $ints = file_get_contents('ints.raw'); echo ord($ints[25]); 

Это предполагает, что ints.raw составляет ровно один миллион байт.

APC хранит данные, сериализованные, поэтому он должен быть неэтериализован, поскольку он загружается из APC. Вот где ваши накладные расходы.

Наиболее эффективным способом загрузки является запись в файл как PHP и include (), но вы никогда не будете иметь какой-либо уровень эффективности с массивом, содержащим миллион элементов … он требует огромного объема памяти и требуется время для загрузки. Вот почему были изобретены базы данных, так что в чем проблема с базой данных?

РЕДАКТИРОВАТЬ

Если вы хотите ускорить сериализацию / десериализацию, ознакомьтесь с расширением igbinary

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

Вы когда-нибудь читали о псевдослучайных числах? Там эта маленькая вещь называется семенем, которая решает эту проблему.

Также сравнивайте свои варианты и претензии. Вы приурочили файл_get_contents к json_decode? Между запасами и ресурсами доступа существует компромисс. Например. если ваши номера равны 0..9 (или 0..255), тогда их может быть проще хранить в строке 2 Мб и использовать для этого функцию доступа. 2Mb будет загружаться быстрее, будь то из FS или APC.

Как отметил Марк, именно поэтому были созданы базы данных – чтобы вы могли эффективно выполнять поиск (и манипулировать, но может быть, не нуждаться) в данных, основанных на ваших обычных шаблонах использования. Это также может быть быстрее, чем реализация собственного поиска с использованием массива. Я предполагаю, что мы говорим о где-то близком к 2-300 МБ данных (до сериализации), которые сериализуются и неэтериализуются каждый раз, когда вы обращаетесь к массиву.

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

Я предполагаю, что вы не можете напрямую хранить zvals, потому что они содержат внутреннее состояние, и вы просто не можете просто указать таблицу символов переменных в предыдущую таблицу.