Comparaison entre Data Merge et Hash (Hashing)
Présentation.
Bien souvent en SAS nous sommes amené à traiter des volumes d’information très important, aussi il est nécessaire de pouvoir optimiser le code de nos programmes SAS, afin de diminuer les temps de traitement.
Dans cette article nous allons nous pencher sur une technique de traitement des données SAS optimisant de manière importante la résolution de la problématique « Performance » lors de la fusion de données SAS
Le Code HASH
Le code HASH ou le souvent nommé « hashing » est disponible depuis la version SAS®9. Il nous fournit des méthodes très rapides et plus efficaces pour stocker, rechercher et plus généralement manipuler des données dans des tables basées sur des clés d’identification.
Fonctionnement, démarche avec le Code Hash
Pour un néophyte, le code hash de SAS, apparait comme étant plus compliqué à assimiler que le langage SAS base. Toutefois cela n’est pas très vrai et quand bien même ! Le jeu en vaut la chandelle.
Le code Hash est un code objet permettant de gérer une table en mémoire, avec comme toute autre table SAS, des lignes et des colonnes (variables, observations).
La syntaxe de ces commandes est la suivante : Nom_objet_Hash.Nom_commande(<paramètre>);
I Créer une table en mémoire (objet hash)
La commande .Declare(), permet de faire cela :
Declare hash ma_table_hash () ;
II Ensuite, définir la structure de la table
Définition des variables : clé et données en utilisant les commandes DefineKey() et DefineData().
* Définir la taille des variables de la table en mémoire;
** Afin d’optimiser, vous pouvez vous reporter dans l’aide de SAS : Using SAS software in your operating environment
** Using SAS in Windows
** Length and precision of variables
** Numeric variables
Vous y apprendrez qu’il est souvent inutile de mettre une longueur de 8 pour des variables numériques.;
Length clevar var1-var4 8 ;
array var(4);
* Définir la variable clé;
rc = ma_table_hash.DefineKey (“clevar”);
* Définir les autres variables;
rc = ma_table_hash.DefineData (“var1”, “var2”, “var3”, “var4”);
*Ecriture de la structure de la table;
rc = ma_table_hash.DefineDone ();
Ensuite il faut remplir cette table.
Puis on initialise les observations de la table en mémoire à partir de la tablun. Les observations sont lues et ajoutées une par une dans l’objet hash grâce à la commande .add().
do until (fin_tableun) ;
set tableun (keep=clevar var1-var4) end =fin_tableun;
rc = ma_table_hash.add() ;
end ;
Accéder aux données de la table
La variable clevar est lue observation par observation à partir de la tabledeux.
Ensuite avec la commande .find(), on recherche dans la table (mémoire) si celle-ci contient un observation correspondante à la valeur recherchée présente dans la variable clevar et si oui, alors les valeurs correspondantes sont écrites dans le PDV (Program Data Vector).
do until (fin_tabledeux) ;
set tabledeux end =fin_tabledeux;
do i=lbound(var) to hbound(var);
var(i) = .;
end;
rc = ma_table_hash.find() ;
output ;
end ;
run ;
Attention l’instruction OUTPUT copie le contenu du PDV dans une observation de la table en sortie, mais elle ne vide pas le contenu des variables du PDV, il est donc essentiel de bien penser à initialiser à manquant les variables du PDV.
Dernier point sur la partie téhorique, le calcul de l’espace mémoire nécessaire à une opération de hashing.
Taille de l’objet hash = 
Bien, nous pouvons maintenant passer à la partie technique de la démonstration, voici ci-après, dans l’ordre le code SAS d’un exemple de programme SAS utilisant la technique traditionnelle DATA SORT et MERGE et enfin le HASHING …
Malgré l’amélioration très significative, facteur 3 du Code Hash, nous verrons qu’il est encore possible de diviser par deux cet honorable résultat.
Tout de suite après ce programme, vous trouverez le compte rendu d’exécution de ce programme, soit le journal SAS ou encore la LOG SAS.
/*
Titre: Comparaison.sas DATA MERGE et DATA HASH
Date: 05/08/2009
Auteur Pascal Maubert issue d’un travail de base de
Pascal Lemetayer.
BUT Le but de ce programme est démontrer que dans
certains cas des techniques comme le Hashing
peuvent optimiser les performances d’un traitement.
D’autres éléments d’optimisations seront exposés.
Machine Windows Vista AMD Phenom 9550 4 coeurs
à 2.20 Ghz SAS 9.10
*/
* Offre des informations supplémentaires sur une éxecution de code SAS;
options fullstimer;
/*Création des tables de données pour le test */
%let grand_obs = 500000;
data petit ( keep = clevar petit: )
grand ( keep = clevar grand: )
;
array keys(1:500000) $1 _temporary_;
length clevar 8;
array petitvar [20]; retain petitvar 12;
array grandvar [682]; retain grandvar 55;
do _i_ = 1 to &grand_obs ;
clevar = ceil (ranuni(1) * &grand_obs);
if keys(clevar) = ‘ ‘ then do;
output grand;
if ranuni(1) < 1/5 then output petit;
keys(clevar) = ‘X’;
end;
end;
run;
/* METHODE 1 SORT, DATA MERGE*/
proc sort data=petit; by clevar;
run;
proc sort data=grand; by clevar;
run;
data match_merge;
merge grand (in=a)
petit (in=b);
by clevar;
if a;
run;
/* METHODE 2 HASHING */
data hash_merge (drop=rc i);
/* Création de la table en mémoire */
declare hash h_petit ();
/* Maintenant la structure */
length clevar petitvar1-petitvar20 8;
array petitvar(20);
rc = h_petit.DefineKey ( « clevar » );
rc = h_petit.DefineData ( « petitvar1″, »petitvar2″, »petitvar3″, »petitvar4″,
« petitvar5″, »petitvar6″, »petitvar7″, »petitvar8″,
« petitvar9″, »petitvar10″, »petitvar11″, »petitvar12″,
« petitvar13″, »petitvar14″, »petitvar15″, »petitvar16″,
« petitvar17″, »petitvar18″, »petitvar19″, »petitvar20″ );
rc = h_petit.DefineDone ();
/* Chargement de la table mémoire avec la table petit */
do until ( fin_petit );
set petit end = fin_petit;
rc = h_petit.add ();
end;
/* Création de la table fusionnée */
do until ( fin_grand );
set grand end = fin_grand;
/*Initialisation des variables manquantes */
do i=lbound(petitvar) to hbound(petitvar);
petitvar(i) = .;
end;
rc = h_petit.find ();
output;
end;
run;
/* Pour aller plus loin et encore optimiser plus
Notez qu’il n’est pas possible de se passer
des étapes de tri, car l’option « notsorted »
ne peut être utilisée pour les instructions
MERGE ou UPDATE
Reprise du test, mais cette fois en utilisant
une optimisation supplémentaires l’option compress.
Il en existe deux ayant pour but d’optimiser la
compression de tables contenant essentiellement
des variables en caractères et une seconde pour des
variables numériques.
O P T I M I S A T I O N - T U N I N G
*/
options bufno=8 bufsize=32768 fullstimer compress=binary;
/* nous pourrions également diminuer la longueur de la variables clevar à 4, voir
« Significant Digits and grandst Integer by Length for SAS Variables under Windows », Z/OS etc…
sous windows 4 permet jusqu’à 2**21 soit 2,097,152, peu utile avec l’option compress */
/*Création des tables de données de test*/
%let grand_obs = 500000;
data petit ( keep = clevar petit: )
grand ( keep = clevar grand: )
;
array keys(1:500000) $1 _temporary_;
length clevar 8;
array petitvar [20]; retain petitvar 12;
array grandvar [682]; retain grandvar 55;
do _i_ = 1 to &grand_obs ;
clevar = ceil (ranuni(1) * &grand_obs);
if keys(clevar) = ‘ ‘ then do;
output grand;
if ranuni(1) < 1/5 then output petit;
keys(clevar) = ‘X’;
end;
end;
run;
/* METHODE 1 SORT, DATA MERGE*/
proc sort data=petit; by clevar;
run;
proc sort data=grand; by clevar;
run;
data match_merge;
merge grand (in=a)
petit (in=b);
by clevar;
if a;
run;
/* METHODE 2 HASHING */
data hash_merge (drop=rc i);
/* Création de la table en mémoire */
declare hash h_petit ();
/* Maintenant la structure */
length clevar petitvar1-petitvar20 8;
array petitvar(20);
rc = h_petit.DefineKey ( « clevar » );
rc = h_petit.DefineData ( « petitvar1″, »petitvar2″, »petitvar3″, »petitvar4″,
« petitvar5″, »petitvar6″, »petitvar7″, »petitvar8″,
« petitvar9″, »petitvar10″, »petitvar11″, »petitvar12″,
« petitvar13″, »petitvar14″, »petitvar15″, »petitvar16″,
« petitvar17″, »petitvar18″, »petitvar19″, »petitvar20″ );
rc = h_petit.DefineDone ();
/* Chargement de la table mémoire avec la table petit */
do until ( fin_petit );
set petit end = fin_petit;
rc = h_petit.add ();
end;
/* Création de la table fusionnée */
do until ( fin_grand );
set grand end = fin_grand;
/*Initialisation des variables manquantes */
do i=lbound(petitvar) to hbound(petitvar);
petitvar(i) = .;
end;
rc = h_petit.find ();
output;
end;
run;
LA LOG SAS
Note: Copyright (c) 2002 by SAS Institute Inc., Cary, NC, USA.
Note: SAS (r) Proprietary Software Version 9.00 (TS M0)
Note: La session est exécutée sur la plate-forme WIN_PRO .
Note: Initialisation de SAS used:
temps réel 0.59 secondes
temps processeur 0.54 secondes
1 /*
2 Titre: Comparaison.sas DATA MERGE et DATA HASH
3 Date: 05/08/2009
4 Auteur Pascal Maubert issue d’un travail de base de
5 Pascal Lemetayer.
6
7 BUT Le but de ce programme est démontrer que dans
8 certains cas des techniques comme le Hashing
9 peuvent optimiser les performances d’un traitement.
10
11 D’autres éléments d’optimisations seront exposés.
12
13 Machine Windows Vista AMD Phenom 9550 4 coeurs
14 à 2.20 Ghz SAS 9.10
15 */
16
17 * Offre des informations supplémentaires sur une éxecution de code SAS;
18 options fullstimer;
19
20 /*Création des tables de données pour le test */
21
22 %let grand_obs = 500000;
23
24 data petit ( keep = clevar petit: )
25 grand ( keep = clevar grand: )
26 ;
27 array keys(1:500000) $1 _temporary_;
28 length clevar 8;
29 array petitvar [20]; retain petitvar 12;
30 array grandvar [682]; retain grandvar 55;
31 do _i_ = 1 to &grand_obs ;
32 clevar = ceil (ranuni(1) * &grand_obs);
33 if keys(clevar) = ‘ ‘ then do;
34 output grand;
35 if ranuni(1) < 1/5 then output petit;
36 keys(clevar) = ‘X’;
37 end;
38 end;
39 run;
Note: La table WORK.PETIT a 63406 observations et 21 variables.
Note: La table WORK.GRAND a 315975 observations et 683 variables.
Note: L’étape DATA used (Total process time):
temps réel 18.17 secondes
temps processeur utilisateur 0.84 secondes
temps processeur système 3.55 secondes
Mémoire 4785k
40
41
42 /* METHODE 1 SORT, DATA MERGE*/
43
44 proc sort data=petit; by clevar;
45 run;
Note: 63406 observations copiées de la table WORK.PETIT.
Note: La table WORK.PETIT a 63406 observations et 21 variables.
Note: La procédure SORT used (Total process time):
temps réel 0.09 secondes
temps processeur utilisateur 0.07 secondes
temps processeur système 0.07 secondes
Mémoire 89k
46
47 proc sort data=grand; by clevar;
48 run;
Note: 315975 observations copiées de la table WORK.GRAND.
Note: La table WORK.GRAND a 315975 observations et 683 variables.
Note: La procédure SORT used (Total process time):
temps réel 1:20.49
temps processeur utilisateur 5.46 secondes
temps processeur système 13.16 secondes
Mémoire 223k
49
50 data match_merge;
51 merge grand (in=a)
52 petit (in=b);
53 by clevar;
54 if a;
55 run;
Note: 315975 observations copiées de la table WORK.GRAND.
Note: 63406 observations copiées de la table WORK.PETIT.
Note: La table WORK.MATCH_MERGE a 315975 observations et 703 variables.
Note: L’étape DATA used (Total process time):
temps réel 39.93 secondes
temps processeur utilisateur 1.88 secondes
temps processeur système 6.44 secondes
Mémoire 532k
56
57 /* METHODE 2 HASHING */
58
59 data hash_merge (drop=rc i);
60
61 /* Création de la table en mémoire */
62 declare hash h_petit ();
63
64 /* Maintenant la structure */
65 length clevar petitvar1-petitvar20 8;
66 array petitvar(20);
67 rc = h_petit.DefineKey ( « clevar » );
68 rc = h_petit.DefineData ( « petitvar1″, »petitvar2″, »petitvar3″, »petitvar4″,
69 « petitvar5″, »petitvar6″, »petitvar7″, »petitvar8″,
70 « petitvar9″, »petitvar10″, »petitvar11″, »petitvar12″,
71 « petitvar13″, »petitvar14″, »petitvar15″, »petitvar16″,
72 « petitvar17″, »petitvar18″, »petitvar19″, »petitvar20″ );
73 rc = h_petit.DefineDone ();
74
75 /* Chargement de la table mémoire avec la table petit */
76 do until ( fin_petit );
77 set petit end = fin_petit;
78 rc = h_petit.add ();
79 end;
80
81 /* Création de la table fusionnée */
82 do until ( fin_grand );
83 set grand end = fin_grand;
84 /*Initialisation des variables manquantes */
85 do i=lbound(petitvar) to hbound(petitvar);
86 petitvar(i) = .;
87 end;
88 rc = h_petit.find ();
89 output;
90 end;
91 run;
Note: 63406 observations copiées de la table WORK.PETIT.
Note: 315975 observations copiées de la table WORK.GRAND.
Note: La table WORK.HASH_MERGE a 315975 observations et 703 variables.
Note: L’étape DATA used (Total process time):
temps réel 39.67 secondes
temps processeur utilisateur 2.04 secondes
temps processeur système 5.88 secondes
Mémoire 420k
92
93
94 /* Pour aller plus loin et encore optimiser plus
95
96 Notez qu’il n’est pas possible de se passer
97 des étapes de tri, car l’option « notsorted »
98 ne peut être utilisée pour les instructions
99 MERGE ou UPDATE
100
101 Reprise du test, mais cette fois en utilisant
102 une optimisation supplémentaires l’option compress.
103 Il en existe deux ayant pour but d’optimiser la
104 compression de tables contenant essentiellement
105 des variables en caractères et une seconde pour des
106 variables numériques.
107
108 O P T I M I S A T I O N - T U N I N G
109 */
110
111 options bufno=8 bufsize=32768 fullstimer compress=binary;
112
113 /* nous pourrions également diminuer la longueur de la variables clevar à 4, voir
114 « Significant Digits and grandst Integer by Length for SAS Variables under Windows », Z/OS etc…
115 sous windows 4 permet jusqu’à 2**21 soit 2,097,152, peu utile avec l’option compress */
116
117 /*Création des tables de données de test*/
118
119 %let grand_obs = 500000;
120
121 data petit ( keep = clevar petit: )
122 grand ( keep = clevar grand: )
123 ;
124 array keys(1:500000) $1 _temporary_;
125 length clevar 8;
126 array petitvar [20]; retain petitvar 12;
127 array grandvar [682]; retain grandvar 55;
128 do _i_ = 1 to &grand_obs ;
129 clevar = ceil (ranuni(1) * &grand_obs);
130 if keys(clevar) = ‘ ‘ then do;
131 output grand;
132 if ranuni(1) < 1/5 then output petit;
133 keys(clevar) = ‘X’;
134 end;
135 end;
136 run;
Note: La table WORK.PETIT a 63406 observations et 21 variables.
Note: La compression de la table WORK.PETIT a réduit la taille de 60.86 pourcent.
Compression de 128 pages; la décompression nécessiterait 327 pages.
Note: La table WORK.GRAND a 315975 observations et 683 variables.
Note: La compression de la table WORK.GRAND a réduit la taille de 77.26 pourcent.
Compression de 14371 pages; la décompression nécessiterait 63197 pages.
Note: L’étape DATA used (Total process time):
temps réel 15.08 secondes
temps processeur utilisateur 13.36 secondes
temps processeur système 0.96 secondes
Mémoire 4817k
137
138 /* METHODE 1 SORT, DATA MERGE*/
139
140 proc sort data=petit; by clevar;
141 run;
Note: 63406 observations copiées de la table WORK.PETIT.
Note: La table WORK.PETIT a 63406 observations et 21 variables.
Note: La compression de la table WORK.PETIT a réduit la taille de 60.86 pourcent.
Compression de 128 pages; la décompression nécessiterait 327 pages.
Note: La procédure SORT used (Total process time):
temps réel 0.29 secondes
temps processeur utilisateur 0.24 secondes
temps processeur système 0.06 secondes
Mémoire 89k
142
143 proc sort data=grand; by clevar;
144 run;
Note: 315975 observations copiées de la table WORK.GRAND.
Note: La table WORK.GRAND a 315975 observations et 683 variables.
Note: La compression de la table WORK.GRAND a réduit la taille de 77.26 pourcent.
Compression de 14371 pages; la décompression nécessiterait 63197 pages.
Note: La procédure SORT used (Total process time):
temps réel 39.03 secondes
temps processeur utilisateur 19.45 secondes
temps processeur système 8.14 secondes
Mémoire 261k
145
146 data match_merge;
147 merge grand (in=a)
148 petit (in=b);
149 by clevar;
150 if a;
151 run;
Note: 315975 observations copiées de la table WORK.GRAND.
Note: 63406 observations copiées de la table WORK.PETIT.
Note: La table WORK.MATCH_MERGE a 315975 observations et 703 variables.
Note: La compression de la table WORK.MATCH_MERGE a réduit la taille de 76.18 pourcent.
Compression de 15055 pages; la décompression nécessiterait 63198 pages.
Note: L’étape DATA used (Total process time):
temps réel 18.89 secondes
temps processeur utilisateur 16.66 secondes
temps processeur système 1.15 secondes
Mémoire 546k
152
153 /* METHODE 2 HASHING */
154
155 data hash_merge (drop=rc i);
156
157 /* Création de la table en mémoire */
158 declare hash h_petit ();
159
160 /* Maintenant la structure */
161
162 length clevar petitvar1-petitvar20 8;
163 array petitvar(20);
164 rc = h_petit.DefineKey ( « clevar » );
165 rc = h_petit.DefineData ( « petitvar1″, »petitvar2″, »petitvar3″, »petitvar4″,
166 « petitvar5″, »petitvar6″, »petitvar7″, »petitvar8″,
167 « petitvar9″, »petitvar10″, »petitvar11″, »petitvar12″,
168 « petitvar13″, »petitvar14″, »petitvar15″, »petitvar16″,
169 « petitvar17″, »petitvar18″, »petitvar19″, »petitvar20″ );
170 rc = h_petit.DefineDone ();
171
172 /* Chargement de la table mémoire avec la table petit */
173
174 do until ( fin_petit );
175 set petit end = fin_petit;
176 rc = h_petit.add ();
177 end;
178
179 /* Création de la table fusionnée */
180 do until ( fin_grand );
181 set grand end = fin_grand;
182 /*Initialisation des variables manquantes */
183 do i=lbound(petitvar) to hbound(petitvar);
184 petitvar(i) = .;
185 end;
186 rc = h_petit.find ();
187 output;
188 end;
189 run;
Note: 63406 observations copiées de la table WORK.PETIT.
Note: 315975 observations copiées de la table WORK.GRAND.
Note: La table WORK.HASH_MERGE a 315975 observations et 703 variables.
Note: La compression de la table WORK.HASH_MERGE a réduit la taille de 76.18 pourcent.
Compression de 15055 pages; la décompression nécessiterait 63198 pages.
Note: L’étape DATA used (Total process time):
temps réel 19.79 secondes
temps processeur utilisateur 18.28 secondes
temps processeur système 1.31 secondes
Mémoire 458k