Le Code Hash
Le code hash ou le « hashing » est disponible depuis SAS®9. Il propose des méthodes rapides et efficaces pour stocker, chercher et plus généralement manipuler des données dans des tables basées sur des clés d'identification.
Le principe
A première vue, le code hash semble plus compliqué à appréhender que le langage SAS. Cependant cela n'est qu'une illusion. Un moyen simple de comprendre la logique du code hash est de visualiser l'objet hash comme étant une table contenue en mémoire avec des lignes et des colonnes (ou des variables et des enregistrements). Pour la programmation, les seules connaissances dont vous aurez besoin sont de savoir créer une table, la définir, la remplir et y accéder. Des commandes sont prédéfinies à cet effet.
Généralement la syntaxe des commandes se construit de la manière suivante :
Nom_objet_Hash.Nom_commande() ;
Nous allons voir au travers d'un exemple les quatre commandes principales nécessaires pour utiliser un objet hash.
Le but de cet exemple est d'extraire certaines données d'une table (table1) en fonction d'une seconde (table2) en utilisant un objet hash.
Figure 1 : table1
Figure 2 : table2
Nous souhaitons obtenir une troisième table (extraction) qui contiendra uniquement les enregistrements des variables keyvar, var1, var2, var3 et var4 de la table1 relatifs à ceux contenus dans la table2.
Figure 3 : table extraction souhaitée
L'utilisation du code hash n'est possible qu'au sein d'une étape data. Cela implique certaines limitations qui seront abordées dans la fin de cet article.
1. Créer une table en mémoire (objet hash)
La commande .Declare() crée la table, mais à ce stade aucune structure (variables / enregistrements) n'existe encore.
data extraction (drop = Cr i);
Declare hash mon_objet_hash () ;
2. Définir la structure de la table
Dans cet étape nous allons définir la variable identifiante de l'objet hash (keyvar) et les variables de données (var1, var2, var3 et var4) en utilisant les commandes .DefineKey() et .DefineData().
/*Définir la taille de toutes les variables de la table en mémoire*/
Length keyvar var1-var4 8 ;
array var(4);
/*Définir la variable identifiante*/
Cr = mon_objet_hash.DefineKey ("keyvar");
/*Définir les autres variables*/
Cr = Mon_objet_hash.DefineData ("var1", "var2", "var3", "var4");
/*Ecriture de la structure de la table*/
Cr = Mon_objet_hash.DefineDone ();
3. Remplir la table en mémoire
On initialise ensuite les enregistrements de la table en mémoire à partir de la table1. Les enregistrements sont lus et ajoutés un par un dans l'objet hash grâce à la commande .add().
do until (eof_table1) ;
set work.table1 (keep=keyvar var1-var4) end =eof_table1;
rc = Mon_objet_hash.add() ;
end ;
4. Accéder aux données de la table
La variable keyvar est lue enregistrement par enregistrement à partir de la table2. Ensuite grâce à la commande .find(), on recherche dans la table en mémoire si celle-ci contient un enregistrement correspondant à la valeur de la variable keyvar. Si oui les valeurs correspondantes sont écrites dans le PDV (Program Data Vector).
do until (eof_table2) ;
set work.table2 end =eof_table2;
do i=lbound(var) to hbound(var);
var(i) = .;
end;
rc = Mon_objet_hash.find() ;
output ;
end ;
run ;
L'instruction output écrit le contenu du PDV dans la table en sortie mais ne le vide pas. Il est donc nécessaire à chaque itération de réinitialiser les variables var(i) à manquantes.
Note : vous trouverez le programme de cet exemple dans le fichier exemple.sas
Dans quel cas utiliser le code hash ?
• Chercher et trouver des enregistrements à partir d'une clé d'identification
• Trier des tables
• Fusionner des tables
Le but premier du code hash n'est pas de proposer de nouvelles fonctions de traitement mais de les réaliser plus vite. En effet pour retrouver une donnée à partir d'une clé dans une table, les temps d'exécution peuvent être de 2 à 6 fois inférieurs à ceux correspondant à d'autres méthodes de recherche plus classiques. C'est pour quoi il devient d'autant plus intéressant et performant d'utiliser le code hash si l'on travaille avec des tables de taille importante.
Etude de cas : Fusion de table
Dans cet exemple de fusion de deux tables, nous allons comparer les performances entre la méthode classique de fusion (MERGE) et celles utilisant des tables en mémoire grâce au code hash. Les tests sont réalisés sur une machine PC Windows XP pro SP2 à 2Ghz et 2Go de RAM.
- Données d'exemple
Dans un premier temps nous allons créer des données d'exemple :
- Une première table work.small d'environ 60 000 enregistrements avec 21 variables (10 Mo)
- Une seconde table work.large d'environ 315 000 enregistrements avec 683 variables (1.8 Go)
Note : Vous trouverez le programme de génération des données d'exemple ainsi que tous les autres programmes utilisés dans le fichier attaché à cet article : comparaison.sas
- Méthode classique
Dans la majorité des cas, pour réaliser une fusion entre 2 tables, on utilise un programme du type suivant :
proc sort data=work.small; by keyvar; run;
proc sort data=work.large; by keyvar; run;
data work.match_merge;
merge work.large (in=a)
work.small (in=b);
by keyvar;
if a;
run;
Vous noterez que dans ce cas, il est indispensable de réaliser un tri sur les deux tables à fusionner par rapport à la clé d'identification.
Résultats :
33 min 30 sec en temps réel soit 3 min en temps processeur =
5 min (tri de la table work.small) + 28 min 42 sec (tri de la table work.large) + 5 min 43 sec (étape data avec l'instruction merge)
- Méthode avec le « hashing »
Nous allons réaliser la même opération en utilisant les objets de hashing :
data work.hash_merge (drop=rc i);
/* Création d'un objet hash (table) */
declare hash h_small ();
/* Définition de l'objet */
length keyvar smallvar1-smallvar20 8; /*taille des variables*/
array smallvar(20);
rc = h_small.DefineKey ( "keyvar" ); /*définition de la clé d'identification*/
/* Définition des données*/
rc = h_small.DefineData ( "smallvar1","smallvar2","smallvar3","smallvar4",
"smallvar5","smallvar6","smallvar7","smallvar8",
"smallvar9","smallvar10","smallvar11","smallvar12",
"smallvar13","smallvar14","smallvar15","smallvar16",
"smallvar17","smallvar18","smallvar19","smallvar20" );
rc = h_small.DefineDone ();
/* Initialisation de la table*/
do until ( eof_small );
set work.small end = eof_small;
rc = h_small.add ();
end;
/* Fusion des tables */
do until ( eof_large );
set work.large end = eof_large;
/* initialisation des variables avant qu'elles soient fusionnées à partir de h_small */
do i=lbound(smallvar) to hbound(smallvar);
smallvar(i) = .;
end;
rc = h_small.find ();
output;
end;
run;
Dans ce cas, aucun tri préalable n'est nécessaire sur les tables.
Résultats : 5 min 30 sec (26 sec en temps processeur)
Synthèse :
| Fusion de table | Temps Réel | Temps UC |
| Méthode classique (1) | 33 min 30s | 2 min 59.73 sec |
| Hashing (2) | 5 min 30s | 26.18 sec |
| Rapport (1)/(2) | 6.09 | 6.87 |
Limitations
Le code hash se base sur l'utilisation de la mémoire. Deux conséquences sont à noter :
1. Les objets hash existent et sont accessibles uniquement au sein de l'étape DATA dans laquelle ils ont été crées. Ceux-ci sont supprimés à la fin de l'exécution de cette étape DATA.
2. Les objets hash sont directement stockés dans la RAM. Leur taille est donc limitée par la quantité de mémoire disponible sur la machine. Il est préférable de faire une estimation de la taille qu'ils peuvent atteindre au préalable.
Estimation de la taille d'un objet hash :
|
Conclusion
Tel que nous l'avons démontré dans cet article, le code hash est très puissant pour fusionner des tables, et plus généralement pour manipuler des données. Il ne s'agit pas là pour autant de dire que le code hash est la solution la plus adéquate à toutes les situations. L'utilisation des objets hash donne une alternative intéressante et significative à la programmation des étapes DATA, pour l'optimisation des performances sur de gros traitements.
Pour plus d'informations sur le code hash, vous pouvez consulter:
- les présentations du SAS Forum International :
- http://www2.sas.com/proceedings/sugi27/p012-27.pdf
- http://www2.sas.com/proceedings/sugi31/244-31.pdf
- L'aide en ligne :
Pascal Lemetayer
Consultant Support Clients - SAS France

