Pull to refresh

Comments 74

Больная тема. Протестируйте на 5.3.3 — будете удивлены результатом.
На 5.3.3 на i3 получил «Test serialize Serialize set time 1.4620440006256 get time 36.045368909836». То есть где-то так же. Правда, пришлось memory_limit до гига задрать.
В комментариях к статье «Производительность кодирования и декодирования serialize и json» (http://habrahabr.ru/blogs/php/30210/) рекомендуют для кодирования больших объемов данных использовать реализацию на PHP формата Bencode.
Это случаем не то, чем в header'е torrent-файлов информацию пакуют?
Прошу прощения за невнимательность, с недосыпа не заметил ссылку.
Да, это именно этот формат.
Для больших массивов попробуйте использовать var_dump($var, true); и пишите в файл. По идее в данном случае файловая операция будет быстрее чем 30 секунд.
Это serialize. А unserialize в таком случае как сделать?
Интерестно а что будет если подменить файлик c массивом ( или запись в базе ) на произвольный PHP-код?
Если есть доступ к сессии на запись, то и заменять не нужно ничего, по сути у вас и так полный доступ.
А вы время засекали? Сериализация ведь занимает мало времени, основная проблема десериализация.
Я — нет. Вариант предложил kvf77.
Собственно:

ini_set('memory_limit', '512M');
$file = '/tmp/1';
$data = range(1,2000000);

echo «Test serialize\n»;
$time0 = microtime(1);
file_put_contents($file, var_export($data, true));
unset($data);
$time1 = microtime(1);
eval( '$data='.file_get_contents($file).';');
$time2 = microtime(1);

$timeset = $time1-$time0;
$timeget = $time2-$time1;

echo «Serialize set time $timeset get time $timeget\n»;

Результат:
Serialize set time 3.1096308231354 get time 2.9017460346222
Возможно дело в машине, но у меня json_* показали такие результаты: set time 0.533811092377 get time 0.83913397789
php 5.3.3, core 2 duo, ubuntu
еще можно eval( '$data='.file_get_contents($file).';'); заменить на include $file;
для инклуда тогда нужно в начало файла писать "
эх, какая-то сволочь опять в карму накакала и теперь даже код вставить не могу :(
В общем, eval универсальнее и позволяет читать не только из файла, но и например из БД
Можно сделать лучше:
file_put_contents($file, '<?php return'.var_export($data,1).'; ?>');
$data = include $file;
Можно при экспорте запихнуть в теги <?php ?> и потом просто делать include или require, так по крайней мере закешируется опкод. Это если писать в файл. Если в сессию, то такое не пойдет :(
Извините, тормознул.
Интересно другое. Зачем сериализовать массивы, содержащие 2млн элементов?
Например, есть исследовательский скрипт, который производит анализ определённых данных. Для его работы первоначально нужно составить справочник на 2млн элементов. Справочник составляется долго. Хочется ускорить скрипт. Чтобы не составлять справочник лишний раз, если исходные данные не изменились, можно этот справочник закешировать.
почему бы не использовать любое key:value хранилище?
Да, я понимаю, что можно сделать быстрее, лучше и прочнее. Дело в том, что цели — исследовательские (причем исследование не возможностей PHP, а исследование данных), потому очень часто приходится менять и формат хранения и способы использования.

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

Вот чтобы не отвлекаться на изменение формата хранения я и использую обычные массивы. Исходная проблема с кешированием этого словарика уже решена — сейчас используется json.

Осталась проблема с квадратичным временем выполнения unserialize. Об этом и написана статья.
Редис хорошо и быстро работает с операциями над множествами, попробуйте попробовать =)
А вы не пробовали эксперементировать с ленивой загрузкой или подобными методами?
Небольшой оффтоп: Я что-то не понимаю, но почему здесь нельзя использовать БД?
Попробуйте Data::Dumper и eval.
Или Storable.
хоть и ужасно несекурно, но если нужен результат «на скорую руку»:
function cache_store($data, $key) {
file_put_contents(«cache/».$key.".php", "
Сомнительный метод, как мне кажется. Делать include() для каждого ключа неразумно.
Опомнитесь, key это имя кешируемого элемента
Точно, упустил из виду, что там делается var_export($data, true) для каждого ключа.
Изначально подумал, что это была попытка сделать key=value на файлах.
в обычной жизни евал (или инклюд, как в примере) гораздо тормознее сериалайза
… и значительно шустрее unserialize'а. проверено на собственной шкуре. даже массивчика в 2К элементов достаточно, чтобы заставить тупить сайт, использующий unserialize. при тех же условиях include работает сильно быстрее.
2к элементов (исходник):

unserialize: 4.7870788574219
unserialize + file_get_contents: 6.4405863285065
eval: 14.268187999725
include: 21.123723506927
ну может сферический unserialize в вакууме и обгоняет сферический include… в боевых условиях, когда данные — это не просто range(1, 2000), а многомерный массив и обращение к этим данным происходит очень часто include выигрывает (наверное, за счёт кеша фс). по крайней мере, когда в DLE был дефолтный кеш на serialize+unserialize страница под нагрузкой генерилась 20 секунд. когда я сделал var_export+include она стала генериться 400 мсек.
В моем примере не использовался range(1, 2000), а условия генерации очень близки к реальной работе. Давайте подумаем вместе: чтобы загрузить serialize-данные необходимо просто разместить их в памяти, а для инклюд-данных нужно сначала интерпретировать код, что уже само собой отнимает время. Поэтому врядли кеш фс играет тут какую-то роль. Может быть в ваших экспериментах не учтен ускоритель пхп (xcache, eAccelerator)?
Давайте подумаем :)

Чтобы десериализировать данные нужно:
1. вычитать их из хранилища (файл, база, етс.)
2. разместить их в памяти
3. распарсить их в пхп-структуру, проверив корректность

Чтобы сделать инклюд нужно:
1. вычитать код из хранилища
2. разместить его в памяти
3. выполнить его, проверив на корректность

По-вашему выходит так, что распарсить строку в пхп-структуру быстрее, чем сделать eval. Вполне возможно, что в идеальных условиях так и есть, но в реальности, я повторюсь, мой метод выиграл у unserialize'а по производительности с огромным опережением. Речь идёт о проекте с ~200k хитов в сутки. Без нагрузки unserialize справлялся отлично, но как только нагрузку дали страница стала генериться по полминуты. Заменил unserialize на include — вышло 200-400 мсек без каких-либо акселераторов.
Может быть дело в том, что размер массива, записанного в сериализованном виде был 8 мегабайт? :-D
1. Таки что же вы подразумеваете под «выполнить его» в 3-ем пункте инклюда?
2. Сравнима ли интерпретация пхп-кода и простая проверка на длину данных + размещение данных в памяти в serialize?
3. Что значит «нагрузку дали»? Закончилась RAM?
4. Используется ли кешатор пхп-кода в RAM (xcache, eAccelerator и т.п.)?
1 — Ну таки код РНР нужно перегнать в Zend-структуру? Или нет? :)
2 — Вы считаете, что unserialize — это только проверить длину строки и разместить в памяти? А то, что из строки нужно получить массив с правильными типами каждого элемента? Скорее всего внутри РНР идёт посимвольная обработка строки.
3 — Значит, запустили ab :)
4 — Не используется до сих пор. Всё, что изменили — переехали с винды на генту и спрятали апач за джинкс, но всё это вряд ли критично для конкретно обсуждаемого куска кеша.
> Ну таки код РНР нужно перегнать в Zend-структуру? Или нет? :)

