3. C++ et le monde extérieur



sommaire chapitre 3

3.6. Interopérabilité avec Perl

3.6.1. Pourquoi interfacer PERL avec d'autres langages ?

The most obvious reason is performance. For an interpreted language, Perl is very fast. Many people will say "Anything Perl can do, C can do faster". (They never mention the development time :-) Anyway, you may be able to remove a bottleneck in your Perl code by using another language, without having to write the entire program in that language. This keeps your overall development time down, because you're using Perl for all of the non-critical code.

(Brian Ingerson author of Inline::C).

Pour développer en PERL vous pouvez installer l'EDI PADRE (Perl Application Development and Refactoring Environment) ou EPIC (Perl Editor and IDE for Eclipse) :

sudo apt-get install padre

Il existe au moins trois façons différentes d'interfacer PERL avec le langage C :

XS est probablement la plus difficile des trois méthodes, la courbe d'apprentissage est importante et nécessite de maitriser les points suivants :

3.6.2. Inline::C

3.6.2.a  Hello world

Un premier exemple de programme utilisant inline::C est le fameux Hello World. Voici deux versions :

  1. use Inline C;
  2.  
  3. hello_world
  4.  
  5. __END__
  6. __C__
  7. void hello_world() {
  8.     printf("Hello, world\n");
  9. }
  10.  
  11.  
  1. use strict;
  2. use Inline C=><<'END_C';
  3.  
  4. void hello_world() {
  5.     printf("Hello, world\n");
  6. }
  7. END_C
  8.  
  9. hello_world;
  10.  
  11.  

3.6.2.b  Arguments scalaires

Voici un premier exemple avec passage d'argument scalaire :

  1. use Inline C;
  2.  
  3. $name = $ARGV[0];
  4. die "Usage: perl hello_someone.pl name\n" unless $name;
  5.    
  6. hello_world($name)
  7.  
  8. __END__
  9. __C__
  10. void hello_world(char *s) {
  11.     printf("Hello %s\n", s);
  12. }
  13.  

Dans cet exemple on calcule la somme de deux double :

  1. use Inline C;
  2.  
  3. $a = $ARGV[0];
  4. $b = $ARGV[1];
  5. die "Usage: perl compute_add.pl a b\n" unless $a && $b;
  6.    
  7. $c = add($a, $b);
  8. print $c;
  9.  
  10. __END__
  11. __C__
  12. double add(double a, double b) {
  13.     return a + b;
  14. }
  15.  

3.6.2.c  Autres arguments

Quand Perl appelle un sous-programme il utilise une pile (Stack) de SV * (pointer of Scalar Values). Dans le cas de XS on utilise des typemaps (correspondance de type) pour transformer chaque SV* en un type C. Un utilisateur a la possibilité de créer ses propres typemaps. Il en va de même avec Inline::C

Passage d'un tableau :

  1. use Inline C;
  2.  
  3. @array = (1 .. 20);
  4. # use pointer access with \
  5. $s = sum(\@array);    
  6. print "sum = $s\n"
  7.  
  8. __END__
  9. __C__
  10. double sum(AV* array) {
  11.     int i;
  12.     double s = 0.0;
  13.  
  14.     // here av_len(array) = #elements-1
  15.     for (i=0; i<=av_len(array); i++) {
  16.         SV** elem = av_fetch(array, i, 0);
  17.         if (elem != NULL) {
  18.             s += SvNV(*elem);
  19.         }
  20.     }
  21.  
  22.     return s;
  23. }
  24.  

Voici à présent trois fois le même programme de somme des éléments d'un tableau $z_i = x_i + y_i$ avec des doubles (dbl), des entiers signés (int) et des entiers non signés (nat) :

