@prineside
Спам-бот

PHP — стоит ли использовать кэширование результатов выполнения функций

Доброго времени суток.

Работаю над проектом (PHP), в котором практически все операции происходят над файлами и каталогами — чтение, сканирование, вывод дерева и т.д.
Иногда для создания иерархичного дерева каталога (в смысле, с его подкаталогами) нужно больше 6 секунд (со временем время уменьшается до 3, видимо, кэшируются данные о файлах), что сильно тормозит работу.

Возникла идея кэширования результатов выполнения некоторых функций (тех, что связаны со сканированием подкаталогов) для уменьшения времени выполнения и нагрузки на процессор & хард ценой дискового пространства и оперативной памяти (имхо, 500 кб на харде лучше, чем 6 секунд ждать).

Базу данных не использую (для файлов считаю лишним), поэтому даже не думаю использовать ее в целях оптимизации.

Для реализации идеи написал класс:
<?php
class Cacher {

	// Класс для кэширования данных между скриптами
	// Применять к динамическим данным не рекомендуется
	// Сэкономит время при вызове функций, которые выполняются
	// более 10мс.
	// Принцип действия:
	// Данные выполнения функции кэшируются в файл и в переменную класса
	// Если функция будет вызвана в том же скрипте второй (и больше) раз
	// с теми же параметрами, она сразу вернет данные из кэша
	// Если функция вызвана с какими-то параметрами впервые в скрипте,
	// произойдет проверка на существование файла с кэшем и будет возвращено
	// содержимое файла
	//
	// Использование:
	// 1. Создать объект класса / прописать для своего класса extends Cacher
	// 2. Вызвать Cacher->CacheInit(string cache_dir, string prefix), где указать полный путь
	// 	  к каталогу для кэширования и префикс (опционально, желательно для классов)
	// 3. В начале стабильных функций написать
	// 			/* c */	if($this->IsCached('myfunc')){ return $this->GetCache('myfunc'); }
	// 	  где myfunc - имя ячейки кэша (желательно имя функции)
	// 4. Перед отдачей (return) в функции прописать
	// 	  		/* c */	$this->Cache('myfunc', $ret);
	//    где $ret - результат выполнения функции
	
	public $cache = array();
	public $cache_dir;
	public $prefix;
	public $enabled = false;
	
	function CacheInit($cache_dir, $prefix=''){
		if(!is_dir($cache_dir)){
			mkdir($cache_dir);
		}
		$this->cache_dir = $cache_dir;
		$this->prefix = $prefix;
		$this->enabled = true;
	}
	
	function Cache($function, $data){
		if(!$this->enabled){ return false; }
		$this->cache[$function] = $data;
		file_put_contents($this->cache_dir.$this->prefix.$function,serialize($data));
	}
	
	function GetCache($function){
		if(!$this->enabled){ return false; }
		return $this->cache[$function];
	}
	
	function IsCached($function){
		if(!$this->enabled){ return false; }
		if(isset($this->cache[$function])){
			return true;
		}elseif(is_file($this->cache_dir.$this->prefix.$function)){
			$this->cache[$function] = unserialize(file_get_contents($this->cache_dir.$this->prefix.$function));
			return true;
		}else{
			return false;
		}
	}
	
	function ClearCache($function){
		if(!$this->enabled){ return false; }
		if(is_array($function)){
			foreach($function as $unit){
				@unlink($this->cache_dir.$this->prefix.$unit);
			}
		}else{
			@unlink($this->cache_dir.$this->prefix.$function);
		}
		$this->cache = array();
	}
}
?>


Использовал так:
<?php
class SomeClass extends Cacher {

	function __construct(){
		// Включаем кеширование
		$this->CacheInit(ROOT.'/_cache/vcs/');
		// ...
	}

	function ScanDir($path=''){	// Этим будем вытаскивать дерево каталогов
/* c */	$_c = 'ul('.str_replace('/','_slh_',$path.')';
/* c */	if($this->IsCached($_c)){ return $this->GetCache($_c); }

		$return = array();
		$src = opendir($path);
		while($obj = readdir($src)){
			if(is_dir($path.'/'.$obj)){
				$return = array_merge($return, $this->ScanDir($path.'/'.$obj));	// Глубокая рекурсия
			}else{
				$return[] = $path.'/'.$obj;
			}
		}
		closedir($src);

/* c */ $this->Cache($_c, $return);
		return $return;
	}
}

$Obj = new SomeClass();
$Obj->ScanDir();


Пробовал профилировать выполнение с / без «кэширования» в боевых условиях, результат боевых действий (повторные запросы):

Без «кэширования»: 3.1829 с.
С «кэшированием»: 0.0097 с. +420кб на харде

Вопрос к знатокам: в чем может быть подвох, и оправдан ли такой подход (если кто-то с таким сталкивался), если при изменении файлов / каталогов удалять определенные «ячейки кэша» (удалять файлы)
  • Вопрос задан
  • 8069 просмотров
Решения вопроса 1
Кэширование вообще несет в себе одну главную проблему: поддержка актуальности.

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

Меморизация функций (кэширование результатов в зависимости от аргументов) требует выполнение двух ограничений:
1. Функция всегда возвращает одинаковый результат при одинаковых аргументах (не зависит от состояния системы или этими изменениями можно пренебречь)
2. Функция никогда не изменяет внутренние состояние системы (если функция была меморизирована, т.е. результат работы был сохранен в словарь, ключем которого является список аргументов, то дальнейшего вызова этой функции происходить не будет)

Если эти ограничения выполняются, то возможна меморизация функции.

Ответьте самостоятельно можете ли вы использовать кэш и меморизацию исходя из этих критериев.
Ответ написан
Пригласить эксперта
Ответы на вопрос 3
@Sayonji
Всё правильно люди говорят: если вся работа с файлами идет через ваши же скрипты, то хешируйте всё что хотите.
Ответ написан
Комментировать
BuriK666
@BuriK666
Компьютерный псих
Почитайте как/когда/и для чего используют memcache., в Вашем случае всё аналогично.
Ответ написан
AmdY
@AmdY
PHP и прочие вебштучки
я бы начал борьбу с 6-3 секундной функции, например заменив на вызов системного типа find. -print или tree, а так у вас самая большая проблема — инвалидация кеша, при том, что для файлов актуальность важнее скорости.
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы