Оптимизация загрузки статических данных

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

Встала проблема загрузки заранее посчитанных данных в программу поиска пути между двумя точками (не важно какую). Проблема встала настолько сильно, что загрузка просчитанных данных стала занимать 90% всех последующих расчётов.
Мои данные — двухмерный массив, состоящий из 200 на 200 ячеек примерно.

Тестирую unserialize, json_decode


с буквенными ключами
json_decode — 0.080sec
unserialize — 0.072sec

только цифровые ключи
json_decode — 0.041 sec (170kb)
unserialize — 0.037 sec (500kb)

Сам маршрут ищется за 0.0004-0.0012 sec :)
Значит, надо ещё что-нибудь придумать.

Как вариант — можно загружать данные выборочно. Но для меня этот вариант не подошел (потому что при некоторых условиях необходимо использование всех данных).

А если использовать сгенеренный PHP-array с помощью eval?


eval — 0.086sec
Никуда не годится. :(

Но что мне мешает вместо eval подгружать сгенеренный PHP файл, который будет генериться при изменении данных?
По идее он должен загружать данные быстрее.
В результате родился такой код:
define("PREDEFINED_PATH","path.inc");

include_once(PREDEFINED_PATH);

function create(){
  $data=array();

  // Генерация 2D массива $data
  // ...

  file_put_contents(PREDEFINED_PATH,"<?function return_path(){return ".var_export($data,TRUE).";}?>");

}

function use(){
  //использование

  //$path=serialize($p);
  //$path=json_decode($p);
  $path=return_path();
}

// Файл path.inc выглядит
<?function return_path(){return array(1=>array(1=>array(1=>2,2=>12,3=>...)));}?>


// Функция return_path возвращает готовый массив


Тест показал — 0.023 sec (640kb).
Это 1.6 раз быстрее, чем unserialize. Правда, занимает больше пространства.
Но это дело поправимое — можно сохранять в ручную, например, не сохраняя ключи, пробелы и переходы на следующую строку (что делает var_export).
Единственный минус — статичная структура данных.

eAccelerator


Второй минус — мы не провели ещё один эксперимент — загрузка.
include должен преобразовать 640 kb текста в код, и занимает это 0.37 sec. Ух как не здорово.

Здесь нам на помощь приходит eAccelerator :) снижая вторую и последующие include файла до 0.0001, что на результирующую скорость не влияет.

p.s. спасибо юзеру TiGR за конструктивную критику.
+6
2 апреля 2009, 10:51
14
l2k 11,5

комментарии (38)

0
Nc_Soft #
А как на счет использовать бд?
0
l2k #
И как вы предлагаете использовать БД?
0
habraname #
вероятно, камрад предлагает сверхбыстро гнать данные в таблицы через load infile, и нагружать поиском и обработкой уже бд

я лично недопонял задачи: вы упёрлись в ограничение быстродействия фс на выборку и чтение файлов? вы считываете данные из файлов в память для последующей обработки?
0
l2k #
У меня статический массив 200 на 200. (который изменяется в очень крайнем случае)
Чтобы этот массив распаковать в переменную — требуется процессорное время.

0.023 секунды — одна расспаковка такого огромного массива в переменную. Поиск и обработка средствами БД не подходит совсем — потому что в задаче используется пересеченный поиск по массиву.
0
habraname #
чур меня, чур! пхп гнётся уже на 1к элементов, а тут 40к!
может, попробовать что-нибудь прогрессивное типа XQuery? или что-нибудь хорошо забытое, вроде алгоритмов работы с матрицами и разряжёнными матрицами, в частности?
0
l2k #
Несомненно, решить задачу производительности на C было-бы проще, но потрачено времени было-бы намного больше на разработку.
0
pwlnw #
кстати, если вы не храните ссылки на разнообразные данные в этом массиве, то в своем случае можете попробовать свой массив перевести в непрерывный разделяемый блок памяти (это которые system v shared memory ), просто проецировать его в адресное пространство php и арифметическими действиями с распаковкой считывать данные.
Конечно, снизится скорость считывания из массива, но может быть вас устроит общий результат.
0
developer #
да не в фс тут дело а в распаковке данных
хоть в память положите, а как вы транслировать то будите?
ru.php.net/manual/ru/function.shmop-write.php
0
pwlnw #
Можно попытаться копированием значений упаковать массив в непрерывный блок. Там будут ссылки на значения внутри того же блока. Без понимания внутренних механизмов работы php мне сложно оценить успешность.
Вы видите какие-нибудь проблемы?
+1
Roxis #
Функция return_path() лишняя. Можно так:

file_put_contents(PREDEFINED_PATH,"<?php return '.var_export($data,TRUE).';');
...
$path=include PREDEFINED_PATH;
0
l2k #
Можно и так. В принципе смысл от этого не меняется :)
0
dfuse #
Еще как меняется, в этом случае Вы не плодите лишнюю глобальную функцию, данные можно подтягивать не заботясь о том, какие Вы перед этим подтягивали и сколько раз.

Плюс рекомендую пользоваться $path = include(some_path); (вызов со скобками, так корректнее — как бы функция вернет значение), хотя вот это уже точно не принципиально )
0
dfuse #
А вообще — при 40К элементов возможно есть смысл как-то данные разбить или переформировать. Чтобы обрабатывать кусками.