то есть распарсить, проверить на синтаксис и интерпретировать, в процессе чего будет перегонка данных в структуру?

> Вы считаете, что unserialize — это только проверить длину строки и разместить в памяти

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

> Значит, запустили ab :)

при таких размерах данных, видимо, закончилась RAM и их негде было размещать. Или там были одни и те же данные? Других размеров?

1 — в процессе интерпретации ;-)
2 — ещё раз, я не говорю про длину и всё такое. кроме проверки длины нужно РАЗОБРАТЬ строку и СФОРМИРОВАТЬ структуру.
3 — это был список категорий :-) он практически не менялся. память не закончилась, памяти было несколько гигов свободно. сильно загружался процессор.
> ещё раз, я не говорю про длину и всё такое. кроме проверки длины нужно РАЗОБРАТЬ строку и СФОРМИРОВАТЬ структуру.

я правильно понимаю, что затраты на разбор строки сериализованных данных несравнимы с затратами на распарсивание кода?

> это был список категорий :-) он практически не менялся. память не закончилась, памяти было несколько гигов свободно. сильно загружался процессор.

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

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

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

стоял (и стоит) голый интерпретатор, без оптимайзеров, без кешеров. не могу утверждать на 100%, код не читал, но мне кажется, что сам по себе интерпретатор не кеширует код между запросами.