Il faut se référer à perlgluts pour obtenir plus d'informations :

  1. use warnings;
  2.  
  3. use Inline C => <<'EOC';
  4.  
  5. AV * sum(SV * x, ...) {
  6.     Inline_Stack_Vars;
  7.     long array_size, i;
  8.     AV * ret = newAV();
  9.  
  10.     array_size = SvUV(Inline_Stack_Item(0));
  11.  
  12.     if (Inline_Stack_Items != (2 * array_size) + 1) {
  13.         croak("bad number of arguments supplied to sum");
  14.     }
  15.  
  16.     // for efficiency, make the array big enough
  17.      av_extend(ret, array_size - 1);
  18.  
  19.     /* multiply corresponding array elements and push the result into ret */
  20.     for(i = 1; i <= array_size; i++) {
  21.         double x_i = SvNV(Inline_Stack_Item(i));
  22.         double y_i = SvNV(Inline_Stack_Item(i + array_size));
  23.        
  24.         av_push(ret, newSVnv(x_i + y_i));
  25.     }
  26.     return ret;
  27. }
  28. EOC
  29.  
  30. my @num1 = (2.2, 3.3, 4.4,5.5);
  31. my @num2 = (6.6, 7.7, 8.8, 9.9);
  32.  
  33. $arref = sum(scalar(@num1), @num1, @num2);
  34.  
  35. print "@$arref\n";
  36.  
  37.  
  1. use warnings;
  2.  
  3. use Inline C => <<'EOC';
  4.  
  5. AV * sum(SV * x, ...) {
  6.     Inline_Stack_Vars;
  7.     long array_size, i;
  8.     AV * ret = newAV();
  9.  
  10.     array_size = SvUV(Inline_Stack_Item(0));
  11.  
  12.     if (Inline_Stack_Items != (2 * array_size) + 1) {
  13.         croak("bad number of arguments supplied to sum");
  14.     }
  15.  
  16.     // for efficiency, make the array big enough
  17.     av_extend(ret, array_size - 1);
  18.  
  19.     for(i = 1; i <= array_size; i++) {
  20.         int x_i = SvIV(Inline_Stack_Item(i));
  21.         int y_i = SvIV(Inline_Stack_Item(i + array_size));
  22.        
  23.         av_push(ret, newSViv(x_i + y_i));
  24.     }
  25.    
  26.     return ret;
  27. }
  28.  
  29. EOC
  30.  
  31. my @num1 = (-2, 3, -4, 5);
  32. my @num2 = (6, -7, 8, -9);
  33.  
  34. $arref = sum(scalar(@num1), @num1, @num2);
  35.  
  36. print "@$arref\n";
  37.  
  38.  
  1. use warnings;
  2.  
  3.  
  4. use Inline C => <<'EOC';
  5.  
  6. AV * sum(SV * x, ...) {
  7.     Inline_Stack_Vars;
  8.     long array_size, i;
  9.     // create array that will be returned to perl
  10.     AV * ret = newAV();
  11.  
  12.     array_size = SvUV(Inline_Stack_Item(0));
  13.  
  14.     if (Inline_Stack_Items != (2 * array_size) + 1) {
  15.         croak("bad number of arguments supplied to sum");
  16.     }
  17.    
  18.     // for efficiency, make the array big enough
  19.     av_extend(ret, array_size - 1);
  20.  
  21.     for(i = 1; i <= array_size; i++) {
  22.         unsigned long int x_i = SvUV(Inline_Stack_Item(i));
  23.         unsigned long int y_i = SvUV(Inline_Stack_Item(i + array_size));
  24.         av_push(ret, newSVuv(x_i + y_i));
  25.     }
  26.    
  27.     return ret;
  28. }
  29.  
  30. EOC
  31.  
  32. my @num1 = (2, 3, 4, 5);
  33. my @num2 = (6, 7, 8, 9);
  34.  
  35. $arref = sum(scalar(@num1), @num1, @num2);
  36.  
  37. print "@$arref\n";
  38.  
  39.  

Par exemple si on utilise des valeurs (-6, -7, -8, -9) pour @num2 dans la version nat on obtiendra :

18446744073709551612 18446744073709551612 18446744073709551612 18446744073709551612

soit $2^63 - 4 = 18 446 744 073 709 551 616$

