Traitement batch multi-cœurs en PHP

8 05 2009

Sous ce titre un peu barbare se cache la possibilité de profiter pleinement de son dual, quad, octo, hexa ou autres cœurs lorsqu’on a du traitement de masse à faire.

Pourquoi PHP ? Parce que ! Blague à part, tout simplement parce que lorsque j’ai développé cette fonction, je faisais uniquement du PHP.

Avez-vous déjà remarqué comme la plupart des commandes ne profite jamais de tous les processeurs ou cœurs présents dans votre machine ?

Avec la fonction présentée dans ce billet vous pourrez élaborer des scripts fonctionnant sur un nombre arbitraire de processeurs.

Pré-requis

L’interpréteur PHP en ligne de commandes doit être installé. Sous Ubuntu, tapez :

sudo apt-get install php5-cli

Utilisation

C’est uniquement une fonction que je livre ici ! Si vous voulez vous en servir, il vous faudra rajouter du code (si peu) pour lui faire faire ce que vous voulez.

Le prototype de la fonction est simple :

function pool_execute($commandes,$nb_max_process)

Les paramètres sont les suivants :

  • $commandes : Un tableau dont chaque entrée est une chaîne de caractères contenant une commande shell à exécuter.
  • $nb_max_process : Le nombre maximum de commandes pouvant être exécutées simultanément.

Exemple d’utilisation : Conversion de PNG en JPEG sur un dual-core

$commandes=array(
  "convert photo1.png photo1.jpg",
  "convert photo2.png photo2.jpg",
  "convert photo3.png photo3.jpg"
);
pool_execute($commandes,2);

Plutôt simple d’utilisation, non ?

L’appel à pool_execute ne retourne que lorsque tous les processus se sont terminés. C’est à vous de vous assurer qu’aucun processus ne va se retrouver dans une situation de boucle infinie…

Exemple pouvant resservir : Lecture des commandes depuis un fichier pour exécution sur un quad core :

$commandes=explode("\n",file_get_contents("commandes.txt"));
pool_execute($commandes,4);

Note : Le fichier commandes.txt doit contenir une commande par ligne.

Explications

La fonction pool_execute utilise un pool de processus. Il s’agit d’un banal tableau dont la taille est égale au nombre maximum de commandes à exécuter.

La boucle principale va ensuite s’activer toutes les 0,05 secondes (50000 millionièmes de secondes) pour voir si une nouvelle commande ne peut pas être lancée.

Pour cela, elle parcourt chaque entrée du pool (qui correspond à une commande exécutée) et pour chaque entrée, elle teste si le processus associé est encore en cours d’exécution. S’il ne l’est plus, on le vire du pool pour en installer un nouveau.

Un seul lancement de commande se fait par itération, toutes les 0,05 secondes.

La seconde boucle permet d’attendre les dernières commandes encore en cours d’exécution avant de rendre la main à l’appelant.

Pool_execute fait appel à 3 fonctions de gestion de processus de PHP :

  • proc_open : Permet de lancer un processus en tâche de fond, lorsque proc_open retourne, le processus n’est pas encore terminé, peut-être même n’est-il pas encore initialisé par le système.
  • proc_close : Même lorsque le processus est terminé, il faut quand même le fermer. En effet, le processus terminé peut avoir laissé des informations que l’appelant se doit de consulter.
  • proc_get_status : Permet de connaître l’état du processus. Pool_execute vérifie continuellement si les processus sont à l’état « running » pour pouvoir les remplacer par de nouveaux dans le cas contraire.

Code source

Cette fonction est à incorporer à vos scripts :

<?php
function pool_execute($commandes,$nb_max_process) {
  // Initialise le pool de processus
  $pool=array();
  for($i=0;$i<$nb_max_process;$i++) {
    $pool[$i]=FALSE;
  }

  // Exécute toutes les commandes
  while(count($commandes)>0) {
    $commande=array_shift($commandes);

    // Essaie de lancer une commande
    $commande_lancee=FALSE;
    while($commande_lancee==FALSE) {
      usleep(50000);

      // Recherche une entrée libre dans le pool
      for($i=0;$i<$nb_max_process and $commande_lancee==FALSE;$i++) {
        // Teste si l'entrée a déjà été utilisée
        if($pool[$i]===FALSE) {
          $pool[$i]=proc_open($commande,array(),$foo);
          $commande_lancee=TRUE;
        } else {
          // Teste si l'entrée pointe sur un processus terminé
          $etat=proc_get_status($pool[$i]);
          if($etat['running']==FALSE) {
            proc_close($pool[$i]);
            $pool[$i]=proc_open($commande,array(),$foo);
            $commande_lancee=TRUE;
          }
        }
      }
    }
  }

  // Attend que toutes les commandes restantes se terminent
  $commande_restante=TRUE;
  while($commande_restante==TRUE) {
    usleep(50000);

    // Recherche une commande encore en cours d'exécution dans le pool
    $commande_restante=FALSE;
    for($i=0;$i<$nb_max_process and $commande_restante==FALSE;$i++) {
      // Teste si l'entrée a déjà été utilisée
      if($pool[$i]===FALSE) continue;

      // Teste si l'entrée pointe sur un processus terminé
      $etat=proc_get_status($pool[$i]);
      if($etat['running']!==FALSE) $commande_restante=TRUE;
    }
  }
}

Actions

Information

One response

8 05 2009
Traitement batch multi-cœurs en Python « Le blog de Zigazou

[…] Traitement batch multi-cœurs en Python 8 05 2009 Ce billet est le petit frère du précédent billet Traitement batch multi-cœurs en PHP. […]

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s




%d blogueurs aiment cette page :