Journal Si si, le C++ peut parfois être plus rapide que le C

Posté par . Licence CC by-sa
2
21
nov.
2013

C'est trolldi tout bientôt, et je vous partage ce journal bookmark qui parle de C++;

http://akrzemi1.wordpress.com/2013/11/18/type-erasure-part-i/

Un article qui parle de type-erasure, donc à la base rien à voir avec une question de bench ou autre. Cependant, le deuxième exemple qui m'a fait tiquer par rapport à ce que ressortent souvent les dev' C qui n'y connaissent (généralement) rien au C++: oui, parfois le C++ peut être tout aussi performant—voir plus que le C.

Mais comme qui dirait, on s'en fout—ce qui compte pour la plupart des applis, c'est de pouvoir les relire facilement.

  • # Oups

    Posté par . Évalué à -5.

    désolé pour les tags

  • # heuuuu

    Posté par . Évalué à 5. Dernière modification le 21/11/13 à 23:09.

    int c_bigger (const void * a, const void * b)
    {
      if (*(int*)a > *(int*)b)      return -1;
      else if (*(int*)a < *(int*)b) return 1;
      else                          return 0;
    }

    ???

    int c_bigger (const void * a, const void * b)
    {
      return *(int*)a - *(int*)b;
    }

    C'est trop mainstream ? L'auteur de l'article à raison (si l'on ne considère pas la compilation C à coup de LTO), mais du code crade n'a rien à foutre dans un bench. Parce que comme l'auteur de fait bien remarquer, le compilateur C ne sait pas que c'est une fonction qui doit juste retourner un nombre négatif si a est plus grand que b, un nombre nul s'ils sont égaux, et un nombre positifs sinon. Il compile bêtement ce code bête, pleins de sauts, et ça rame.

    On parle des devs C++ qui ne connaissent pas le C et qui balancent de la merde dessus ?

    ajout : Je suis quand même surpris d'une différence de perfs. Ça mériterait vraiment de vrais tests…

    Please do not feed the trolls

    • [^] # Re: heuuuu

      Posté par (page perso) . Évalué à 10.

      c'est un peu du pinaillage mais les deux versions ne sont pas strictement equivalentes puisque le std C ne dit pas si les int signés wrappent ou pas (je sais pas comment le dire en francais, enfin on se comprend) en cas d'overflow. Du coup c_bigger(INT_MIN, 1) est indefini.

      • [^] # Re: heuuuu

        Posté par . Évalué à 5.

        Les version C et C++ ne sont surtout pas du tout équivalentes:

        int c_bigger (const void * a, const void * b)
        {
          if (*(int*)a > *(int*)b)      return -1;
          else if (*(int*)a < *(int*)b) return 1;
          else                          return 0;
        }
        

        et

        auto cpp_bigger = { return a > b; }

        c_bigger(1, 2) => 1 alors que cpp_bigger(1, 2) => 0
        c_bigger(2, 1) => -1 alors que cpp_bigger(2, 1) => 1

        Il n'y a que pour les cas d'égalité (pas gérés pas la version c++ d'ailleurs…) que les deux fonctions retournent 0.

        Bref, c'est tout pourri comme comparaison.

        Accessoirement, puisque c'est le compilateur qui fait la résolution grâce au typage statique, ça veut dire que le développeur peut aussi le faire en C même si ça va finir par faire du code sans doute moins générique et élégant.

        Mes 2 centimes.

        • [^] # Re: heuuuu

          Posté par . Évalué à 1.

          Les version C et C++ ne sont surtout pas du tout équivalentes:

          Sans blague. L'interface requise pour bsearch() et std::equal_range() n'est pas du tout la même. Donc les fonctions ne font pas la même chose. T'en a d'autres comme ça des portes ouvertes à enfoncer ?

          La plupart des algorithmes de la bibliothèque standard réclament une fonction de comparaison qui introduit un ordre strict faible. Pour les non-matheux, ça veut dire une fonction booléene compare(a,b) qui doit retourner vrai si a est strictement en dessous de b. On teste l'égalité trivialement avec not compare(a,b) and not compare(b,a), même si en pratique, c'est souvent testé en même temps que les inégalités à coup de branches du style :

          if (compare(a,b)) { /* a en dessous de b */ }
          else if (compare(b,a)) { /* a au dessus de b */ }
          else { /* a équivalent à b */ }

          Par défaut, ça utilise std::less (donc a<b). Là le monsieur doit avoir un tableau d'entiers trié par ordre décroissant, et à choisi de ré-implémenter std::greater plutôt que d'utiliser celui qui est dans la bibliothèque standard.

      • [^] # Commentaire supprimé

        Posté par . Évalué à 9.

        Ce commentaire a été supprimé par l'équipe de modération.

        • [^] # Commentaire supprimé

          Posté par . Évalué à 3. Dernière modification le 22/11/13 à 15:14.

          Ce commentaire a été supprimé par l'équipe de modération.

          • [^] # Re: heuuuu

            Posté par (page perso) . Évalué à 4.

            Là maintenant les overflow sont défini. Mais il sont définitivement faux :-). c_bigger(INT_MIN, 1) va être positif alors que 1 est plus grand que INT_MIN.

            (Tu peux aussi « maîtriser » l'overflow en C si tu utilise des unsigned mais ça n'aide pas)

  • # Il serait peut-être temps d'utiliser des langages modernes

    Posté par (page perso) . Évalué à 6.

    C'est moi ou ce que cet article décrit n'est rien d'autre qu'une implémentation hackish au possible du polymorphisme paramétrique ?

    Genre, ML le faisait dans les années 70…

    « Mauvais langage, changer langage ? »

    • [^] # Re: Il serait peut-être temps d'utiliser des langages modernes

      Posté par (page perso) . Évalué à 0.

      Non.

      Je ne savais pas ce qu'était le polymorphisme paramétrique, donc j'ai recherché. Mais apparemment, ça correspond juste aux fonction templates ou aux overloads en C++ qui sont de base dans le langage.

      Ce que cet article essaye de décrire (assez mal je trouve) c'est le « type erasure ». Mais on est sur un blog dédié au C++, il faut s'attendre à du C++ porn.
      En pratique, les développeurs utilisent simplement QVariant ou boost::any pour faire du « type erasure » sans même avoir besoin de connaître le terme.

      • [^] # Re: Il serait peut-être temps d'utiliser des langages modernes

        Posté par . Évalué à 2.

        En pratique, les développeurs utilisent simplement QVariant ou boost::any pour faire du « type erasure » sans même avoir besoin de connaître le terme

        Ou même le bon vieux "union", pour ceux qui ont fait un peu de C, ou qui veulent quand même ne pas effacer complètement le type.

        • [^] # Re: Il serait peut-être temps d'utiliser des langages modernes

          Posté par (page perso) . Évalué à 3.

          Sauf que le bon vieux union il ne marche que avec des type triviaux (sans constructeur ni destructeur) en C++03.
          En C++11 il y a les union généralisés qui aident beaucoup mais il faut quand même gérer le destructeur sois même. D'où l'interêt de boost::variant

      • [^] # Re: Il serait peut-être temps d'utiliser des langages modernes

        Posté par (page perso) . Évalué à 8.

        Les overloads sont justement l'antithèse du polymorphisme paramétrique, ce qu'on appelle aussi polymorphisme ad-hoc, parce que les fonctions peuvent faire des choses très différentes selon leur type. Les templates présentent le problème cité dans l'article, à savoir une duplication de code à la compilation.

        Pour prendre un équivalent de la fonction bsearch en ML, on pourrait imaginer une fonction

        bsearch: 'a -> 'a list -> ('a -> 'a -> bool) -> 'a
        

        où le premier 'a correspond à key, le 'a list correspond à base, la fonction ('a -> 'a -> bool) à compare et le derner 'a est le type retourné.

        Ici, point de duplication de code, il n'y a qu'une seule fonction créée à la compilation. Le 'a joue ici le rôle du void* effacé, i.e. c'est un type universellement quantifié, sauf qu'en plus on a la type-safety au niveau des contraintes. Le code se comporte de manière uniforme sur tout les types, au contraire de l'overloading.

        • [^] # Re: Il serait peut-être temps d'utiliser des langages modernes

          Posté par . Évalué à 6.

          Le problème de la duplication de code de C++ venait des compilateurs qui créait une fonction à chaque instance paramétrique. Dans les faits, il y a seulement une poignée de types différents utilisés pour un template donné. Un code spécialisé est tout de même beaucoup plus rapide qu'un code universel comme le génère ocaml, en tout cas pour des objets simples.

          "La première sécurité est la liberté"

          • [^] # Re: Il serait peut-être temps d'utiliser des langages modernes

            Posté par . Évalué à 3.

            La comparaison est inégale au vu des années-effort mises dans la réduction et spécialisation des montages de code produites par un compilateur C++, en comparaison au peu de sophistication (relativement) de l'inlining/duplication côté compilateur OCaml. Déjà aujourd'hui avec le boxing autour de std::function il ne me semble pas du tout clair que la stratégie de compilation de OCaml soit perdante. Et ce, en partant d'un langage plus simple, plus élégant et plus uniforme.

            • [^] # Re: Il serait peut-être temps d'utiliser des langages modernes

              Posté par . Évalué à 1.

              Ocaml est très bon, par rapport au peu d'effort sur sa compilation. Mais il pourrait faire bien mieux, justement parce que le langage comporte plus d'info pour le compilo. Il pourrait faire bien plus de spécialisation et d'inlining.

              Les codes purement calculatoires ne sont pas connu pour être très rapide en ocaml, car le boxing fait souvent mal.

              Le code généré passe trop souvent par la mémoire, au lieu d'utiliser les registres plus rapides. On dirait qu'il n'y a pas de scheduling statique des instructions pour éloigner les dépendances read-after-write, ou de déroulage de boucle, pour que le code soit encore plus parallélisable (gcc déroule les boucles 16 fois, 4 fois permet souvent d'utiliser uniquement les registres, tout en restant compacte).

              "La première sécurité est la liberté"

              • [^] # Re: Il serait peut-être temps d'utiliser des langages modernes

                Posté par . Évalué à 3.

                En Haskell, tu peux faire tout ça, et c’est fait dans les codes numériques, mais ça se fait à coup d’annotations (pour l’unboxing, l’inlining ou le déroulage), de template haskell (pour le déroulage), ou en changeant de backend (-fllvm pour les registres). Heureusement, beaucoup de ce genre de code est dans des bibliothèques déjà prêtes (vector pour l’unboxing).

              • [^] # Re: Il serait peut-être temps d'utiliser des langages modernes

                Posté par (page perso) . Évalué à 2.

                Concernant la spécialisation et l'inlining, tout dépend du contexte en réalité. Imaginons une fonction max de type 'a -> 'a -> 'a (puisque l'opérateur (>) est polymorphe). Puisqu'en OCaml un fichier est considéré comme une unité de la compilation ou plutôt un module, même dans le cas où le seul cas d'utilisation serait avec des entiers, comme tu le soulignes, le code généré d'OCaml ne sera pas très performant (pas de spé. et pas d'inlining). Pourquoi ? Tout simplement parce que le module a été défini avec un max polymorphe - et nous pouvons imaginer une tout autre utilisation de cette fonction dans un autre module avec des float.

                Dans une autre situation avec la même fonction mais dont sa définition est local (restant donc interne au module), OCaml peut se permettre (dans le cas de l'utilisation de la fonction avec des int uniquement) de faire une spécialisation est de l'inlining. Mais le premier cas peut toujours être optimisé en forçant le typage de max avec des int.

                Je pense que, comme le dit gasche à juste titre, ce n'est qu'une question de temps et d'approbation des différentes optimisations en évitant que celles-ci changent sémantiquement le code. Haskell a opté pour une utilisation de LLVM-IR (un projet OCaml utilise aussi LLVM, ça doit être dans GitHub). Je sais pas si c'est un choix judicieux (Haskell a été pas mal intrusif sur LLVM-IR selon moi) mais de toute façon, j'ai un PC i7 avec 16 Go de RAM :D !

  • # Commentaire supprimé

    Posté par . Évalué à 4.

    Ce commentaire a été supprimé par l'équipe de modération.

    • [^] # Re: C - inline - restrict

      Posté par (page perso) . Évalué à 3.

      Comme il n'y pas d'écritures dans le tableau je pense que le restrict n'apporte rien dans ce cas précis

      • [^] # Commentaire supprimé

        Posté par . Évalué à 2. Dernière modification le 22/11/13 à 11:45.

        Ce commentaire a été supprimé par l'équipe de modération.

        • [^] # Re: C - inline - restrict

          Posté par . Évalué à 2.

          Après, avec les inlining etc., il est possible que cela ait un effet sur la fonction englobante bsearch (pour laquelle le param key n'est naturellement pas défini en restrict).

          Si le param key n'est pas défini en restrict mais que tu a le droit de considérer qu'il l'est, j'ai du mal à comprendre pourquoi le code n'est pas considéré comme étant invalide.

          Dans tout les cas, si la fonction de tri se fait assez inliner et que la valeur à chercher ne sort pas trop d'un coin paumé ou d'une arithmétique de pointeur foireuse, le compilo va s'en rendre compte tout seul qu'il n'y a pas d'aliasing possible. En fait je vois même pas l'intérêt de mettre du restrict ou du const sur une fonction qui va se faire inliner la tronche.

          Dans tous les cas, effet du restrict ou pas, les perfs resteront supérieures ou égales au code c++. Le restrict est juste une petite indication supplémentaire au compilo mais cela n'est pas le coeur de la solution, celui-ci étant l'inlining.

          Il va falloir m'expliquer comment la perf dans ce cas pourrai être supérieure au code C++. Que ça soit égal, c'est ce à quoi je m'attend, mais supérieure ?

  • # la performance par l'inlining

    Posté par (page perso) . Évalué à 3.

    Si j'ai bien compris, le gain de performance dont tu parles est lié au fait que le code est inliné à la compilation au lieu de devoir passer par un pointeur de fonction à l'exécution. Outre le fait que ça prend plus de temps à compiler (on a beau dire, ça devient vite pénible), il y a le fait que ça augmente la taille du code et met donc plus de pressions sur le(s) cache(s) et au final peut ralentir l'exécution d'un programme complexe (ce qui ne se verra pas forcément dans un micro benchmark).

    pertinent adj. Approprié : qui se rapporte exactement à ce dont il est question.

    • [^] # Re: la performance par l'inlining

      Posté par . Évalué à 5.

      " il y a le fait que ça augmente la taille du code et met donc plus de pressions sur le(s) cache(s) et au final peut ralentir l'exécution d'un programme complexe (ce qui ne se verra pas forcément dans un micro benchmark)."

      Il faut arreter avec ce genre d'argument tarte à la crème. La vrai réponse est "cela dépend".

      Quand le code est inliné, il y a toutes les passes de simplification qui repassent dessus et le code peut être plus petit. En gros, un peu d'inlining réduit la taille du code, jusqu'à un certain point, ou il faut arreter d'inliner.

      "La première sécurité est la liberté"

      • [^] # Commentaire supprimé

        Posté par . Évalué à 2.

        Ce commentaire a été supprimé par l'équipe de modération.

        • [^] # Re: la performance par l'inlining

          Posté par . Évalué à 2.

          L'inlining sert surtout à virer des branches et des cas impossibles. L'exemple typique c'est la fonction qui vérifie ses paramètres et qui renvoie une erreur lorsqu'ils sont incorrects, alors que le code appelant ne passe que des paramètres corrects. Points bonus quand le code appelant vérifie la valeur de retour.

        • [^] # Re: la performance par l'inlining

          Posté par . Évalué à 2.

          Non, tu te trompes lourdement. Il y a plein de cas ou l'inlining est plus petit. Un getter/setter inliné peut réduire le code de la fonction appelante, alors qu'un call prend 2 instructions. C'est vrai pour chaque fonction appelante.

          Tu as aussi les cas à plusieurs branches, où la simplification s’arrête faute d'information, si la propagation de constante continue, une partie entière de code disparait.

          J'ai aussi le cas de constante composé à coup de "PLOP | PLIP" qui ne font que changer une constante de code, au lieu d'un appel de fonction, avec sauvegarde des registres.

          Ton postulat faux de base est que l'inline ajoute toujours du code, et que dans le bilan il faut retirer le code d'appel et tenir compte du code de la fonction elle-même. Mais dans les faits, pour certaine fonctions, cela simplifie tellement le code appelant, que pour chaque inline, la fonction appelante est réduite.

          "La première sécurité est la liberté"

          • [^] # Re: la performance par l'inlining

            Posté par . Évalué à 2.

            Ouvre la fenêtre et respire un grand coup… le commentaire d'avant est tout à fait juste, en plus d'être pointu, la conclusion se focalise juste sur certains cas. Certaines de tes remarques aussi sont relativement pertinents mais se focalisent sur d'autres cas, et le gain (ou non) va plutôt dépendre au final du style d'écriture.

            Ce qui est sûr, c'est que l'hubris face à des personnes visiblement plus compétentes, ça passe assez mal.

            • [^] # Re: la performance par l'inlining

              Posté par . Évalué à 4.

              Tu n'aimes peut être pas mon style, alenvers est sans doute très compétant, mais cela n'enlève pas mes compétence non plus.

              Par exemple, dans le projet Lisaac, il a été fait un test sur le compilateur (50 kloc) pour voir l'effet, incrément par incrément, de l'inlining, et la diminution puis l'augmentation de la taille du code était très visible. ET le pourquoi aussi.

              L'équation fournis plus haut, formalise simplement le fait que l'on va plus vite en évitant un appel de fonction, et les aller-retour dans les registres que cela implique. Ce genre de code va en effet plus vite, mais, comme je l'ai déjà dit les compilo font bien plus que ça. Dés que l'on parle d'inline, on entend toujours quelqu'un dire "attention cela va pourri les caches avec l'augmentation de la taille du code", sauf que personne n'a jamais montré cette augmentation.

              Et de mes propres expériences, j'ai remarqué une diminution. Cette croyance va si loin, que j'ai déjà vu des gens découpés un projet en différent .c pour empêcher l'inline automatique du compilateur, car il croit mieux faire le compilo : le code est finalement plus lent et plus gros. C'est une croyance tenace pour les gros code, alors que c'est justement ceux qui ont le plus tendance à réduire.

              J'ai aussi codé un firmeware fondu dans un chip TI pour la gestion d'énergie des SOC de téléphone, le genre de truc où tu comptes les octets. Au final, tu te retrouves avec l'équivalent d'un gros fichier avec plein de fonctions static (sinon le code est inliné mais une fonction existe toujours en cas d'appel externe dans le code objet), ce qui correspond au code généré le plus petit. Je dis "l'équivalent d'un gros fichier" car c'est tout de même plus propre de découper et d'utiliser des "#includes".

              Il ne faut pas oublier non plus le début du thread : "il y a le fait que ça augmente la taille du code et met donc plus de pressions sur le(s) cache(s) et au final peut ralentir l'exécution d'un programme complexe (ce qui ne se verra pas forcément dans un micro benchmark)." pour que cela soit vrai, la fonction doit être énorme, et ne pas se recombiner avec le code alentour, ce qui est rarement le cas.

              "La première sécurité est la liberté"

              • [^] # Re: la performance par l'inlining

                Posté par . Évalué à 4.

                " personne n'a jamais montré cette augmentation. "

                En fait, c'est même pire que ça. Parce que l'augmentation de la taille de code n'implique pas forcément un code plus lent. D'ailleurs à cause du parallélisme des cpus actuels, un code plus gros, peut être plus rapide (ex: déroulage de boucle).

                cf http://users.polytech.unice.fr/~dedale/cours/unix_et_linux/divers/GNULinux_France_Magazine_archives/lm32/C_Hack.html (cela date de 2001, écrit par moi-même, dans cette version il manque les images par contre)

                "La première sécurité est la liberté"

                • [^] # Re: la performance par l'inlining

                  Posté par . Évalué à 3.

                  « L'équation fournis plus haut, formalise simplement le fait que l'on va plus vite […] »

                  Faux : elle formalise la taille du code, et pas en bien ou en mal, ça ne dépendra que des paramètres utilisés au final. Tu es visiblement trop embourbé dans ta suffisance pour comprendre de quoi il s'agit.

                  « sauf que personne n'a jamais montré cette augmentation »

                  Le comble de la misère intellectuelle. Je ne me renseigne pas sur ce que font les autres, que de toutes façons je méprise, donc ça n'existe pas. 30s de recherche me donne :
                  http://lwn.net/Articles/82495/
                  http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.9.3800
                  http://www.cs.indiana.edu/~dyb/pubs/inlining.pdf

                  Et s'il te plait, si tu veux écraser les autres avec vérités ridicules comme « l'inlining réduit toujours la taille du code » évite de nous parler d'obscurs projets que toi seul connais, sans même donner un seul chiffre, ou encore de papiers qui n'ont rien à voir avec le schmilblic mais bon, culture confiture comme on dit.

                  • [^] # Re: la performance par l'inlining

                    Posté par . Évalué à 1.

                    Ce n'est pas parce que tu pointes des papiers qui parlent d'inline, que cela un rapport avec le thread.

                    "évite de nous parler d'obscurs projets que toi seul connais,"

                    Pourtant, on en a parlé souvent sur linuxfr (si tu parles de Lisaac, parce que si tu ne connais pas Texas Instrument, personne ne peux rien pour toi)! Tu dois être trop jeune sans doute.

                    "ou encore de papiers qui n'ont rien à voir avec le schmilblic"

                    Tu n'as pas du le lire, il y est question d'inline, même si cela n'est pas son sujet unique.

                    "culture confiture comme on dit."

                    On doit toujours être le vieux con de quelqu'un, j'imagine. Et toi, qu'est-ce que tu as fait de tellement exceptionnel pour te permettre de juger les gens ?

                    "La première sécurité est la liberté"

                    • [^] # Re: la performance par l'inlining

                      Posté par . Évalué à 3.

                      Le phread était à propos d'avoir des chiffres de ralentissement par rapport à l’agressivité de l'inlining, et non des généralités sur l'inline.

                      "One of the benefits of inlining, aside from eliminating the call/return, is that it opens new optimization opportunities by optimizing across the caller/callee boundary. In effect, it allows the called function to be specialized for the context from which it was called. For instance, one of the operands to a function might be a flag that enables/disables some feature controlled by the function. If that flag is a constant in the call, entire codepaths from the callee might become dead code. " commentaire sur lwm de 2004 (il y a presque 10 ans, une éternité pour l'informatique).

                      "La première sécurité est la liberté"

            • [^] # Re: la performance par l'inlining

              Posté par (page perso) . Évalué à 3.

              Ce qui est sûr, c'est que l'hubris

              Merci ! Mot compte triple ! J'ai gagné.

          • [^] # Commentaire supprimé

            Posté par . Évalué à 2. Dernière modification le 26/11/13 à 17:16.

            Ce commentaire a été supprimé par l'équipe de modération.

            • [^] # Re: la performance par l'inlining

              Posté par . Évalué à 2.

              "En conclusion, la réduction de la taille du code n'est pas pertinente pour « arrêter d'inliner ». C'est la perte de localité le facteur déterminant d'arrêt de l'inlining."

              On est d'accord :) Disons que c'est évident pour un code donné.

              Dans le cas d'une heuristique pour un compilo, avoir le code qui ré-augmente de taille permet de savoir quand arrêter d'inliner, même si cela n'est pas optimal (comme tu le dis le L1 change selon les CPU, l'optimum dépend donc de chaque cpu, ce qui a peu d’intérêt pour un binaire à distribué).

              "La première sécurité est la liberté"

    • [^] # Re: la performance par l'inlining

      Posté par . Évalué à 2.

      il y a le fait que ça augmente la taille du code et met donc plus de pressions sur le(s) cache(s) et au final peut ralentir l'exécution d'un programme complexe (ce qui ne se verra pas forcément dans un micro benchmark).

      On s'en fout de la taille du code (dans les cas généraux), non ? C'est la longueur du chemin parcouru qui peut augmenter la pression sur le cache, je pense. Le fait d'avoir des optimisation comme la suppression de variables va au contraire diminuer la charge par rapport à une méthode qui recrée tout son contexte.

      Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)

      • [^] # Commentaire supprimé

        Posté par . Évalué à 3.

        Ce commentaire a été supprimé par l'équipe de modération.

        • [^] # Re: la performance par l'inlining

          Posté par . Évalué à 3.

          Tu as bien plus que l'absence de cout de l'appel de fonction (tu as de la simplification en +) ! Dans un gros code, avec des appels dans tous les sens, tu as très peu de probabilité que ta fonction utilitaire soit dans le cache, alors que les prefetch locaux font que tu as toutes les chances que le code de ta fonction en cours, y soit.

          "La première sécurité est la liberté"

Suivre le flux des commentaires

Note : les commentaires appartiennent à ceux qui les ont postés. Nous n'en sommes pas responsables.