Si les tableaux sont de grande taille, il faut éviter de les passer par valeur mais les passer par adresse :

  1. use warnings;
  2.  
  3.  
  4. use Inline C => <<'EOC';
  5.  
  6. AV * sum(SV * x, ...) {
  7.     Inline_Stack_Vars;
  8.     long array_size, i;
  9.     // create array that will be returned to perl
  10.     AV * ret = newAV();
  11.  
  12.     array_size = SvUV(Inline_Stack_Item(0));
  13.  
  14.     if (Inline_Stack_Items != (2 * array_size) + 1) {
  15.         croak("bad number of arguments supplied to sum");
  16.     }
  17.    
  18.     // for efficiency, make the array big enough
  19.     av_extend(ret, array_size - 1);
  20.  
  21.     for(i = 1; i <= array_size; i++) {
  22.         unsigned long int x_i = SvUV(Inline_Stack_Item(i));
  23.         unsigned long int y_i = SvUV(Inline_Stack_Item(i + array_size));
  24.         av_push(ret, newSVuv(x_i + y_i));
  25.     }
  26.    
  27.     return ret;
  28. }
  29.  
  30. EOC
  31.  
  32. my @num1 = (2, 3, 4, 5);
  33. my @num2 = (6, 7, 8, 9);
  34.  
  35. $arref = sum(scalar(@num1), @num1, @num2);
  36.  
  37. print "@$arref\n";
  38.  
  39.  

3.6.2.d  test de performance : popcount

On s'intéresse à la fonction popcount (ou population count -- popcnt eax, ebx en assembleur) qui compte le nombre de bits à 1 dans un entier 32 ou 64 bits.

Cette fonction a été implantée de manière native au sein des processeurs d'architecture x86 à partir de 2007 (SSE4a d'AMD, puis SSE4 d'Intel).

Voici une première version qui implante la fonction popcount en perl, on réalise une boucle de test sur 10.000.000 d'entiers.

  1. #!/usr/bin/env perl
  2. use strict;
  3. use warnings;
  4. #use Test::More;
  5. #use List::Util qw(reduce);
  6.  
  7. my @masks = map {
  8.   my $group_bin = 0 x $_ . 1 x $_;
  9.   my $bin_length = $_ * 2;
  10.   my $mask_bin = $group_bin x ( 32 / $bin_length );
  11.   unpack 'N', pack 'B32', $mask_bin;
  12. } 1, 2, 4, 8, 16;
  13.  
  14. sub popcount($) {
  15.   my $num = shift;
  16.   maskshift( $num, 0, 1 );
  17. }
  18.  
  19. sub maskshift {
  20.   my ( $num, $mask_index, $length ) = @_;
  21.   my $mask = $masks[$mask_index];
  22.   my $sum = ( $num & $mask ) + ( $num >> $length & $mask );
  23.   if ( $mask_index < $#masks ) {
  24.     maskshift( $sum, $mask_index + 1, $length * 2 );
  25.   } else {
  26.     $sum;
  27.   }
  28. }
  29.  
  30. my $x = 10000000;
  31. my $i;
  32. for ($i = 1; $i <= $x; ++$i) {
  33.     popcount($i);
  34. }  
  35.  
  36.  

Voici à présent la version qui utilise la fonction __builtin_popcount de gcc :

  1. #!/usr/bin/env perl
  2.  
  3. use warnings;
  4.  
  5. use Inline C =><<'END_OF_C_CODE';
  6.  
  7. int c_popcount(int x) {
  8.     return __builtin_popcount(x);
  9. }
  10. END_OF_C_CODE
  11.  
  12.  
  13. my $x = 10000000;
  14. my $i;
  15. for ($i = 1; $i <= $x; ++$i) {
  16.     c_popcount($i);
  17. }  
  18.  
  19.  

Les résultats en terme de temps d'exécution en secondes figurent dans le tableau suivant :

 Processeur   perl   inline::C   amélioration   facteur 
 Intel Core i3 2375M @ 1.5 Ghz   68   3.00   95%   x22.66 
 Intel Core i5-4570 @ 3.20GHz   19.40   1.41   93%   x13.75 
 Intel Core i5-3570K @ 3.40GHz   36.36   2.08   94%   x17.48 
 Intel Core i7-4790 @ 3.60GHz   17.36   1.19   93%   x14.58 
