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, но я был бы рад услышать другие идеи.
Ответы
Ответ 1
Скажем, что целые числа равны 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
составляет ровно миллион байтов.
Ответ 2
APC хранит данные, сериализованные, поэтому он должен быть неэтериализован, поскольку он загружается из APC. Это где ваши накладные расходы.
Наиболее эффективным способом загрузки является запись в файл как PHP и include(), но вы никогда не будете иметь какой-либо уровень эффективности с массивом, содержащим миллион элементов... он принимает огромное количество памяти, и для загрузки требуется время. Вот почему были изобретены базы данных, поэтому в чем проблема с базой данных?
ИЗМЕНИТЬ
Если вы хотите ускорить сериализацию/десериализацию, посмотрите igbinary
Ответ 3
Я не могу случайным образом генерировать его каждый раз, потому что он должен быть последовательным и всегда иметь одинаковые значения в тех же индексах.
Вы когда-нибудь читали о псевдослучайных числах? Там эта маленькая вещь называется семенем, которая решает эту проблему.
Также сравнивайте свои варианты и формулы. Вы приурочили файл_get_contents к json_decode? Между запасами и ресурсами доступа существует компромисс. Например. если ваши номера равны 0..9 (или 0..255), тогда их может быть проще хранить в строке 2 Мб и использовать для этого функцию доступа. 2Mb будет загружаться быстрее, будь то из FS или APC.
Ответ 4
Как отметил Марк, именно поэтому были созданы базы данных - чтобы вы могли эффективно выполнять поиск (и манипулировать, но может быть, не нуждаться) в этих данных, основываясь на ваших обычных шаблонах использования. Это также может быть быстрее, чем реализация собственного поиска с использованием массива. Я предполагаю, что мы говорим о где-то близком к 2-300 МБ данных (до сериализации), которые сериализуются и несериализуются каждый раз, когда вы обращаетесь к массиву.
Если вы хотите ускорить его, попробуйте назначить каждый элемент массива отдельно - вы можете торговать накладными вызовами функций за время, затрачиваемое на сериализацию. Вы также можете расширить его с помощью собственного расширения, обернув свой набор данных в небольшом интерфейсе поиска.
Я предполагаю, почему вы не можете напрямую хранить zvals, потому что они содержат внутреннее состояние, и вы просто не можете просто указать таблицу символов переменных в предыдущую таблицу.