intérêt de boost::multi_array ?

intérêt de boost::multi_array ? - C++ - Programmation

Marsh Posté le 14-04-2007 à 17:24:39    

Bonjour,
 
J'ai tout un tas de données qui se stockent dans des tableaux pleins à plusieurs dimensions (disons par exemple : un prix qui dépend du pas de temps, du client, du fournisseur, du produit et du pays).
 
Solution C :
 

Code :
  1. double *****prix


 
Beurk. En outre il faut allouer et détruire, j'ai une vingtaine de types de données du même genre : impossible.
 
Solution STL :

Code :
  1. vector<vector<vector<vector<vector<double> > > > prix


 
Re-beurk. Allocation à peine plus chouette qu'en C.
 
Je précise qu'il y a plusieurs types de données différentes, certaines entières d'autres réelles, et pouvant dépendre de 0 à 5 dimensions. Mon exemple du prix ci-dessus correspond au "pire" cas.
 
Solution Boost :

Code :
  1. multi_array<double,5> prix


 
Bien ! Problème : j'ai fait quelques tests sous VC++ 2005 (toutes optimisations à fond, "checked iterators" désactivés). Je fait des boucles pour stocker et lire 10^8 fois soit a) un rand() b) une constante.
 
* Résultats sur les rand() :
 
En 1 dimension (10 éléments), STL ~ Boost.
En 2 dimensions (10*10 = 100 éléments), Boost 2 fois plus lent que STL.  :heink:  
 
* Résultats sur 1 constante :
 
En 1 dimension, Boost 2 fois plus lent que la STL. Là OK, un conteneur multi-dimensions n'est pas forcément super adapté pour un tableau simple.
En 2 dimensions, Boost 8 fois plus lent que la STL  :ouch:  :ouch: Bon, si je teste avec 2*10^6 éléments, l'écart se ressert (Boost 3 fois plus lent) mais bof quoi.
 
Donc je ne comprends pas. boost::multi_array me semblait justement fait pour moi : données dont je connais a priori le nombre de dimensions mais pas la taille de chaque dimension + pas envie d'écrire des horreurs comme vector<vector<vector<... Quel est l'intérêt s'il m'explose mon temps de calcul ? Je ne suis pas en train de me défouler dessus, hein, je voudrais vraiment savoir à quoi sert cette librairie, j'ai sûrement raté quelque chose. En plus je peux écrire :
 

Code :
  1. template<class T> struct Types
  2. {
  3.   typedef vector<vector<vector<vector<vector<T> > > > tab5Dim;
  4. };


 
et garder les perfs de la STL + une syntaxe relativement concise.


Message édité par boulgakov le 14-04-2007 à 17:26:16
Reply

Marsh Posté le 14-04-2007 à 17:24:39   

Reply

Marsh Posté le 14-04-2007 à 18:26:48    

Spa forcément la meilleure solution, mais pour te réduire l'écriture d'empilement de vecteurs tu peut faire ça :

 
Code :
  1. #include <vector>
  2. #include <iostream>
  3.  
  4. /* Template de base */
  5. template <typename T, int depth>
  6. class metavector : public std::vector<metavector<T, depth-1> > {};
  7. /* Specialisation partielle pour le dernier tableau */
  8. template <typename T>
  9. class metavector<T, 1> : public std::vector<T> {};
  10.  
  11. int main(void) {
  12. /* Tadam ! */
  13. metavector<double, 4> bidule;
  14. bidule.resize(1);
  15. bidule[0].resize(1);
  16. bidule[0][0].resize(1);
  17. bidule[0][0][0].resize(1);
  18. bidule[0][0][0][0] = 42.1337;
  19. std::cout << bidule[0][0][0][0] << std::endl;
  20. }
 

Evidemment faut voir si ton compilo supporte bien la spécialisation partielle, j'utilise que gcc ici, je peut pas test :/
Après peut-être que les valarray peuvent t'aider aussi...

Message cité 1 fois
Message édité par 0x90 le 14-04-2007 à 18:29:09

---------------
Me: Django Localization, Yogo Puzzle, Chrome Grapher, C++ Signals, Brainf*ck.
Reply

Marsh Posté le 14-04-2007 à 19:50:14    

Et en quoi le couple classe + conteneur classique serait inapproprié au problème ?

Reply