Temps d'exécution en secondes de popcount pour différents processeurs, comparaison perl et perl avec inline::C

3.6.2.e  test de performance : mandelbrot

On désire tester l'efficacité du calcul sur l'ensemble de Mandelbrot.

  1. #!/usr/bin/perl
  2.  
  3. sub escapes {
  4.     my ($cr, $ci, $it) = @_;
  5.     my $zr = 0.0;
  6.     my $zi = 0.0;
  7.     for ($i = 0; $i < $it; ++$i) {
  8.         # z <- z^2 + c
  9.         my $zrtmp = $zr*$zr - $zi*$zi + $cr;
  10.         $zi = 2*$zr*$zi + $ci;
  11.         $zr = $zrtmp;
  12.         if (($zr*$zr + $zi*$zi) > 4) {
  13.             return " ";
  14.          }  
  15.     }
  16.     return "X";
  17. }
  18.      
  19. sub mandel {
  20.     my ( $xmin, $xmax, $xstep, $ymin, $ymax, $ystep, $iterations ) = @_;
  21.     for (my $yc = 0; $yc < $ystep; $yc++) {
  22.         my $y = $yc*($ymax-$ymin)/$ystep + $ymin;
  23.         for (my $xc = 0; $xc < $xstep; $xc++) {
  24.             my $x = $xc*($xmax-$xmin)/$xstep + $xmin;
  25.             print escapes($x, $y, $iterations);
  26.         }
  27.         print "\n";
  28.     }  
  29. }
  30.  
  31. mandel(-2.0, 1.0, 256, -1.0, 1.0, 256, 100000);
  32.  
  33.  

Voici à présent la version qui utilise la fonction inline::C :

  1. use warnings;
  2. use Inline C => Config => CLEAN_AFTER_BUILD => 0,
  3.     CC => '/usr/bin/gcc',
  4.     CCFLAGS => '-fopenmp', BUILD_NOISY => 1,
  5.     LIBS => '-lgomp -lpthread -ldl -lm';
  6. use Inline C =><<'END_OF_C_CODE';
  7.  
  8. #include <omp.h>
  9.  
  10. int escapes(double cr, double ci, int it) {
  11.   double zr = 0;
  12.   double zi = 0;
  13.   double zrtmp;
  14.   int i;
  15.  
  16.   for(i=0; i<it; i++) {
  17.     // z <- z^2 + c
  18.     zrtmp = zr*zr - zi*zi + cr;
  19.     zi = 2*zr*zi + ci;
  20.     zr = zrtmp;
  21.     if (zr*zr + zi*zi > 4) {
  22.       return 1;
  23.     }
  24.   }
  25.   return 0;
  26. }
  27.  
  28. void mandel(double xmin, double xmax, int xstep, double ymin, double ymax, int ystep, int iters) {
  29.   int yc;
  30.      
  31.   // array of string to store result
  32.   char *m = (char *) malloc(ystep * (xstep + 1) * sizeof(char));
  33.  
  34.   #pragma omp parallel for
  35.   for(yc=0; yc<ystep; yc++) {
  36.     double y = yc*(ymax-ymin)/ystep + ymin;
  37.     int xc;
  38.    
  39.     for(xc=0; xc<xstep; xc++) {
  40.       double x = xc*(xmax-xmin)/xstep + xmin;
  41.       if (escapes(x, y, iters)) {
  42.         m[yc * (xstep + 1) + xc] = ' ';
  43.       } else {
  44.         m[yc * (xstep + 1) + xc] = 'X';  
  45.       }
  46.     }
  47.     // add end of string
  48.     m[yc * (xstep+1) + xstep] = '\0';  
  49.    
  50.   }
  51.  
  52.   for(yc=0; yc<ystep; yc++) {
  53.       printf("%s\n", &m[yc * (xstep+1)]);
  54.   }
  55.   free(m);
  56. }
  57.  
  58. END_OF_C_CODE
  59.  
  60. mandel(-2.0, 1.0, 256, -1.0, 1.0, 256, 100000);
  61.  
  62.  
  63.  