зы: php 5.2.13 win32, apache 2.2, windows 2k3 32bit, 8 процессоров по 2 ядра, 16 гиг оперативки.
Думаю, из моих тестов можно сделать выводы по скорости разбора (4-5 раз). Наивно предполагать, что десеаризация, скажем, объекта будет занимать дольше его же распарсивания в коде.

С некоторой погрешностью можно представить мою мысль наглядно:
// unserialize
$data = unserialize("...");

// упрощенный процесс include
eval('$data = unserialize("...")');
упрощённый процесс инклюда другой :)
eval('$data = "..."');
откуда там десериализация внутри евала?
Все правильно, может быть $data = "...", может $data = array("...") или $data = new Dir;
считаю, что ресурсы на распарсивание данных после "=" сопоставимы с разбором unserialize.
Простите, не уследил за вашей мыслью. Можно медленно и подробно, что с чем и почему :)
чтобы подробно, можно посмотреть этот код, чтобы медленно увеличьте кол-во итераций.

unserialize: 13.223651647568
php: 11.908849477768
кстати, на графике в посте не видно кривой инклюда
автор в посте инклюды не тестировал :)
цель статьи рассказать про проблему unserialize, а не найти лучший способ
>В PHP есть две замечательные функции serialize и unserialize.

никакие они не замечательные, встроенные объекты абсолютно не переваривают. Причем без всяких предупреждений, просто на выходе пустой объект. И разработчики даже не считают это багом :(
Отключи magic quotes или перед unserialize убери слеши ( stripslashes ).
при чем тут magic quotes, если прямо после serialize получается пустой объект? Если думаешь, что я делаю что-то не так, можешь не стараться: в документации есть примечание про встроенные объекты
То есть на наборе, на котором serialize отрабативает за 30 сек., json_encode — за 0.27..? Я правильно понял?
на P4 3.0Ghz, ubuntu выдало: «Test serialize Serialize set time 9.42804217339 get time 65.6582648754» о_О
Test serialize
Serialize set time 1.63469910622 get time 2.52785992622

Intel® Core(TM)2 Quad CPU Q9300 @ 2.50GHz
PHP 5.3.2-1ubuntu4.2
Есть такая засада. В общем, они там при десериализации с целью поддержки ссылок каждую переменную кладут еще и в отдельный список. Список этот — обычный односвязный, по 1024 переменные на элемент списка. Правда, чтобы положить очередную переменную, они проходятся по списку, начиная с головы, так что сложность алгоритма получается порядка N2*log(N)/1024.

Я обнаружил, что время работы функции unserialize может оказаться неожиданно большим.
к бабке гадалке далеко ходить не надо, ясен пень, что большим. Все что связано с деревьями — это тяжелые операции. Все почему-то не любят по этому XML, но забывают про другие структуры данных. Но если использовать простые структуры данных (типа одномерного массивчика), то это на фоне общей производительность будет не заметно.
Я сначала тоже подумал, что здесь слишком большое время построения двоичного дерева по ключам, и особенность принципиальная. Потому я и проверил эту гипотезу на JSON. Как видим, можно сделать и быстрее, но почему-то не сделано.
Все из-за кривой реализации поддержки ссылок, в транке уже поправили, см. мою ссылку выше.
Sign up to leave a comment.

Articles