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

Laisser un commentaire