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 (eXternal SUBroutine) : une interface de description des variables et fonctions C qui permet de créer une librairie qui sera chargée statiquement ou dynamiquement dans Perl.
- SWIG (Simplified Wrapper and Interface Generator) : est un logiciel Open source, permettant de connecter des logiciels ou bibliothèques logicielles écrites en C/C++ avec des langages de scripts tels que : Tcl, Perl, Python, Ruby, PHP, Lua ou d'autres langages de programmation comme Java, C#, Scheme et OCam
- Inline::C et inline::CPP (C++) : permet d'ajouter du code source d'autres langages directement dans le code d'un module PERL et gère les langages suivants : C, C++, Java, Python, Ruby, Tcl, Assembler, Basic, Guile, Befunge, Octave, Awk, BC, TT (Template Toolkit), WebChat ainsi que PERL
XS est probablement la plus difficile des trois méthodes, la courbe d'apprentissage est importante et nécessite de maitriser les points suivants :
- perlxs
- perlxstut
- perlapi
- perlguts : Perl API (Application Programming Interface)
- perlmod
- h2xs
- xsubpp
- ExtUtils::MakeMaker
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 :
use Inline C;
hello_world
__END__
__C__
void hello_world() {
}
use strict;
use Inline C=><<'END_C';
void hello_world() {
}
END_C
hello_world;
3.6.2.b Arguments scalaires
Voici un premier exemple avec passage d'argument scalaire :
use Inline C;
$name = $ARGV[0];
die "Usage: perl hello_someone.pl name\n" unless $name;
hello_world($name)
__END__
__C__
void hello_world
(char
*s) {
}
Dans cet exemple on calcule la somme de deux double :
use Inline C;
$a = $ARGV[0];
$b = $ARGV[1];
die "Usage: perl compute_add.pl a b\n" unless $a && $b;
$c = add($a, $b);
__END__
__C__
double add(double a, double b) {
}
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 :
use Inline C;
@array = (1 .. 20);
# use pointer access with \
$s = sum(\@array);
__END__
__C__
double sum(AV* array) {
// here av_len(array) = #elements-1
for (i=0; i<=av_len(array); i++) {
SV** elem = av_fetch(array, i, 0);
if (elem != NULL) {
}
}
}
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) :
- les tableaux sont passés par valeur ce qui a pour effet en perl de recopier leur contenu dans la pile : si on a deux tableaux de 10 éléments, on aura 21 éléments dans la pile soit 1 + 2 * 10 (on a choisi de placer la taille des tableaux en premier élément)
- on retourne à Perl, un tableau qui contient la somme pour chaque élément $[z_0, ... z_{n-1}]$
Il faut se référer à perlgluts pour obtenir plus d'informations :
- types de données
- les fonctions de types SvIV(SV*), SvUV(SV*), SvNV(SV*) permettent l'accès à la valeur pointée par un type SV * et de le convertir en entier signé, non signé ou en double
use warnings;
use Inline C => <<'EOC';
AV * sum(SV * x, ...) {
Inline_Stack_Vars;
long array_size, i;
AV * ret = newAV();
array_size = SvUV(Inline_Stack_Item(0));
if (Inline_Stack_Items != (2 * array_size) + 1) {
croak("bad number of arguments supplied to sum");
}
// for efficiency, make the array big enough
av_extend(ret, array_size - 1);
/* multiply corresponding array elements and push the result into ret */
for(i = 1; i <= array_size; i++) {
double x_i = SvNV(Inline_Stack_Item(i));
double y_i = SvNV(Inline_Stack_Item(i + array_size));
av_push(ret, newSVnv(x_i + y_i));
}
return ret;
}
EOC
my @num1 = (2.2, 3.3, 4.4,5.5);
my @num2 = (6.6, 7.7, 8.8, 9.9);
$arref = sum
(scalar(@num1), @num1, @num2);
use warnings;
use Inline C => <<'EOC';
AV * sum(SV * x, ...) {
Inline_Stack_Vars;
long array_size, i;
AV * ret = newAV();
array_size = SvUV(Inline_Stack_Item(0));
if (Inline_Stack_Items != (2 * array_size) + 1) {
croak("bad number of arguments supplied to sum");
}
// for efficiency, make the array big enough
av_extend(ret, array_size - 1);
for(i = 1; i <= array_size; i++) {
int x_i = SvIV(Inline_Stack_Item(i));
int y_i = SvIV(Inline_Stack_Item(i + array_size));
av_push(ret, newSViv(x_i + y_i));
}
return ret;
}
EOC
my @num1 = (-2, 3, -4, 5);
my @num2 = (6, -7, 8, -9);
$arref = sum
(scalar(@num1), @num1, @num2);
use warnings;
use Inline C => <<'EOC';
AV * sum(SV * x, ...) {
Inline_Stack_Vars;
long array_size, i;
// create array that will be returned to perl
AV * ret = newAV();
array_size = SvUV(Inline_Stack_Item(0));
if (Inline_Stack_Items != (2 * array_size) + 1) {
croak("bad number of arguments supplied to sum");
}
// for efficiency, make the array big enough
av_extend(ret, array_size - 1);
for(i = 1; i <= array_size; i++) {
unsigned long int x_i = SvUV(Inline_Stack_Item(i));
unsigned long int y_i = SvUV(Inline_Stack_Item(i + array_size));
av_push(ret, newSVuv(x_i + y_i));
}
return ret;
}
EOC
my @num1 = (2, 3, 4, 5);
my @num2 = (6, 7, 8, 9);
$arref = sum
(scalar(@num1), @num1, @num2);
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 :
use warnings;
use Inline C => <<'EOC';
AV * sum(SV * x, ...) {
Inline_Stack_Vars;
long array_size, i;
// create array that will be returned to perl
AV * ret = newAV();
array_size = SvUV(Inline_Stack_Item(0));
if (Inline_Stack_Items != (2 * array_size) + 1) {
croak("bad number of arguments supplied to sum");
}
// for efficiency, make the array big enough
av_extend(ret, array_size - 1);
for(i = 1; i <= array_size; i++) {
unsigned long int x_i = SvUV(Inline_Stack_Item(i));
unsigned long int y_i = SvUV(Inline_Stack_Item(i + array_size));
av_push(ret, newSVuv(x_i + y_i));
}
return ret;
}
EOC
my @num1 = (2, 3, 4, 5);
my @num2 = (6, 7, 8, 9);
$arref = sum
(scalar(@num1), @num1, @num2);
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.
#!/usr/bin/env perl
use strict;
use warnings;
#use Test::More;
#use List::Util qw(reduce);
my $group_bin = 0 x $_ . 1 x $_;
my $bin_length = $_ * 2;
my $mask_bin = $group_bin x ( 32 / $bin_length );
} 1, 2, 4, 8, 16;
sub popcount($) {
maskshift( $num, 0, 1 );
}
sub maskshift {
my ( $num, $mask_index, $length ) = @_;
my $mask = $masks[$mask_index];
my $sum = ( $num & $mask ) + ( $num >> $length & $mask );
if ( $mask_index < $#masks ) {
maskshift( $sum, $mask_index + 1, $length * 2 );
} else {
$sum;
}
}
my $x = 10000000;
my $i;
for ($i = 1; $i <= $x; ++$i) {
popcount($i);
}
Voici à présent la version qui utilise la fonction __builtin_popcount de gcc :
#!/usr/bin/env perl
use warnings;
use Inline C =><<'END_OF_C_CODE';
}
END_OF_C_CODE
my $x = 10000000;
my $i;
for ($i = 1; $i <= $x; ++$i) {
c_popcount($i);
}
Les résultats en terme de temps d'exécution en secondes figurent dans le tableau suivant :
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.
#!/usr/bin/perl
sub escapes {
my ($cr, $ci, $it) = @_;
my $zr = 0.0;
my $zi = 0.0;
for ($i = 0; $i < $it; ++$i) {
# z <- z^2 + c
my $zrtmp = $zr*$zr - $zi*$zi + $cr;
$zi = 2*$zr*$zi + $ci;
$zr = $zrtmp;
if (($zr*$zr + $zi*$zi) > 4) {
}
}
}
sub mandel {
my ( $xmin, $xmax, $xstep, $ymin, $ymax, $ystep, $iterations ) = @_;
for (my $yc = 0; $yc < $ystep; $yc++) {
my $y = $yc*($ymax-$ymin)/$ystep + $ymin;
for (my $xc = 0; $xc < $xstep; $xc++) {
my $x = $xc*($xmax-$xmin)/$xstep + $xmin;
print escapes
($x, $y, $iterations);
}
}
}
mandel(-2.0, 1.0, 256, -1.0, 1.0, 256, 100000);
Voici à présent la version qui utilise la fonction inline::C :
use warnings;
use Inline C => Config => CLEAN_AFTER_BUILD => 0,
CC => '/usr/bin/gcc',
CCFLAGS => '-fopenmp', BUILD_NOISY => 1,
LIBS => '-lgomp -lpthread -ldl -lm';
use Inline C =><<'END_OF_C_CODE';
#include <omp.h>
int escapes
(double cr
, double ci
, int it
) {
double zr = 0;
double zi = 0;
double zrtmp;
for(i=0; i<it; i++) {
// z <- z^2 + c
zrtmp = zr*zr - zi*zi + cr;
zi = 2*zr*zi + ci;
zr = zrtmp;
if (zr*zr + zi*zi > 4) {
}
}
}
void mandel
(double xmin
, double xmax
, int xstep
, double ymin
, double ymax
, int ystep
, int iters
) {
// array of string to store result
char
*m = (char
*) malloc
(ystep
* (xstep
+ 1) * sizeof
(char
));
#pragma omp parallel for
for(yc=0; yc<ystep; yc++) {
double
y = yc
*(ymax
-ymin
)/ystep
+ ymin
;
for(xc=0; xc<xstep; xc++) {
double x = xc*(xmax-xmin)/xstep + xmin;
if (escapes
(x
, y, iters
)) {
m[yc
* (xstep
+ 1) + xc
] = ' ';
} else {
m[yc
* (xstep
+ 1) + xc
] = 'X';
}
}
// add end of string
m[yc
* (xstep
+1) + xstep
] = '\0';
}
for(yc=0; yc<ystep; yc++) {
printf("%s\n", &m[yc
* (xstep
+1)]);
}
}
END_OF_C_CODE
mandel(-2.0, 1.0, 256, -1.0, 1.0, 256, 100000);
Les résultats en terme de temps d'exécution en secondes figurent dans le tableau suivant :
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
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
use Inline C => 'DATA', structs => ['Person'];
my $obj = Inline::Struct::Person->new;
$obj->name("toto");
$obj->age(10);
print Person_print
($obj), "\n";
__END__
__C__
struct Person {
char *name;
};
typedef struct Person Person;
SV *Person_print(Person *p) {
return newSVpvf
("name=%s, age=%d", p
->name, p
->age);
}
my $obj1 = Person->new('riri', 12);
my $obj2 = Person->new('fifi', 11);
my $obj3 = Person->new('loulou', 10);
for my $obj ($obj1, $obj2, $obj3) {
print $obj->get_name, " ", $obj->get_age, "\n";
}
#---------------------------------------------------------
use Inline C => <<'END';
typedef struct {
char* name;
int age;
} Person;
SV* new(char* class, char* name, int age) {
Person* p = malloc(sizeof(Person));
SV* obj_ref = newSViv(0);
SV* obj = newSVrv(obj_ref, class);
p->name = strdup(name);
p->age = age;
sv_setiv(obj, (IV)p);
SvREADONLY_on(obj);
return obj_ref;
}
char* get_name(SV* obj) {
return ((Person*)SvIV(SvRV(obj)))->name;
}
int get_age(SV* obj) {
return ((Person*)SvIV(SvRV(obj)))->age;
}
void DESTROY(SV* obj) {
Person* p = (Person*)SvIV(SvRV(obj));
free(p->name);
free(p);
}
END
3.6.2.g manipulation de classes C++
Pour manipuler des objets C++, on peut utiliser Inline::CPP :
sudo cpan -i Inline::CPP
use Inline 'C++';
my $obj1 = Person->new('riri', 12);
my $obj2 = Person->new('fifi', 11);
my $obj3 = Person->new('loulou', 10);
for my $obj ($obj1, $obj2, $obj3) {
print $obj->get_name, " ", $obj->get_age, "\n";
}
__END__
__C++__
class Person {
public:
Person
(char
*name, int age
);
char *get_name();
private:
char *name;
};
Person
::Person(char
*name, int age
) {
this->name = name;
this->age = age;
}
char *Person::get_name() {
}
}
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 :
#include <stdio.h>
#include <stdlib.h>
#include <omp.h>
int escapes(double cr, double ci, int it) {
double zr = 0;
double zi = 0;
double zrtmp;
int i;
for(i=0; i<it; i++) {
// z <- z^2 + c
zrtmp = zr*zr - zi*zi + cr;
zi = 2*zr*zi + ci;
zr = zrtmp;
if (zr*zr + zi*zi > 4) {
return 1;
}
}
return 0;
}
/**
* main function of the module
*/
void mandel(double xmin, double xmax, int xstep, double ymin, double ymax, int ystep, int iters) {
int yc;
// array of string to store result
char *m
= (char *) malloc(ystep
* (xstep
+ 1) * sizeof(char));
#pragma omp parallel for
for(yc=0; yc<ystep; yc++) {
double y = yc*(ymax-ymin)/ystep + ymin;
int xc;
for(xc=0; xc<xstep; xc++) {
double x = xc*(xmax-xmin)/xstep + xmin;
escapes(x, y, iters);
if (escapes(x, y, iters)) {
m[yc * (xstep + 1) + xc] = ' ';
} else {
m[yc * (xstep + 1) + xc] = 'X';
}
}
// add end of string
m[yc * (xstep+1) + xstep] = '\0';
}
for(yc=0; yc<ystep; yc++) {
printf("%s\n", &m
[yc
* (xstep
+1)]);
}
}
swig -perl5 -shadow -module mandelbrot mandelbrot_swig.c
la comande précédente génère les fichiers :
- mandelbrot_swig_wrap.c : wrapper
- mandelbrot.pm : module à charger, il faudra donc créer
une librairie dynamique libmandelbrot.so
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:
#!/usr/bin/env perl
use warnings;
use mandelbrot;
mandelbrot::mandel(-2.0, 1.0, 256, -1.0, 1.0, 256, 100000);
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.
#ifndef MANDELBROT_H
#define MANDELBROT_H
void mandel(double xmin, double xmax, int xstep, double ymin, double ymax, int ystep, int iters);
#endif
#include <stdio.h>
#include <stdlib.h>
#include <omp.h>
int escapes(double cr, double ci, int it) {
double zr = 0;
double zi = 0;
double zrtmp;
int i;
for(i=0; i<it; i++) {
// z <- z^2 + c
zrtmp = zr*zr - zi*zi + cr;
zi = 2*zr*zi + ci;
zr = zrtmp;
if (zr*zr + zi*zi > 4) {
return 1;
}
}
return 0;
}
void mandel(double xmin, double xmax, int xstep, double ymin, double ymax, int ystep, int iters) {
int yc;
// array of string to store result
char *m
= (char *) malloc(ystep
* (xstep
+ 1) * sizeof(char));
#pragma omp parallel for
for(yc=0; yc<ystep; yc++) {
double y = yc*(ymax-ymin)/ystep + ymin;
int xc;
for(xc=0; xc<xstep; xc++) {
double x = xc*(xmax-xmin)/xstep + xmin;
escapes(x, y, iters);
if (escapes(x, y, iters)) {
m[yc * (xstep + 1) + xc] = ' ';
} else {
m[yc * (xstep + 1) + xc] = 'X';
}
}
// add end of string
m[yc * (xstep+1) + xstep] = '\0';
}
for(yc=0; yc<ystep; yc++) {
printf("%s\n", &m
[yc
* (xstep
+1)]);
}
}
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 :
- -n Mandelbrot permet de spécifier le nom du module et crée en conséquence le répertoire correspondant
- -O permet de réécrire les fichiers s'ils existent déjà
- -x génération automatique de XSUBs basé sur les déclarations de fonction de mandelbrot.h
- -F `-I..' permet d'indiquer où trouver les fichiers .h
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
use warnings;
use Mandelbrot;
Mandelbrot::mandel(-2.0, 1.0, 256, -1.0, 1.0, 256, 100000);
3.6.5. Bibliographie / Sitographie