Еще советую посмотреть в сторону APC — он умеет кешировать переменные, потестируйте. В связке с eAccelerator должно хорошо работать.
0
l2k #
Хороший совет. Спасибо.
0
l2k #
Век учись… eAccelerator имеет оказывается свой API… А я вилосипед придумывал :)

В результате практически мгновенно можно получить данные, пряма из памяти горячими:
(массив 1кб)
eaccelerator_get — 0.00004 sec
apc_get — 0.00816 sec
0
l2k #
И снова не прав я… accelerator_get хранит ссылку на объект :)
0
l2k #
apc_get работает со скоростью unserialize
0
dfuse #
Занятно, не ожидал )
0
l2k #
Сейчас провёл ещё один интересный тест:
unserialize($string) — 0.54
unserialize(accelerator_get()) — 0.56
unserialize(file_get_content($file)) — 0.53
apc_get() — 0.46

Вывод — apc_get быстрее на 10%, чем unserialize :).
То есть, при операциях в 10 ms (0.001 сек) не имеет смысла его использовать. (выйгрыш по сравнению с unserialize будет 1ms). Есть смысл использования только при больших объемах.
0
dfuse #
Эммм… а как получается, что

unserialize($string) — 0.54
unserialize(file_get_content($file)) — 0.53

Какой-то неадекват, Вам не кажется? Ведь во втором случае присутствует операция с файлом, и это всяко дольше, чем со строкой, которая уже есть. А время меньшее.
0
l2k #
Да, я согласен. В первом случае 0.541, во втором 0.539 ;)… 1% ошибки вычислений вкрался…
0
dfuse #
Ну так все равно второй быстрее получается :) Результат противоречит моей логике… Не может файл дергаться быстрее, чем PHP со своей строкой работает… Просто файл с точки зрения РНР — это в итоге та же строка.
0
l2k #
Я говорю о том, что процессор — многозадачная машина и в этом случае обязательно будет набегать ошибка.

Работа с файлом не быстрее работы с памятью — а медленее, потому что необходимо сделать несколько операций:
1) Запуск функции
2) Проверка кеша (если файл закеширован, что в нашем случае и происходит)
3) Возврат из кеша файла.

Просто параллельно выполнялось какое-то действие, (кто-то решил загрузить страницу на сервере). Значения, которые я тут предоставил на самом деле ±0.01 сек.
0
dfuse #
Ну так для этого надо запустить 100 раз в разных условиях, чтобы минимизировать погрешность )
0
l2k #
Времени особо небыло ;)
0
pwlnw #
Тут скрыта еще одна проблема: потребление памяти. При таких объемах каждый экземпляр интерпретатора php создает свою копию массива. Есть целый класс движков, которые используют сходное кеширование. Из-за этого, в некоторых ситуациях получаем просто невероятный перерасход памяти. Очень бы помогла реализация уже собранных и упакованных в сплошной блок разделяемой памяти статичных массивов, но я ее не нашел.
0
VladimirAndreev #
м.б. memcache?
+2
pwlnw #
А еще с помощью мемкешед можно будет лечить спид. В новых версиях, говорят, будет поддержка.

Нет. Там та же самая десереализация да и еще и перекачка данных по tcp-стеку.
0
nickon #
хм… результаты json_encode неплохи в целом…
уже даже то что json не передаёт информацию о размере данных а только их значения позволяет использовать его для кеширования и снизить размер кеш-файлов… протестив его дальше заметил не очень приятную особенность, а именно вырезание русских строк…
пример:

$arr = array ( '1' => 'хабрахабр' );
echo json_encode( $arr );

на выходе получаем {«1»:""}…
0
l2k #
UTF-8 вас спасёт :).
Обязательно перед кодированием необходимо пребобразовывать строку в UTF-8.

Чтобы не мучатся, у себя я уже давно использую UTF-8, как основное, работая с ним прямо в Базе данных.
0
nickon #
UTF всегда спасает :) также его юзаю…
но в данном случае выходит что использовать UTF-8 накладно, т.к. при выполнении вышеуказанного примера получаем:

{«1»:"\u0445\u0430\u0431\u0440\u0430\u0445\u0430\u0431\u0440"}

т.е. 62 байта из 17, что в следствии даст увеличение размера кеша.
0
l2k #
p.s. охрененная фотография :)
0
highw #
Оптимизировать надо не код, уменьшая загрузку с 0.08 до 0.02, а архитектуру
0
l2k #
Задачи бывают совершенно разные
0
DjOnline #
Кстати, замечено по многочисленным тестам, что загрузка сохранённого в файл в виде "
0
DjOnline #
Кстати, замечено по многочисленным тестам, что загрузка сохранённого в файл в виде
0
l2k #
Попробуйте www.softcoder.ru/habraeditor/ (и обязательно предпросмотр перед постом)
0
DjOnline #
Кстати, замечено по многочисленным тестам, что загрузка сохранённого в файл в виде php return массива при включенном APC (и соответственно закэшированном скомпилированном файле, т.е. файл уже находится в памяти) работает быстрее, чем загрузка тех же самых данных при помощи apc_get.
Похоже это связано с тем, что всё равно при apc_get возможно проходит какая-то десериализация.

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