Les résultats en terme de temps d'exécution en secondes figurent dans le tableau suivant :

 Processeur   type   inline::C   amélioration   facteur 
 Intel Core i3 2375M @ 1.5 Ghz   standard perl   200   -   - 
 Intel Core i3 2375M @ 1.5 Ghz   Inline (1 thread)   3.53   %98   x 57 
 Intel Core i3 2375M @ 1.5 Ghz   Inline (2 threads)   1.71   %99   x 117 
 Intel Core i5-3570K @ 3.40GHz   standard perl   86.82   -   - 
 Intel Core i5-3570K @ 3.40GHz   Inline (1 thread)   13.68   84%   x6.34 
 Intel Core i5-4570 @ 3.20GHz   standard perl   55.35   -   - 
 Intel Core i5-4570 @ 3.20GHz   Inline (1 thread)   0.61   98%   x90.73 
 Intel Core i5-4570 @ 3.20GHz   Inline (2 threads)   0.32   99%   x172.96 
 Intel Core i5-4570 @ 3.20GHz   Inline (3 threads)   0.28   99%   x197.67 
 Intel Core i7-4790 @ 3.60GHz   standard perl   49.78   -   - 
 Intel Core i7-4790 @ 3.60GHz   Inline (1 thread)   0.55   98%   x90 
 Intel Core i7-4790 @ 3.60GHz   Inline (2 threads)   0.29   99%   x171 
 Intel Core i7-4790 @ 3.60GHz   Inline (4 threads)   0.26   99%   x191 
 Intel Core i7-4790 @ 3.60GHz   Inline (8 threads)   0.18   99%   x276 
 Intel Core i7-4790 @ 3.60GHz   Inline (20 threads)   0.12   99%   x414 
Temps d'exécution en secondes de mandelbrot (10.000 itérations) pour différents processeurs, comparaison perl et perl avec inline::C
 Processeur   type   inline::C   amélioration   facteur 
 Intel Core i5-4570 @ 3.20GHz   standard perl (v5.18.2)   560   -   - 
 Intel Core i5-4570 @ 3.20GHz   Inline (1 thread)   5.85   98%   x96 
 Intel Core i5-4570 @ 3.20GHz   Inline (2 threads)   2.99   99%   x187 
 Intel Core i5-4570 @ 3.20GHz   Inline (4 threads)   2.54   99%   x220 
 Intel Core i5-4570 @ 3.20GHz   Inline (8 threads)   1.80   99%   x311 
 Intel Core i5-4570 @ 3.20GHz   Inline (20 threads)   1.59   99%   x352 
 Intel Core i7-4790 @ 3.60GHz   standard perl (v5.18.2)   502   -   - 
 Intel Core i7-4790 @ 3.60GHz   Inline (1 thread)   5.23   98%   x96 
 Intel Core i7-4790 @ 3.60GHz   Inline (2 threads)   2.68   99%   x187 
 Intel Core i7-4790 @ 3.60GHz   Inline (4 threads)   2.24   99%   x224 
 Intel Core i7-4790 @ 3.60GHz   Inline (8 threads)   1.5   99%   x334 
 Intel Core i7-4790 @ 3.60GHz   Inline (20 threads)   0.94   99%   x534 
Temps d'exécution en secondes de mandelbrot (100.000 itérations, 256x256 pixels) pour différents processeurs, comparaison perl et perl avec inline::C

A noter que j'ai tenté d'utiliser OpenMP avec Inline:C mais dans un premier temps cela ne fonctionnait pas : au final seul un thread etait utilisé malgré mes efforts pour en utiliser au moins deux.

