MediCoder Aide

Blogues » MediCoder » Optimisation par cache de fonction
Par mytto20892 points 

Optimisation par cache de fonction

Revenons aujourd'hui sur de l'optimisation côté serveur.

Certaines fonctions utilitaires — ou méthodes statiques — sont appelées de manière très régulières avec des arguments identiques ou presque au sein d'une même requete HTTP. Si ces fonctions sont couteuses en ressources, par exemple en exécutant des requêtes SQL? sur un serveur tiers, on peut avoir intérêt à créer un cache d'API intra-requête qui, par définition, conservera en mémoire les valeurs calculées pour un jeu d'arguments donné, afin de les ressortir sans calcul en cas d'appel ultérieur avec le même jeu.

Remarque: il faut bien différencier les caches intra-requête qui peuvent se baser sur un stockage temporaire, souvent dans un variable static, des caches inter-requêtes qui nécessitent l'usage de la mémoire partagée et qui ont nécessité d'être vidés de manière explicite.

Pour la suite, nous prendrons l'exemple de la fonction CSejour::getTagNDA() qui consomme pas mal, notamment en réalisant 4 requêtes SQL — certes mises en cache coté serveur — qui induisent une latence importante sur les gros volumes d'usage.

static function getTagNDA($group_id = null, $type_tag = "tag_dossier") {
  // Calcul compliqué de $tag
  // ...
  // ...
 
  return $tag;
}

Implémentation manuelle

La technique basique consiste à déclarer un tableau statique au sein de la fonction, à calculer une clé de hashage des paramètres et à stocker la valeur calculée en fin de premer traitement pour la ressortir en début des passages suivants.

static function getTagNDA($group_id = null, $type_tag = "tag_dossier") {
  static $cache = array();
  $cache_id = "$group_id-$type_tag";
  if (isset($cache[$cache_id])) {
   return $cache[$cache_id];
  }
 
  // Calcul compliqué de $tag
  // ...
  // ...
 
  return $cache[$cache_id] = $tag;
}

La technique est bonne, efficace et fiable, demande 5 lignes en début de fonction et une altération de la ligne finale du return.

En termes de propagation vers d'autres fonctions, la seule chose qui change vraiment et la clé $cache_id qui concatène l'ensemble des paramètres d'appel. On souhaiterait factoriser de fragment et rendre la déclaration du cache de fonction plus explicite.

Implémentation automagique

Voici une classe utilitaire CFunctionCache (external link) qui se charge de du stockage des valeurs et de leur contexte.

Modification : pour des raisons de performances et de compatibilité, le contexte est désormais passé explicitement (cf. commentaires).

static function getTagNDA($group_id = null, $type_tag = "tag_dossier") {
  $context = array(__METHOD__, func_get_args());
  if (CFunctionCache::exist($context)) {
    return CFunctionCache::get($context);
  }
 
  // Calcul compliqué de $tag
  // ...
  // ...
 
  return CFunctionCache::set($context, $tag);
}

Accessoirement, en bonus, les propriétés CFunctionCache:$data et CFunctionCache:$hits donnent des infos sur respectivement l'état du cache pour l'ensemble de fonctions mises en cache et sur le nombre d'appels réussis au cache.

Sponsors privilégiés

Mediboard project