Marsh Posté le 14-04-2007 à 20:36:00    

el muchacho a écrit :

Et en quoi le couple classe + conteneur classique serait inapproprié au problème ?


 
Ce ne serait pas inapproprié à strictement parler, mais je trouverais quand même assez bizarre d'ajouter une classe à mon code qui encapsulerait juste un tableau multidimensionnel. Si les dimensions n'étaient connues qu'à l'exécution, je ne dis pas (je l'ai déjà fait), mais là... Ajouter des conteneurs propriétaires qui n'encapsulent que l'existant me paraît à éviter. Je n'aime pas trop tomber sur des codes où les gens ont commencé par redéfinir des class Queue et des Pile, par exemple.
 

0x90 a écrit :

Spa forcément la meilleure solution, mais pour te réduire l'écriture d'empilement de vecteurs tu peut faire ça :
 
[...]
 
Evidemment faut voir si ton compilo supporte bien la spécialisation partielle, j'utilise que gcc ici, je peut pas test :/
Après peut-être que les valarray peuvent t'aider aussi...


 
Pas mal du tout. Je n'ai jamais écrit de templates récursifs, je ne risquais pas de trouver ça tout seul. Ta solution m'évite aussi de définir les N types avec des noms comme 'tabNDims', 'tab(N-1)Dims', etc..., ce qui aurait été très moche. Merci !
 
Sinon, j'ai réfléchi et faut avouer qu'il y a quelques trucs pas mal dans boost::multi_array. Genre, on peut définir des vues sur un tableau. Par exemple "vue à 2 dimensions, où les indices 2,3 et 4 de mon tableau X à 5 dimensions sont fixés et où je ne prends que les éléments d'indices pairs sur la 5ème dimension" : comme les itérateurs sont définis sur les vues, c'est compatible avec les algos de la STL et je peux très bien trier ces éléments par ordre croissants en une ligne avec un std:sort, etc etc... Bref, l'intérêt est plutôt de se conformer au modèle de programmation générique de la STL, ce qui ne m'intéresse pas dans le cas présent.
 

Reply

Marsh Posté le 15-04-2007 à 02:34:41    

boulgakov a écrit :

Ce ne serait pas inapproprié à strictement parler, mais je trouverais quand même assez bizarre d'ajouter une classe à mon code qui encapsulerait juste un tableau multidimensionnel. Si les dimensions n'étaient connues qu'à l'exécution, je ne dis pas (je l'ai déjà fait), mais là... Ajouter des conteneurs propriétaires qui n'encapsulent que l'existant me paraît à éviter. Je n'aime pas trop tomber sur des codes où les gens ont commencé par redéfinir des class Queue et des Pile, par exemple.


Non, ce que je dis, c'est qu'à première vue, si on prend ton exemple de tuple hétérogène (un prix qui dépend du pas de temps, du client, du fournisseur, du produit et du pays), on peut/doit faire une classe de ça. Et on met les objects instanciés dans un conteneur standard.

Reply

Marsh Posté le 15-04-2007 à 03:45:56    

el muchacho a écrit :

Non, ce que je dis, c'est qu'à première vue, si on prend ton exemple de tuple hétérogène (un prix qui dépend du pas de temps, du client, du fournisseur, du produit et du pays), on peut/doit faire une classe de ça. Et on met les objects instanciés dans un conteneur standard.


 
Euh... bah non. J'ai pas dû être clair. C'est un code numérique, hein, j'ai un peu instancié pour que ça parle aux gens mais mon temps, mes clients,... ce sont juste des indices entiers. Pourquoi je créerais une classe ?
 

Code :
  1. class Prix {
  2. private :
  3.   metavector<double, 5> valeurs;
  4. public :
  5.   getPrix(const unsigned int client, const unsigned int fournisseur, ...)
  6.     {
  7.       return valeurs[client][fournisseur]...;
  8.     }
  9. };


 
Pas super utile, voire même nuisible.
 

Reply

Marsh Posté le 15-04-2007 à 13:01:31    

Mais non. C'est du n'importe quoi, là.  
 
Ce à quoi je pensais, c'était plutôt qq chose comme:

Code :
  1. class truc {
  2.   int client, fournisseur, produit, pays;
  3. }
  4. vector<pair<int prix, truc maStruc> >


par exemple.
 
Mais si ça se trouve, ça n'est pas ce que tu veux faire. Tu veux p-ê faire des recherches sur d'autres colonnes ? Pour l'instant, tout ce que je vois, c'est un problème extrêmement mal modélisé, l'équivalent d'une base de données avec toutes les données en bordel dans une seule table, et donc une solution qui ne marchera probablement pas avec un nombre conséquent de données.
Mais comme tu n'es pas disert sur ce que tu veux réellement faire, on ne peut pas vraiment aider, on ne peut que faire des supputations.


Message édité par el muchacho le 15-04-2007 à 13:01:55
Reply

Marsh Posté le 15-04-2007 à 13:07:14    

On peut avoir ton code de benchmark ?

Reply

Marsh Posté le 15-04-2007 à 13:48:59    

Taz a écrit :

On peut avoir ton code de benchmark ?


 
Ouaip, avec plaisir, si ça se trouve je tire les mauvaises conclusions du mauvais test. J'ai la flemme de mettre au propre, par contre. Le test 1 dim et le test 2 dims se suivent dans le main(). En 2 dimensions, je sais que t'attaque toujours la diagonale de la matrice ce qui pourrait être un biais, mais j'ai fait quelques variantes et cela ne change rien.
 

Code :
  1. #include <iostream>
  2. #include <ctime>
  3. #include <vector>
  4. #include <exception>
  5. #include <algorithm>
  6. #include <cstdlib>
  7. #include "boost/multi_array.hpp"
  8. using namespace boost;
  9. using namespace std;
  10. const unsigned int NB_TESTS = 10;
  11. const long unsigned int NB_ITERATIONS = 10000000;
  12. const unsigned int LONGUEUR = 10;
  13. double recipient = 0.0;
  14. // Test 1 dim
  15. template<typename T> double test(T &tableau)
  16. {
  17. long unsigned int i = 0;
  18. unsigned int indiceTab = 0;
  19. time_t time1 = clock();
  20. for ( i = 0; i < NB_ITERATIONS; i++ )
  21. {
  22.  indiceTab = i % LONGUEUR;
  23.  tableau[indiceTab] = 10.0; // Ecriture
  24.  recipient = tableau[indiceTab]; // Lecture
  25. }
  26. time_t time2 = clock();
  27. return (time2 - time1) / 1000.0;
  28. }
  29. // Test 2 dim
  30. template<typename T> double test2(T &tableau)
  31. {
  32. long unsigned int i = 0;
  33. unsigned int indiceTab = 0;
  34. time_t time1 = clock();
  35. for ( i = 0; i < NB_ITERATIONS; i++ )
  36. {
  37.  indiceTab = i % LONGUEUR;
  38.  tableau[indiceTab][indiceTab] = 10.0; // Ecriture
  39.  recipient = tableau[indiceTab][indiceTab]; // Lecture
  40. }
  41. time_t time2 = clock();
  42. return (time2 - time1) / 1000.0;
  43. }
  44. int main()
  45. {
  46. try
  47. {
  48.  srand( (unsigned)time( NULL ) );
  49.  cout << " *** Tests 1 Dimension " << endl << endl;
  50.  vector<vector<double>> resultats(3);
  51.  multi_array<double, 1> tabBoost(extents[LONGUEUR]);
  52.  vector<double> tabSTL(LONGUEUR);
  53.  double *tabC = new double[LONGUEUR];
  54.  for (unsigned int iTest = 0; iTest < NB_TESTS; iTest++ )
  55.  {
  56.   double resSTL = test(tabSTL);
  57.   double resC = test(tabC);
  58.   double resBoost = test(tabBoost);
  59.   resultats[0].push_back(resSTL);
  60.   resultats[1].push_back(resC);
  61.   resultats[2].push_back(resBoost);
  62.   cout << "STL : " << resSTL << " s." << endl;
  63.   cout << "C : " << resC << " s." << endl;
  64.   cout << "multiarray : " << resBoost << " s." << endl;
  65.   cout << endl;
  66.  }
  67.  delete [] tabC;
  68.  cout << "Moyenne STL : " << accumulate(resultats[0].begin(), resultats[0].end(), 0.0) / NB_TESTS << endl;
  69.  cout << "Moyenne C : " << accumulate(resultats[1].begin(), resultats[1].end(), 0.0) / NB_TESTS << endl;
  70.  cout << "Moyenne Boost : " << accumulate(resultats[2].begin(), resultats[2].end(), 0.0) / NB_TESTS << endl;
  71.  cout << endl << " *** Tests 2 Dimensions " << endl;
  72.  vector<vector<double> >  resultats2(3);
  73.  multi_array<double, 2> tabBoost2(extents[LONGUEUR][LONGUEUR]);
  74.  vector<vector<double> > tabSTL2(LONGUEUR, vector<double>(LONGUEUR));
  75.  double **tabC2 = new double *[LONGUEUR];
  76.  for ( unsigned int l = 0; l < LONGUEUR; l++ )
  77.  {
  78.   tabC2[l] = new double[LONGUEUR];
  79.  }
  80.  for (unsigned int iTest2 = 0; iTest2 < NB_TESTS; iTest2++ )
  81.  {
  82.   double resSTL2 = test2(tabSTL2);
  83.   double resC2 = test2(tabC2);
  84.   double resBoost2 = test2(tabBoost2);
  85.   resultats2[0].push_back(resSTL2);
  86.   resultats2[1].push_back(resC2);
  87.   resultats2[2].push_back(resBoost2);
  88.   cout << "STL : " << resSTL2 << " s." << endl;
  89.   cout << "C : " << resC2 << " s." << endl;
  90.   cout << "multiarray : " << resBoost2 << " s." << endl;
  91.   cout << endl;
  92.  }
  93.  cout << "Moyenne STL : " << accumulate(resultats2[0].begin(), resultats2[0].end(), 0.0) / NB_TESTS << endl;
  94.  cout << "Moyenne C : " << accumulate(resultats2[1].begin(), resultats2[1].end(), 0.0) / NB_TESTS << endl;
  95.  cout << "Moyenne Boost : " << accumulate(resultats2[2].begin(), resultats2[2].end(), 0.0) / NB_TESTS << endl;
  96.  for ( unsigned int l = 0; l < LONGUEUR; l++ )
  97.  {
  98.   delete [] tabC2[l];
  99.  }
  100.  delete [] tabC2;
  101. }
  102. catch(exception &e)
  103. {
  104.  cout << e.what() << endl;
  105. }
  106. }


 
Edit - Les résultats sur les 2 dimensions :
 
**
Moyenne STL : 0.0972
Moyenne C : 0.0828
Moyenne Boost : 1.0731
**


Message édité par boulgakov le 15-04-2007 à 13:53:26
Reply

Marsh Posté le 15-04-2007 à 14:52:24    

Code :
  1. #include <iostream>
  2. #include <ctime>
  3. #include <vector>
  4. #include <exception>
  5. #include <algorithm>
  6. #include <cstdlib>
  7. #include <boost/multi_array.hpp>
  8. #include <boost/timer.hpp>
  9. using namespace boost;
  10. using namespace std;
  11. const unsigned int NB_TESTS = 100;
  12. const long unsigned int NB_ITERATIONS = 10000000;
  13. const unsigned int LONGUEUR = 10;
  14. template<typename Array>
  15. double do_test(Array &a, double (&f)(Array &a))
  16. {
  17.   timer t;
  18.   for (unsigned i = 0; i != NB_TESTS; ++i)
  19.     f(a);
  20.   return t.elapsed();
  21. }
  22. // Test 1 dim
  23. template<typename T> double test(T &tableau)
  24. {
  25.   timer t;
  26.   long unsigned int i = 0;
  27.   unsigned int indiceTab = 0;
  28.   for ( i = 0; i < NB_ITERATIONS; i++ )
  29.     {
  30.       indiceTab = i % LONGUEUR;
  31.       tableau[indiceTab] *= 10.0;
  32.     }
  33.   return t.elapsed();
  34. }
  35. // Test 2 dim
  36. template<typename T> double test2(T &tableau)
  37. {
  38.   timer t;
  39.   long unsigned int i = 0;
  40.   unsigned int indiceTab = 0;
  41.   for ( i = 0; i < NB_ITERATIONS; i++ )
  42.     {
  43.       indiceTab = i % LONGUEUR;
  44.       tableau[indiceTab][indiceTab] *= 10.0;
  45.     }
  46.   return t.elapsed();
  47. }
  48. int main()
  49. {
  50.   try
  51.     {
  52.       srand( (unsigned)time( NULL ) );
  53.       multi_array<double, 1> tabBoost(extents[LONGUEUR]);
  54.       vector<double> tabSTL(LONGUEUR);
  55.       double *tabC = new double[LONGUEUR];
  56.       cout << "[1] STL   " << do_test(tabSTL, test) << '\n'
  57.    << "[1] C     " << do_test(tabC, test) << '\n'
  58.    << "[1] Boost " << do_test(tabBoost, test) << '\n';
  59.       delete [] tabC;
  60.       multi_array<double, 2> tabBoost2(extents[LONGUEUR][LONGUEUR]);
  61.       vector<vector<double> > tabSTL2(LONGUEUR, vector<double>(LONGUEUR));
  62.       double **tabC2 = new double *[LONGUEUR];
  63.       for ( unsigned int l = 0; l < LONGUEUR; l++ )
  64. {
  65.   tabC2[l] = new double[LONGUEUR];
  66. }
  67.       cout << "[2] STL   " << do_test(tabSTL2, test2) << '\n'
  68.    << "[2] C     " << do_test(tabC2, test2) << '\n'
  69.    << "[2] Boost " << do_test(tabBoost2, test2) << '\n';
  70.       for ( unsigned int l = 0; l < LONGUEUR; l++ )
  71. {
  72.   delete [] tabC2[l];
  73. }
  74.       delete [] tabC2;
  75.     }
  76.   catch(exception &e)
  77.     {
  78.       cout << e.what() << endl;
  79.     }
  80. }

en nettoyant un peu et en simplifiant :
[1] STL   15.89
[1] C     15.88
[1] Boost 18.51
[2] STL   21.45
[2] C     20.45
[2] Boost 24.43

Reply

Marsh Posté le 15-04-2007 à 14:52:24   

Reply

Marsh Posté le 15-04-2007 à 14:58:27    

et pour la petite histoire, avec une allocation dynamique de tableau (vrai tableau contigüe), ça donne :
[2] STL   21.44
[2] C     20.42
[2] Ca    19.44
[2] Boost 23.51

Reply

Marsh Posté le 15-04-2007 à 15:20:59    

petite analyse de l'assembler ppc du code précédent, et plus précisément sur la boucle :
 

Code :
  1. _Z4testIN5boost11multi_arrayIdLj1ESaIdEEEEdRT_
  2. double test<boost::multi_array<double, 1u, std::allocator<double> > >(boost::multi_array<double, 1u, std::allocator<double> >&
  3. 882 .L141:
  4. 883         mulhwu 0,9,10
  5. 884         srwi 0,0,3
  6. 885         mulli 0,0,10
  7. 886         subf 0,0,9
  8. 887         addi 9,9,1
  9. 888         mullw 0,0,8
  10. 889         slwi 0,0,3
  11. 890         lfdx 0,11,0
  12. 891         fmul 0,0,13
  13. 892         stfdx 0,11,0
  14. 893         bdnz .L141
  15. _Z4testIPdEdRT_
  16. test<double*>(double*& )
  17. 1392 .L207:
  18. 1393         mulhwu 9,11,0
  19. 1394         srwi 9,9,3
  20. 1395         mulli 9,9,10
  21. 1396         subf 9,9,11
  22. 1397         addi 11,11,1
  23. 1398         slwi 9,9,3
  24. 1399         lfdx 0,29,9
  25. 1400         fmul 0,0,13
  26. 1401         stfdx 0,29,9
  27. 1402         bdnz .L207
  28. _Z4testISt6vectorIdSaIdEEEdRT
  29. double test<std::vector<double, std::allocator<double> > >(std::vector<double, std::allocator<double> >& )
  30. 1528 .L219:
  31. 1529         mulhwu 9,11,0
  32. 1530         srwi 9,9,3
  33. 1531         mulli 9,9,10
  34. 1532         subf 9,9,11
  35. 1533         addi 11,11,1
  36. 1534         slwi 9,9,3
  37. 1535         lfdx 0,29,9
  38. 1536         fmul 0,0,13
  39. 1537         stfdx 0,29,9
  40. 1538         bdnz .L219
  41. double test2<boost::multi_array<double, 2u, std::allocator<double> > >(boost::multi_array<double, 2u, std::allocator<double> >& )
  42. _Z5test2IN5boost11multi_arrayIdLj2ESaIdEEEEdRT
  43. 1669 .L231:
  44. 1670         mulhwu 0,10,6
  45. 1671         srwi 0,0,3
  46. 1672         mulli 9,0,10
  47. 1673         subf 0,9,10
  48. 1674         addi 10,10,1
  49. 1675         mullw 9,0,7
  50. 1676         mullw 11,0,5
  51. 1677         slwi 9,9,3
  52. 1678         slwi 11,11,3
  53. 1679         add 9,8,9
  54. 1680         lfdx 0,9,11
  55. 1681         fmul 0,0,13
  56. 1682         stfdx 0,9,11
  57. 1683         bdnz .L231
  58. double test2<double (*) [10]>(double (*& ) [10])
  59. _Z5test2IPA10_dEdRT_
  60. 1809 .L243:
  61. 1810         mulhwu 0,10,8
  62. 1811         srwi 0,0,3
  63. 1812         mulli 9,0,10
  64. 1813         subf 0,9,10
  65. 1814         addi 10,10,1
  66. 1815         mulli 9,0,80
  67. 1816         slwi 11,0,3
  68. 1817         add 9,29,9
  69. 1818         lfdx 0,11,9
  70. 1819         fmul 0,0,13
  71. 1820         stfdx 0,11,9
  72. 1821         bdnz .L243
  73. double test2<double**>(double**& )
  74. _Z5test2IPPdEdRT_
  75. 1947 .L255:
  76. 1948         mulhwu 0,8,7
  77. 1949         srwi 0,0,3
  78. 1950         mulli 9,0,10
  79. 1951         subf 0,9,8
  80. 1952         addi 8,8,1
  81. 1953         slwi 9,0,2
  82. 1954         slwi 10,0,3
  83. 1955         lwzx 11,9,29
  84. 1956         lfdx 0,10,11
  85. 1957         fmul 0,0,13
  86. 1958         stfdx 0,10,11
  87. 1959         bdnz .L255
  88. double test2<std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > > >(std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > >& )
  89. _Z5test2ISt6vectorIS0_IdSaIdEESaIS2_EEEdRT_
  90. 2086         mulhwu 0,8,7
  91. 2087         srwi 0,0,3
  92. 2088         mulli 9,0,10
  93. 2089         subf 0,9,8
  94. 2090         addi 8,8,1
  95. 2091         mulli 9,0,12
  96. 2092         slwi 10,0,3
  97. 2093         lwzx 11,9,29
  98. 2094         lfdx 0,10,11
  99. 2095         fmul 0,0,13
  100. 2096         stfdx 0,10,11
  101. 2097         bdnz .L267


 
On constate que les codes sont extrêment voisins quand ils ne sont pas rigoureusement identiques. La seule différence est sur le calcul d'index : tantôt des shift quand c'est possible, tantôt des multiplication.
 
Dans le test a 2D, cette différence slwi/mulli est la seule entre les méthodes STL et et C. La différence de vitesse de cette instruction se sent sur 10**8 exécutions.
 
Pour moi la différence est faible, le plus lent l'étant de 20% par rapport au plus rapide.

Reply

Marsh Posté le 15-04-2007 à 16:13:19    

Taz a écrit :

en nettoyant un peu et en simplifiant :
[1] STL   15.89
[1] C     15.88
[1] Boost 18.51
[2] STL   21.45
[2] C     20.45
[2] Boost 24.43


 
J'ai dû instancier explicitement les fonctions pour que VC++ avale ton code
 

Code :
  1. do_test(tabSTL, test<vector<double>> )
  2. etc etc


 
et j'obtiens :
 
[1] STL   9.985
[1] C     68.796  :heink: (Là, j'ai relancé plusieurs fois et même installé le "Service Pack" en désespoir de cause mais rien n'y fait)
[1] Boost 17.375
[2] STL   11.515
[2] C     10.235
[2] Boost 43.515
 
Conclusion 1 : mon problème vient du compilo, pas de multi_array. Conclusion 2 : j'ai tous les éléments et je n'ai plus qu'à prendre une option, on verra ça plus tard. Il fait quand même un poil beau dehors, je vais pas passer mon WE là-dessus.  
 
Merci  bcp !

Reply

Sujets relatifs:

Leave a Replay

Make sure you enter the(*)required information where indicate.HTML code is not allowed