Après plusieurs heures d'investigation, je me suis aperçu que Inline::C utilisait le compilateur clang (cc) que j'avais installé il y a quelques temps, au lieu de gcc. Pour remédier au problème, il suffit de modifier la configuration d'Inline::C en utilisant :

use Inline C => Config => CLEAN_AFTER_BUILD => 0, 
	CC => '/usr/bin/gcc', ...

Concernant l'installation sous Ubuntu cpan -f -i Inline::C ne fonctionnait pas, en installant MakeMaker cela fonctionne :

sudo cpan -i Inline::MakeMaker
sudo cpan -f -i Inline::C

3.6.2.f  manipulation de structures C

Pour manipuler des structures C, on peut utiliser Inline::Struct :

sudo cpan -i Inline::Struct
  1. use Inline C => 'DATA', structs => ['Person'];
  2.  
  3. my $obj = Inline::Struct::Person->new;
  4. $obj->name("toto");
  5. $obj->age(10);
  6.  
  7. print Person_print($obj), "\n";
  8.  
  9. __END__
  10. __C__
  11.  
  12. struct Person {
  13.     char *name;
  14.     int age;
  15. };
  16.  
  17. typedef struct Person Person;
  18.  
  19. SV *Person_print(Person *p) {
  20.     return newSVpvf("name=%s, age=%d", p->name, p->age);
  21. }
  22.  
  1. my $obj1 = Person->new('riri', 12);
  2. my $obj2 = Person->new('fifi', 11);
  3. my $obj3 = Person->new('loulou', 10);
  4.  
  5. for my $obj ($obj1, $obj2, $obj3) {
  6.     print $obj->get_name, " ",  $obj->get_age, "\n";
  7. }
  8.  
  9. #---------------------------------------------------------
  10. package Person;
  11. use Inline C => <<'END';
  12. typedef struct {
  13.     char* name;
  14.     int age;
  15. } Person;
  16.  
  17. SV* new(char* class, char* name, int age) {
  18.     Person* p = malloc(sizeof(Person));
  19.     SV* obj_ref = newSViv(0);
  20.     SV* obj = newSVrv(obj_ref, class);
  21.     p->name = strdup(name);
  22.     p->age = age;
  23.     sv_setiv(obj, (IV)p);
  24.     SvREADONLY_on(obj);
  25.     return obj_ref;
  26. }
  27.  
  28. char* get_name(SV* obj) {
  29.     return ((Person*)SvIV(SvRV(obj)))->name;
  30. }
  31.  
  32. int get_age(SV* obj) {
  33.  return ((Person*)SvIV(SvRV(obj)))->age;
  34. }
  35.  
  36. void DESTROY(SV* obj) {
  37.     Person* p = (Person*)SvIV(SvRV(obj));
  38.     free(p->name);
  39.     free(p);
  40. }
  41. END
  42.  

3.6.2.g  manipulation de classes C++

Pour manipuler des objets C++, on peut utiliser Inline::CPP :

sudo cpan -i Inline::CPP
  1. use Inline 'C++';
  2.  
  3. my $obj1 = Person->new('riri', 12);
  4. my $obj2 = Person->new('fifi', 11);
  5. my $obj3 = Person->new('loulou', 10);
  6.  
  7. for my $obj ($obj1, $obj2, $obj3) {
  8.     print $obj->get_name, " ",  $obj->get_age, "\n";
  9. }
  10.  
  11. __END__
  12. __C++__
  13.  
  14. class Person {
  15. public:
  16.     Person(char *name, int age);
  17.     char *get_name();
  18.     int get_age();
  19. private:
  20.     char *name;
  21.     int age;
  22. };
  23.  
  24. Person::Person(char *name, int age) {
  25.     this->name = name;
  26.     this->age = age;
  27. }
  28.  
  29. char *Person::get_name() {
  30.     return name;
  31. }
  32.  
  33. int Person::get_age() {
  34.     return age;
  35. }
  36.  
  37.  

3.6.3. SWIG

