P
P
prineside2013-07-12 17:24:36
PHP
prineside, 2013-07-12 17:24:36

PHP - Should You Cache Function Results?

Good day.

I am working on a project (PHP) in which almost all operations occur on files and directories - reading, scanning, displaying a tree, etc.
Sometimes it takes more than 6 seconds to create a hierarchical directory tree (in the sense, with its subdirectories) (over time, the time decreases to 3, apparently, file data is cached), which greatly slows down the work.

The idea arose of caching the results of some functions (those related to scanning subdirectories) to reduce execution time and load on the processor & hard at the cost of disk space and RAM (IMHO, 500 kb on hard is better than waiting 6 seconds).

I don’t use a database (I consider it superfluous for files), so I don’t even think about using it for optimization purposes.

To implement the idea, I wrote a class:

<?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();
  }
}
?>


Used like this:
<?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();


Tried to profile execution with/without "caching" in combat, result of combat (repeated requests):

Without "caching": 3.1829 s.
With "caching": 0.0097 s. +420kb on hard

A question for connoisseurs: what could be the catch, and is such an approach justified (if someone has come across such a thing), if when changing files / directories, delete certain “cache cells” (delete files)

Answer the question

In order to leave comments, you need to log in

4 answer(s)
D
Dmitry Guketlev, 2013-07-12
@prineside

Caching in general carries one main problem: keeping it up to date.
If you have mechanisms to keep the cache up to date, or have the ability to ignore changes that occur between two cache updates (for you, this time is equal to the work of the php script, the next run will generate a new cache), then such a cache can be used.
Memorization of functions (caching of results depending on arguments) requires two restrictions:
1. The function always returns the same result for the same arguments (does not depend on the state of the system or these changes can be neglected)
2. The function neverdoes not change the internal state of the system (if the function was memorized, i.e. the result of the work was stored in a dictionary whose key is the list of arguments, then this function will not be called further)
If these restrictions are met, then the function can be memorized.
Answer yourself whether you can use cache and memorization based on these criteria.

A
Andrey Burov, 2013-07-12
@BuriK666

Read how/when/and why memcache is used. In your case, everything is the same.

V
Vyacheslav Plisko, 2013-07-12
@AmdY

I would start the fight with a 6-3 second function, for example, replacing it with a call to the find system type. -print or tree, otherwise you have the biggest problem - cache invalidation, despite the fact that for files, relevance is more important than speed.

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question