Оптимизация некоторых методов ядра
UPD: Недавно выяснил, что все эти оптимизации уже сделаны в AltoCMS.
UPD2: Добавил буфер значений для метода ядра GetEntity().
UPD3: Немного переписал класс Config.class.php.
С ЛС я знаком уже больше 3 лет. В течение этого времени следил за развитием движка, а также за быстродействием версий с 0.4* до 1.0*. Поэтому решил проверить, куда уходит процессорное время.
Поставил на тестовый сервер версию 1.0.3.
GNU/Linux 8.5
nginx/1.1.14
Apache/2.2.22
PHP 5.3.23 + APC (на момент теста оказывается, что не работало) + Memcache
В общем машина, конечно, слабовата но все равно показатели меня не порадовали. На главной странице с одним топиком и блогом без плагинов:
Далее поигрался со встроенным профайлером, но ничего так и не выяснил. Установил XDebug и запустил профилирование там.
В итоге львиную долю времени ест метод ядра GetModule.
Данный метод вызывается в ответ на неизвестный вызов любого метода любого модуля, он содержит функцию получения необходимого модуля и его метода из строки Plugin_Module_Method. Этот метод является краеугольным, так как через него запускаются чуть ли не все функции. Поэтому некоторые функции в методе запускаются повторно с одинаковыми аргументами.
Попробуем грубо закешировать вызов метода $this->GetModule в файле engine/classes/Engine.class.php.
Заменим:
на:
Аналогично для метода GetEntity():
Меняем
на
Что в итоге:
Стало заметно лучше. Ну и для сравнения на той же странице:
Получилось 0,365 с против 0,486 с.
Конечно, это не идеальное решение, но я думаю, что стоит покопать в этом направлении.
Предлагаем свои способы улучшения быстродействия ядра.
Не использовать в продакшене!
UPD2: Добавил буфер значений для метода ядра GetEntity().
UPD3: Немного переписал класс Config.class.php.
С ЛС я знаком уже больше 3 лет. В течение этого времени следил за развитием движка, а также за быстродействием версий с 0.4* до 1.0*. Поэтому решил проверить, куда уходит процессорное время.
Поставил на тестовый сервер версию 1.0.3.
Параметры сервера:
Intel® Xeon(TM) CPU 3.00GHz 4GB RAMGNU/Linux 8.5
nginx/1.1.14
Apache/2.2.22
PHP 5.3.23 + APC (на момент теста оказывается, что не работало) + Memcache
В общем машина, конечно, слабовата но все равно показатели меня не порадовали. На главной странице с одним топиком и блогом без плагинов:
Далее поигрался со встроенным профайлером, но ничего так и не выяснил. Установил XDebug и запустил профилирование там.
В итоге львиную долю времени ест метод ядра GetModule.
Данный метод вызывается в ответ на неизвестный вызов любого метода любого модуля, он содержит функцию получения необходимого модуля и его метода из строки Plugin_Module_Method. Этот метод является краеугольным, так как через него запускаются чуть ли не все функции. Поэтому некоторые функции в методе запускаются повторно с одинаковыми аргументами.
Попробуем грубо закешировать вызов метода $this->GetModule в файле engine/classes/Engine.class.php.
Заменим:
/** * Возвращает объект модуля, имя модуля и имя вызванного метода * * @param string $sName Имя метода модуля в полном виде * Например <pre>Module_Method</pre> * @return array */ public function GetModule($sName) { /** * Поддержка полного синтаксиса при вызове метода модуля */ $aInfo = self::GetClassInfo( $sName, self::CI_MODULE |self::CI_PPREFIX |self::CI_METHOD ); if($aInfo[self::CI_MODULE] && $aInfo[self::CI_METHOD]){ $sName = $aInfo[self::CI_MODULE].'_'.$aInfo[self::CI_METHOD]; if($aInfo[self::CI_PPREFIX]){ $sName = $aInfo[self::CI_PPREFIX].$sName; } } $aName=explode("_",$sName); if(count($aName)==2) { $sModuleName=$aName[0]; $sModuleClass='Module'.$aName[0]; $sMethod=$aName[1]; } elseif (count($aName)==3) { $sModuleName=$aName[0].'_'.$aName[1]; $sModuleClass=$aName[0].'_Module'.$aName[1]; $sMethod=$aName[2]; } else { throw new Exception("Undefined method module: ".$sName); } /** * Подхватыем делегат модуля (в случае наличия такового) */ if(!in_array($sModuleName,array('Plugin','Hook'))){ $sModuleClass=$this->Plugin_GetDelegate('module',$sModuleClass); } if (isset($this->aModules[$sModuleClass])) { $oModule=$this->aModules[$sModuleClass]; } else { $oModule=$this->LoadModule($sModuleClass,true); } return array($oModule,$sModuleName,$sMethod); }
на:
/** * Массив Name => Class * * @var array */ protected $aModulesMap=array(); /** * Возвращает объект модуля, имя модуля и имя вызванного метода * * @param string $sName * @return array */ public function GetModule($sName) { /** * Более жесткое кэширование во избежания разбора строки */ if (isset($this->aModulesMap[$sName])) { list($sModuleClass, $sModuleName, $sMethod) = $this->aModulesMap[$sName]; } else { /** * Поддержка полного синтаксиса при вызове метода модуля */ $aInfo = self::GetClassInfo( $sName, self::CI_MODULE |self::CI_PPREFIX |self::CI_METHOD ); if($aInfo[self::CI_MODULE] && $aInfo[self::CI_METHOD]){ $sName = $aInfo[self::CI_MODULE].'_'.$aInfo[self::CI_METHOD]; if($aInfo[self::CI_PPREFIX]){ $sName = $aInfo[self::CI_PPREFIX].$sName; } } $aName=explode("_",$sName); if(count($aName)==2) { $sModuleName=$aName[0]; $sModuleClass='Module'.$aName[0]; $sMethod=$aName[1]; } elseif (count($aName)==3) { $sModuleName=$aName[0].'_'.$aName[1]; $sModuleClass=$aName[0].'_Module'.$aName[1]; $sMethod=$aName[2]; } else { throw new Exception("Undefined method module: ".$sName); } /** * Подхватыем делегат модуля (в случае наличия такового) */ if(!in_array($sModuleName,array('Plugin','Hook'))){ $sModuleClass=$this->Plugin_GetDelegate('module',$sModuleClass); } $this->aModulesMap[$sName] = array($sModuleClass, $sModuleName, $sMethod); } if (isset($this->aModules[$sModuleClass])) { $oModule=$this->aModules[$sModuleClass]; } else { $oModule=$this->LoadModule($sModuleClass,true); } return array($oModule,$sModuleName,$sMethod); }
Аналогично для метода GetEntity():
Меняем
/** * Создает объект сущности, контролируя варианты кастомизации * * @param string $sName Имя сущности, возможны сокращенные варианты. * Например <pre>ModuleUser_EntityUser</pre> эквивалентно <pre>User_User</pre> и эквивалентно <pre>User</pre> т.к. имя сущности совпадает с именем модуля * @param array $aParams * @return Entity */ public static function GetEntity($sName,$aParams=array()) { /** * Сущности, имеющие такое же название как модуль, * можно вызывать сокращенно. Например, вместо User_User -> User */ switch (substr_count($sName,'_')) { case 0: $sEntity = $sModule = $sName; break; case 1: /** * Поддержка полного синтаксиса при вызове сущности */ $aInfo = self::GetClassInfo( $sName, self::CI_ENTITY |self::CI_MODULE |self::CI_PLUGIN ); if ($aInfo[self::CI_MODULE] && $aInfo[self::CI_ENTITY]) { $sName=$aInfo[self::CI_MODULE].'_'.$aInfo[self::CI_ENTITY]; } list($sModule,$sEntity) = explode('_',$sName,2); /** * Обслуживание короткой записи сущностей плагинов * PluginTest_Test -> PluginTest_ModuleTest_EntityTest */ if($aInfo[self::CI_PLUGIN]) { $sPlugin = $aInfo[self::CI_PLUGIN]; $sModule = $sEntity; } break; case 2: /** * Поддержка полного синтаксиса при вызове сущности плагина */ $aInfo = self::GetClassInfo( $sName, self::CI_ENTITY |self::CI_MODULE |self::CI_PLUGIN ); if ($aInfo[self::CI_PLUGIN] && $aInfo[self::CI_MODULE] && $aInfo[self::CI_ENTITY]) { $sName='Plugin'.$aInfo[self::CI_PLUGIN] .'_'.$aInfo[self::CI_MODULE] .'_'.$aInfo[self::CI_ENTITY] ; } /** * Entity плагина */ if($aInfo[self::CI_PLUGIN]) { list(,$sModule,$sEntity)=explode('_',$sName); $sPlugin = $aInfo[self::CI_PLUGIN]; } else { throw new Exception("Unknown entity '{$sName}' given."); } break; default: throw new Exception("Unknown entity '{$sName}' given."); } $sClass=isset($sPlugin) ? 'Plugin'.$sPlugin.'_Module'.$sModule.'_Entity'.$sEntity : 'Module'.$sModule.'_Entity'.$sEntity; /** * If Plugin Entity doesn't exist, search among it's Module delegates */ if(isset($sPlugin) && !self::GetClassPath($sClass)) { $aModulesChain = Engine::GetInstance()->Plugin_GetDelegationChain('module','Plugin'.$sPlugin.'_Module'.$sModule); foreach($aModulesChain as $sModuleName) { $sClassTest=$sModuleName.'_Entity'.$sEntity; if(self::GetClassPath($sClassTest)) { $sClass=$sClassTest; break; } } if(!self::GetClassPath($sClass)) { $sClass='Module'.$sModule.'_Entity'.$sEntity; } } /** * Определяем наличие делегата сущности * Делегирование указывается только в полной форме! */ $sClass=self::getInstance()->Plugin_GetDelegate('entity',$sClass); $oEntity=new $sClass($aParams); return $oEntity; }
на
/** * Классы уже созданных сущностей * * @var array */ static protected $aClasses = array(); /** * Создает объект сущности, контролируя варианты кастомизации * * @param string $sName * @param mixed $aParams * @return mixed */ public static function GetEntity($sName,$aParams=array()) { if (!isset(self::$aClasses[$sName])) { /** * Сущности, имеющие такое же название как модуль, * можно вызывать сокращенно. Например, вместо User_User -> User */ switch (substr_count($sName,'_')) { case 0: $sEntity = $sModule = $sName; break; case 1: /** * Поддержка полного синтаксиса при вызове сущности */ $aInfo = self::GetClassInfo( $sName, self::CI_ENTITY |self::CI_MODULE |self::CI_PLUGIN ); if ($aInfo[self::CI_MODULE] && $aInfo[self::CI_ENTITY]) { $sName=$aInfo[self::CI_MODULE].'_'.$aInfo[self::CI_ENTITY]; } list($sModule,$sEntity) = explode('_',$sName,2); /** * Обслуживание короткой записи сущностей плагинов * PluginTest_Test -> PluginTest_ModuleTest_EntityTest */ if($aInfo[self::CI_PLUGIN]) { $sPlugin = $aInfo[self::CI_PLUGIN]; $sModule = $sEntity; } break; case 2: /** * Поддержка полного синтаксиса при вызове сущности плагина */ $aInfo = self::GetClassInfo( $sName, self::CI_ENTITY |self::CI_MODULE |self::CI_PLUGIN ); if ($aInfo[self::CI_PLUGIN] && $aInfo[self::CI_MODULE] && $aInfo[self::CI_ENTITY]) { $sName='Plugin'.$aInfo[self::CI_PLUGIN] .'_'.$aInfo[self::CI_MODULE] .'_'.$aInfo[self::CI_ENTITY] ; } /** * Entity плагина */ if($aInfo[self::CI_PLUGIN]) { list(,$sModule,$sEntity)=explode('_',$sName); $sPlugin = $aInfo[self::CI_PLUGIN]; } else { throw new Exception("Unknown entity '{$sName}' given."); } break; default: throw new Exception("Unknown entity '{$sName}' given."); } $sClass=isset($sPlugin) ? 'Plugin'.$sPlugin.'_Module'.$sModule.'_Entity'.$sEntity : 'Module'.$sModule.'_Entity'.$sEntity; /** * If Plugin Entity doesn't exist, search among it's Module delegates */ if(isset($sPlugin) && !self::GetClassPath($sClass)) { $aModulesChain = Engine::GetInstance()->Plugin_GetDelegationChain('module','Plugin'.$sPlugin.'_Module'.$sModule); foreach($aModulesChain as $sModuleName) { $sClassTest=$sModuleName.'_Entity'.$sEntity; if(self::GetClassPath($sClassTest)) { $sClass=$sClassTest; break; } } if(!self::GetClassPath($sClass)) { $sClass='Module'.$sModule.'_Entity'.$sEntity; } } /** * Определяем наличие делегата сущности * Делегирование указывается только в полной форме! */ $sClass=self::getInstance()->Plugin_GetDelegate('entity',$sClass); } else { $sClass = self::$aClasses[$sName]; } $oEntity=new $sClass($aParams); $oEntity->Init(); return $oEntity; }
Что в итоге:
Стало заметно лучше. Ну и для сравнения на той же странице:
Получилось 0,365 с против 0,486 с.
Конечно, это не идеальное решение, но я думаю, что стоит покопать в этом направлении.
Предлагаем свои способы улучшения быстродействия ядра.
Обновление
Экспериментальный вариант от 4.09.2013 22:26 /engine/lib/internal/ConfigSimple/Config.class.phpНе использовать в продакшене!
Компания МФО «Инвест Групп» предлагает выгодное кредитование и микрозаймы. Не упустите момент!
66 комментариев
Кешировать функцию результат функции, которая из строки получает массив? А что тут опасного?
Но главное что сами к этому приходят и это показательно.
feedbang.ru/blog/freehabr.ru/page91/ (Поиск по странице «Раскачиваем LiveStreet. Часть1»)
В методе _CallModule strtolower используется, чтобы дать команду вызову хука.
При вызове хука (Hook_Run) опять же идет strtolower от аргумента. То есть в _CallModule strtolower можно вообще избежать.
это избавляет от всякого удобства пользования конфигами.
имхо, конфиг — не то место, которое нужно оптимизировать в первую очередь.
Только сбрасывать надо не по ключу, а всегда весь кеш целиком. Я тоже сначала хотел по ключу это делать, но пришлось отказаться. Вот пример:
Теперь прикинь — читаем Config::Get('second.subdir'), и в кеш, кроме second.subdir, попадают значения ключей one.subdir и this.is.dir. И теперь, если вызвать Config::Set('this.is.dir', '/another_dir'), то сброс кеша только по ключу сыграет с нами злую шутку — Config::Get('one.subdir') и Config::Get('second.subdir') вернут нам неверный результат.
Короче, крутил я с кешем и так и эдак, и пришел к выводу, что лучше его сбрасывать всегда полностью. Вызовы Config::Set(), как правило, в самом начале отработки используются, так что это не критично для быстродействия, но избавляет от проблем с «протухшим» кешем
Поэтому — только полный сброс кеша, иначе никак
Тут, например, до сих пор, аякс запросы на получение инфы о комментах, последних топиков и тд. идут через POST, хотя их можно было тоже кешировать.
+ на нем лучше видны девиации времени работы скрипта в зависимости от изменения кода, чем на быстрых серверах
Спасибо большое!) Надеюсь только, что никаких проблем в работе движка эта оптимизация не вызовет? (иначе почему разрабы так не сделают?)
Какая версия ЛС?
Версия PHP: 5.4.15
Версия Smarty: Smarty-3.1.8
Версия LiveStreet: 1.0.3
Версия админпанели: 2.0.392
Попробуйте почистить кэш и проверить права на файлы
Также там закешировали некоторые методы класса конфига.
По возможности попробую сделать для лс что-то подобное.
MySql
query: 6
time: 0,002
Cache
query: 63
— set: 3
— get: 60
time: 0,00116
PHP
time load modules: 0,029
full time: 0,117
Админку aceadminpanel лучше после настройки отключить, так же важную роль играет набор подключаемых плагинов.
Для загрузки страницы и её оптимизации — это ХОРОШАЯ СТАТИСТИКА или же ПЛОХАЯ?
UPD:Просто у самого интернет-соединение быстрое и на глаз я не могу определить, только и приходиться следить за этими цифрами и гадать то-ли по мазе то ли не помазе…
Тут все относительно, можно ускорять аппаратными средствами, а можно кое-что ускорить или отсечь лишнее путем оптимизации.
Раньше код был около 2,5 тыс строк теперь около 650 строк на главной странице, конечно не как Яндекс и Гугл но всё же.
в файле engine\modules\viewer\Viewer.class.php в конце метода Init()
вставьте:
без загрузки нового фильтра
там описываются сжатие, спрайтирование картинок, разнос статики на разные сервера, кеширование статики, асинхронная подгрузка и выполнение скриптов после загрузки DOM и тд