A partir du fichier suivant on désire créer un module qui pourra être appelé depuis Perl. Notez qu'on utilise la librairie OpenMP :

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <omp.h>
  4.  
  5. int escapes(double cr, double ci, int it) {
  6.   double zr = 0;
  7.   double zi = 0;
  8.   double zrtmp;
  9.   int i;
  10.  
  11.   for(i=0; i<it; i++) {
  12.     // z <- z^2 + c
  13.     zrtmp = zr*zr - zi*zi + cr;
  14.     zi = 2*zr*zi + ci;
  15.     zr = zrtmp;
  16.     if (zr*zr + zi*zi > 4) {
  17.       return 1;
  18.     }
  19.   }
  20.   return 0;
  21. }
  22.  
  23. /**
  24.  * main function of the module
  25.  */
  26. void mandel(double xmin, double xmax, int xstep, double ymin, double ymax, int ystep, int iters) {
  27.   int yc;
  28.  
  29.   // array of string to store result
  30.   char *m = (char *) malloc(ystep * (xstep + 1) * sizeof(char));
  31.  
  32.   #pragma omp parallel for  
  33.   for(yc=0; yc<ystep; yc++) {
  34.     double y = yc*(ymax-ymin)/ystep + ymin;
  35.     int xc;
  36.     for(xc=0; xc<xstep; xc++) {
  37.       double x = xc*(xmax-xmin)/xstep + xmin;
  38.       escapes(x, y, iters);
  39.       if (escapes(x, y, iters)) {
  40.         m[yc * (xstep + 1) + xc] = ' ';
  41.       } else {
  42.         m[yc * (xstep + 1) + xc] = 'X';  
  43.       }
  44.     }
  45.     // add end of string
  46.     m[yc * (xstep+1) + xstep] = '\0';  
  47.    
  48.   }
  49.  
  50.   for(yc=0; yc<ystep; yc++) {
  51.       printf("%s\n", &m[yc * (xstep+1)]);
  52.   }
  53.  
  54.   free(m);
  55. }
  56.  
  57.  
swig -perl5 -shadow -module mandelbrot mandelbrot_swig.c

la comande précédente génère les fichiers :

On compile ensuite les fichiers .c et on crée la librairie : ne pas oublier d'ajouter -fopenmp avec GCC et -openmp avec ICC (Intel C Compiler) :

# compilation avec wrapper with GCC
gcc -c `perl -MConfig -e 'print join(" ", @Config{qw(ccflags optimize cccdlflags)},"-I\$Config{archlib}/CORE")'` mandelbrot_swig.c mandelbrot_swig_wrap.c -fopenmp

# édition de liens
gcc `perl -MConfig -e 'print \$Config{lddlflags}'` mandelbrot_swig.o mandelbrot_swig_wrap.o -o libmandelbrot.so -fopenmp
# compilation avec wrapper with Intel ICC
icc -c `perl -MConfig -e 'print join(" ", @Config{qw(ccflags optimize cccdlflags)},"-I$Config{archlib}/CORE")'` mandelbrot_swig.c mandelbrot_swig_wrap.c -openmp

# édition de liens
icc `perl -MConfig -e 'print \$Config{lddlflags}'` mandelbrot_swig.o mandelbrot_swig_wrap.o -o libmandelbrot.so -Wl,--start-group -L/opt/intel/mkl/lib/intel64 -L/opt/intel/lib/intel64 -Wl,--end-group -lmkl_core -liomp5 -lmkl_intel_thread -lpthread -ldl -lm

Eventuellement, si on doit lier avec une librairie qui n'est pas dans les chemins par défaut, on devra ajouter l'option -Xlinker <path> lors de l'édition de liens.

Copier ensuite la librairie générée dans le répertoire des modules publics de Perl (sur ma machine export PERL5LIB="/home/richer/perl5/lib/perl5/x86_64-linux-gnu-thread-multi:/home/richer/perl5/lib/perl5")

cp libmandelbrot.so /home/richer/perl5/lib/perl5

Puis exécuter le programme:

  1. #!/usr/bin/env perl
  2.  
  3. use warnings;
  4. use mandelbrot;
  5. package mandelbrot;
  6.  
  7. mandelbrot::mandel(-2.0, 1.0, 256, -1.0, 1.0, 256, 100000);
  8.  
export OMP_NUM_THREADS=1 && time perl mandelbrot_swig.pl >m1.txt
real	0m33.585s
user	0m33.534s
sys	0m0.024s

export OMP_NUM_THREADS=2 && time perl mandelbrot_swig.pl >m1.txt
real	0m17.066s
user	0m33.585s
sys	0m0.028s

OpenMP est bien pris en compte : avec un thread on met 33 secondes, avec deux threads on ne met plus que 17 secondes.

3.6.4. XS

Créer un répertoire xs et un sous-répertoire Mandelbrot qui servira de nom de module et à y placer les fichiers .h et .c qui correspondent au code à accéder.

  1. #ifndef MANDELBROT_H
  2. #define MANDELBROT_H
  3.  
  4. void mandel(double xmin, double xmax, int xstep, double ymin, double ymax, int ystep, int iters);
  5.  
  6. #endif
  7.  
  8.  
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <omp.h>
  4.  
  5. int escapes(double cr, double ci, int it) {
  6.   double zr = 0;
  7.   double zi = 0;
  8.   double zrtmp;
  9.   int i;
  10.  
  11.   for(i=0; i<it; i++) {
  12.     // z <- z^2 + c
  13.     zrtmp = zr*zr - zi*zi + cr;
  14.     zi = 2*zr*zi + ci;
  15.     zr = zrtmp;
  16.     if (zr*zr + zi*zi > 4) {
  17.       return 1;
  18.     }
  19.   }
  20.   return 0;
  21. }
  22.  
  23. void mandel(double xmin, double xmax, int xstep, double ymin, double ymax, int ystep, int iters) {
  24.   int yc;
  25.  
  26.   // array of string to store result
  27.   char *m = (char *) malloc(ystep * (xstep + 1) * sizeof(char));
  28.  
  29.   #pragma omp parallel for  
  30.   for(yc=0; yc<ystep; yc++) {
  31.     double y = yc*(ymax-ymin)/ystep + ymin;
  32.     int xc;
  33.     for(xc=0; xc<xstep; xc++) {
  34.       double x = xc*(xmax-xmin)/xstep + xmin;
  35.       escapes(x, y, iters);
  36.       if (escapes(x, y, iters)) {
  37.         m[yc * (xstep + 1) + xc] = ' ';
  38.       } else {
  39.         m[yc * (xstep + 1) + xc] = 'X';  
  40.       }
  41.     }
  42.     // add end of string
  43.     m[yc * (xstep+1) + xstep] = '\0';  
  44.    
  45.   }
  46.  
  47.   for(yc=0; yc<ystep; yc++) {
  48.       printf("%s\n", &m[yc * (xstep+1)]);
  49.   }
  50.  
  51.   free(m);
  52. }
  53.  
  54.  
xs
├── Mandelbrot
│   ├── mandelbrot.c
│   └── mandelbrot.h
└── mandelbrot_xs.pl

dans le répertoire xs saisir les commandes :

h2xs -n Mandelbrot -O -x -F '-I..' Mandelbrot/mandelbrot.h
cd Mandelbrot

les options de la commande h2xs sont les suivantes :

Pour compiler avec OpenMP modifier LIBS, CCFLAGS, CC dans le fichier Makefile.PL :

	LIBS      => ['-lgomp'], # e.g., '-lm'
	CCFLAGS   => '-fopenmp',
	CC        => '/usr/bin/gcc'

puis dans le terminal :

perl Makefile.PL
make
make install

A présent, pour utiliser le module, toujours dans le répertoire Mandelbrot, taper :

# revenir dans le répertoire xs
cd ..
perl mandelbrot_xs.pl
  1. use warnings;
  2. use Mandelbrot;
  3.  
  4.  
  5. Mandelbrot::mandel(-2.0, 1.0, 256, -1.0, 1.0, 256, 100000);
  6.  

3.6.5. Bibliographie / Sitographie