20

Fini les vacances pour ARM !!!

juin

Ca devrait arriver, même si on pouvait espérer que non!
Intel arrive dans le monde du smartphone avec son medfield Z2460.
Si Intel sait faire des processeurs (et de très bons processeurs), on avait cru que la gestion de l’énergie n’était pas son fort, et que donc, il n’était pas prêt de venir concurrencer l’ARM dans nos chers smartphones.
Et pourtant ils arrivent et ça va faire mal !

Présentation de la bête

Comme le disait Sun TZU, “Qui connaît son ennemi comme il se connaît, en cent combats ne sera point défait”. Aussi ai-je décidé de m’offrir l’un des tout premiers téléphones à base de processeur Intel, à savoir le “Orange avec Intel Inside” afin de me faire une idée sur les chances de survie de l’ARM dans les 10 prochaines années !
Derrière ce nom (qui on s’en doute à du mobiliser la moitié de l’équipe marketing d’Orange pendant au moins 15 jours de brainstorming intensif), se cache un téléphone encore pas trop cher pour ce que c’est !
250 euros l’engin, c’est acceptable pour un mobile avec ces caractéristiques !

  • Android 2.3.7, mais avec option migration sur ICS d’ici x mois (avec 1 < = x < 36) !
  • Ecran 4 pouces 1024*600
  • Processeur Z2460 mono Coeur (mais hyper threading) 1.6 Ghz !
  • 1 Go de Ram (quand même) !

Le reste des spécifications n’a pas grand intérêt !

Autant le dire tout de suite, JAMAIS une carte SIM n’ira se loger dans mon téléphone Orange ! Tout d’abord parce que je n’ai pas de forfait Orange, mais surtout parce qu’avec un DAS de 1.4 W/kg, l’utilisation prolongé du “Orange avec Intel Inside” (j’adore !) pourrait rapidement devenir plus nocif qu’un séjour d’une semaine au ClubMed de Fukushima !!!

Medfield Z2460

Les specs!

Le processeur d’Intel est donc un Atom Medfield Z2460 (Architecture Saltwell).
Il s’agit d’un processeur 64 bits qui dispose de 32Ko/24Ko de cache (Instruction et données) ainsi que d’un cache de niveau 2 de 512 Ko!
Il est cadencé à 1.6 Ghz, ce qui en fait l’un des processeurs pour smartphone ayant la plus haute fréquence de fonctionnement (ce qui ne veut absolument pas dire qu’il est plus rapide).
Il ne dispose que d’un seul coeur mais est doté de l’hyperthreading, qui, si j’ai bien compris permet de simuler deux coeurs en utilisant plus ou moins les mêmes unités de calcul du processeur.
Il est équipé du chip graphique PowerVR SGX 540 dont je ne peux pas dire grand chose puisque j’y connais rien !
Enfin Il dispose des instructions SSE (équivalent de NEON sur l’architecture ARM) mais pas des instructions AVX (ce qui eu été un plus mais n’est pas rédhibitoire) !

Bon ben voilà. Sur le papier ce processeur n’a pas l’air mal !

La présentation du z2460 d’Intel se trouve ici.
Et une spec un peu plus détaillée ici.

Intel vs ARM

Alors pour ceux qui ne connaîtrait pas trop l’une des deux architectures (ARM ou x86), voilà en gros les principales différences !

CISC vs RISC : Même si cela n’a plus grand chose de vrai, le principe fondateur du processeur ARM a été de dire que 80% des instructions (exécutées) d’un programme représente 20% des instructions (disponibles) du processeurs. En gros cela veut dire que certaines instructions ne sont jamais utilisés. Les processeurs Intel sont dit CISC (Complex Instruction Set Computer). Ils disposent de plusieurs centaines (peut être même un millier) d’instructions. A ce niveau, rare sont les programmeurs assembleurs qui les connaissent toutes et tout aussi rare sont les compilateurs capables des les exploiter à bon escient. A l’opposé, les processeurs ARM sont (était) des processeurs RISC Reduced Instruction Set Computer. A la base il ne disposaient que de quelques dizaines d’instructions. Avec le temps ce nombre à augmenté et comme tout bon vieux processeur qui se respecte, ARM doit à présent se trimbaler des instructions qui ont eu une durée de vie très courtes car remplacées pas autre chose de “mieux”.

Les registres : Le nombre de registres “généraux” est non seulement plus élevé sur les processeurs ARM (15 si on ne compte pas le PC) que sur l’architecture x86, mais en plus ils peuvent être utilisés avec toutes les instructions alors que l’architecture x86 d’Intel dédie des registres à des usages bien précis ! En contre partie, la plupart des instructions de l’Intel peuvent utiliser des variables contenues en mémoire en guise d’opérande alors que l’ARM n’exécute que des instructions dont les données sont contenues dans les registres.

Instruction à 3 opérandes : C’est peut être l’une des plus significatives différences entre les deux architectures !!! L’ARM dispose d’opération à 3 opérandes (A = B + C) alors que les instructions de l’architecture x86 doit écraser l’un des deux registres sources (A = A + B). Cela implique des sauvegardes de données plus fréquentes et généralement un code plus long (en terme de nombre d’instructions) pour obtenir le même résultats que sur ARM. Par contre, les instructions de l’ARM sont toutes codées sur 32 bits alors que les instructions x86 sont codées sur un nombre variable d’octets. Il en résulte un code qui en taille peut s’avérer plus petit pour le processeur Intel.

NEON vs SSE : NEON est l’équivalent de SSE chez Intel. Je l’écris dans ce sens car NEON est arrivé bien après SSE. Du coup, NEON est bien mieux fichu ! On retrouve à peut prêt les mêmes différences sur le jeu d’instructions SIMD que sur le reste du processeur. NEON dispose d’instructions à 3 opérandes et de deux fois plus de registres que SSE !

Bon en clair, 10 ans sépare l’apparition de l’architecture x86 de l’architecture ARM et pourtant cela à suffit pour qu’Intel essuie les plâtres.
L’architecture IA-32 d’Intel fait office de dinosaure. L’ARM, plus récent et bien mieux pensé est nettement plus efficace.

Les gros avantages d’Intel

Mais pourquoi donc le processeur préhistorique d’Intel arrive t-il à tenir la route ???

En fait 3 critères (d’après moi) permettent à Intel de contrebalancer les contraintes liées à la compatibilité de l’archaïque architecture x86 !

Le meilleur fabriquant de semi conducteur du monde
Tout d’abord il ne faut pas se leurrer, Intel est et reste le meilleur fabriquant de processeurs du monde. La société recrute les meilleurs ingénieurs dans le domaine.
Elle investit en masse dans la recherche. Elle dispose également de moyens financier sans commune mesure avec la concurrence. Que ce soit AMD ou bien VIA, personne n’a pu égaler (longtemps) les performances d’Intel dans le monde du x86.
Son portefeuille de brevets et son avance technologique permet en grande partie de combler le retard de son jeu d’instruction obsolète.

Les meilleurs compilateurs
Les compilateurs pour la plateforme x86 sont devenus au fil des années ultra optimisés. Le code généré est d’excellente qualité, et si l’assembleur reste évidement utilisé pour des routines très spécifiques, l’écart entre C et et Assembleur est bien plus mince sur l’architecture x86 que sur les processeurs ARM.

La finesse de gravure
Véritable nerf de la guerre, pour atteindre des fréquences élevées et une dissipation thermique contenue, la finesse de gravure est encore une fois l’un des domaine ou Intel est à la pointe. Là, c’est principalement une histoire de moyens financiers! Construire des usines de gravure de processeurs coûtent une fortune. Je pense d’ailleurs que seul Intel à les moyens d’avoir ses propres usines qui fabriquent uniquement ses processeurs. Les Autres fondeurs (IBM, TSMC, Global Foundries et Samsung) fabriquent des puces pour différents partenaires.

Le bench! le bench! le bench!

Apres avoir testé mes programmes et m’être rendu compte qu’aucun d’entre eux ne marchaient (sic !!!), j’ai fait mon geek de base et j’ai installé quelques benchmarks pour comparer le téléphone “Intel inside” avec ce que j’ai sous la main, à savoir mon Galaxy Note adoré!

Remarque: Bon je ne suis pas un pro des benchmarks ! Je ne suis d’ailleurs par trop sur que comparer un Galaxy Note sous ICS ayant une résolution de 1280*800 pixel avec un “Orange avec Intel Inside” sous Android 2.3.7 ayant une résolution de 1024*600 soit bien représentatif !!!
Pour rappel : Le Galaxy Note dispose d’un samsung duak core Exynos à 1.4 Ghz.

Antutu

AnTuTu plante directement sur le Téléphone Intel !

0xBench

0xBench a le vent en poupe depuis que l’équipe de Linaro l’a utilisé pour mesurer l’avancée de leurs travaux.

Galaxy Note Téléphone Intel
Linpack 43 Mflop/s 80 Mflop/s
JAVA SciMark2 Composite 76 Mflop/s 76 Mflop/s
FFT 48 Mflop/s 54 Mflop/s
Jacobi 172 Mflop/s 164 Mflop/s
Monte Carlo 13 Mflop/s 8 Mflop/s
Sparse MatMul 49 Mflop/s 62 Mflop/s
LU Mat fact. 97 Mflop/s 93 Mflop/s
Virtual Machine Bench 4181 ms 2513 ms
Sun Spider 1454 ms 1391 ms
2D Canvas Stoke Avg 42 fps 46 fps
Image 25 fps 56 fps
Texte 55 fps 56 fps
3D OpenGL Cube 57 fps 56 fps
Blending 61 fps 60 fps
Fog 60 fps 60 fps
Teapot 19 fps 55 fps

Remarque: ce bench a pour effet de laisser le Galaxy Note dans un état plus qu’étrange nécessitant un reboot du Samsung !!!

Quadrant
Galaxy Note Intel z2460
CPU 6587 4315
Memoire 3534 7868
I/O 7155 7318
2D 382 459
3D 2163 2088
Total 3964 4315
Et donc ???

Bon avant de vous faire part de mon analyse hyper pertinente des résultats, je tiens à dire, qu’après avoir exécuté plusieurs fois chacun des benchs, l’Intel n’est pas devenu une chaudière…
La batterie ne s’est pas non plus significativement déchargée. C’est donc plutôt un bon point pour Intel, puisqu’on aurait pu penser le contraire !
Par contre, il y a un drôle d’effet sur ce téléphone. De temps en temps l’écran flashouille un coup. On ne peut pas dire que ce soit ultra gênant, mais d’un autre coté on se serait bien passé de ce petit bug quelque peu étrange!

Niveau performance !

  • Au niveau des calculs entiers, avantage au processeur ARM. L’écart relevé dans le bench de Quandrant est plus élevé que je ne l’aurai pensé (je suppose que ce test n’est pas multithreadé sinon toute mon explication tombe à l’eau :) ). En gros, il faut nettement plus d’instructions aux processeurs Intel qu’aux processeurs ARM pour réaliser une même tâche. Hors (de nos jours) l’exécution d’instructions entières ne prend qu’un seul cycle sur la plupart des processeurs, donc moins d’instructions rime généralement avec plus performant.
  • Pour les flottants, C’est l’Intel qui semble dominer. Ce n’est pas une grosse surprise. ARM a toujours eu un train de retard dans ce domaine. Le Cortex A8 était assez moyen, le A9 (celui du GNote) est déjà nettement plus performant. Mais il semble qu’il faille attendre le cortex A15 (celui sur lequel se base l’architecture Krait du processeur de Qualcomm qui équipe le HTC One S) pour avoir des performances au top dans ce domaine.
  • Dans le domaine de la gestion mémoire, gros avantage pour l’Intel. C’est ce que semble indiquer (encore une fois) le bench de Quandrant. Il est certain que là aussi depuis des décennies Intel doit améliorer la gestion du cache et le controlleur mémoire de ses processeurs. Il faut savoir que le processeur Intel dispose de moins de registres et que donc les accès à la mémoire pour conserver des données temporaires sont plus importants.
  • Enfin au niveau graphique, l’Intel est un peu meilleur que le Galaxy Note, mais c’est probablement due à la résolution moins élevée du “Orange avec Intel Inside”.

Il nous reste deux tests très intéressants (car nettement plus concrets):

  • Le test de la VM qui montre clairement qu’elle fonctionne plus que bien sur le processeur Intel. Cela est probablement étroitement lié aux performances de la gestion mémoire constatée précédemment
  • SunSpider quant à lui montre que l’Intel n’a pas a rougir non plus dans le domaine du Javascript. D’après moi le z2460 est très efficace dans l’exécution du Javascript (comme il l’est pour la VM Java). Par contre il va perdre quelques précieuses milli-seconde sur les tests de calculs !
Un bench reste un bench !!!

A lire les résultats obtenus, on pourrait penser que le Téléphone d’Orange fait jeu égal (voir mieux) que le Galaxy Note.
Soyons donc clair. Il n’en est rien du tout !
A l’usage, le Galaxy Note est nettement plus réactif. Comme quoi les benchs, ça ne veut pas dire grand chose !

C’est sans doute du à la note très élevée qu’a obtenu le téléphone d’Orange dans les I/O.
Où a t-on vu que le fait d’accéder rapidement à la mémoire interne avait un impact significatif sur les performances globales ? Si cette information n’est pas inutile, elle devrait être faiblement pondérée, car elle fausse complètement la note moyenne fournie par Quadrant !

Bon, j’ai de toute façon l’intention de faire marcher mes programmes sur le “Orange avec Intel Inside”.
Je vais vite me faire ma propre opinion ! Je pourrais alors vous donner mon véritable avis sur la bête et surtout les instructions SSE (l’équivalent de NEON) chez Intel !

Premier programme

Ok ! Assez discuté! Entrons dans le vif du sujet !
Voyons donc comment on peut faire pour développer un truc qui fonctionne aussi bien sur ARM que sur X86 (ou Mips ou autre !!!)

Le Java

Si votre application est uniquement développée en JAVA, elle fonctionnera aussi bien (aussi mal ???) sur ARM que sur x86. C’est encore heureux sinon vraiment, le JAVA ne servirai à rien !!!

Le C/C++

Si nous avez développé une fonction en C avec le NDK, il est clair que le code compilé pour un ARM n’aura rien à voir avec le même code compilé pour le processeur Intel !
Heureusement, pour une fois (serais-je tenté de dire) google nous à bien simplifié le travail puisqu’il n’y a presque rien à faire (voir même rien du tout).

La version d’Android pour x86 semble être doté d’un émulateur de code ARM !
Je n’y croyais pas trop jusqu’à ce que je teste mon programme HelloAndroid que j’avais modifié pour qu’il exécute une fonction assembleur ! Et il marche !
J’ai refait quelques tests, et il est clair que le code ARM (qu’il soit produit depuis un source C ou assembleur) est bel et bien traité par l’Android x86. Alors là Bravo !
Par contre, cela ne fonctionne pas à tous les coup, puisque la plupart de mes programmes ne fonctionnent pas (Cela vient soit des instructions NEON, soit peut être de la vilaine habitude que j’ai d’aller modifier le PC à tout bout de champ pour sauter dans du code généré, je ne sais pas trop !)
De plus, on se doute que que la compilation just in time doit avoir ses limites en termes de performances. Nous (enfin moi), ce qu’on veut c’est avoir un super code x86 compilé tout bien !

Pour cela il nous faut déjà avoir un ndk assez récent. La version 6 est la version minimum prenant en compte l’architecture x86!
Il vous suffit alors d’ouvrir (ou de créer) le fichier Application.mk et d’y ajouter une plateforme.
Si vous n’avez pas ce fichier, il faut le créer juste à coté du fichier Android.mk et mettre dedans le code suivant

APP_ABI := armeabi armeabi-v7a x86
APP_PLATFORM := android-8

Voilà.
APP_ABI defini toutes les architectures sur lequel il va falloir compiler les codes C et C++.
Android va compiler toutes les versions, assembler tout ça dans un seul module puis se débrouiller tout seul pour utiliser la bonne lib lorsque vous chargerez le module dans votre Application.

  • armeabi permet d’avoir une compatibilité avec les processeurs ARM antérieure aux Cortex, c’est à dire ARM11 ou plus ancien encore
  • armeabi-v7a permet d’avoir une compatibilité avec les processeur ARM de type Cortex disposant d’un FPU (entre autre)
  • x86 va donc permettre de compiler pour les processeurs Intel

On aurait pu ajouter mips pour le processeur MIPS.
On peut aussi spécifier all pour couvrir toutes les plateformes gérées par Android. L’avantage c’est que vous n’avez pas à connaître toutes les plateformes gérées par Android, l’inconvénient, c’est que la phase de compilation va être d’autant plus longue que la liste des plateformes gérées est grande !!!

L’assembleur

Pour l’assembleur c’est un peu plus compliqué, car on veut généralement fournir à gcc des paramètres de compilation ou plus simplement un code différent pour chaque architecture!

Par exemple, pour l’ARM, je souhaite compiler le fichier assembleur.arm.s avec les options de compilation : -mfpu=neon -march=armv6t2
Ce qui veut dire :

  • -mfpu=neon : pouvoir utiliser les instructions NEON
  • -march=armv6t2 : Les instructions que j’utilise doivent être compatibles avec le jeu d’instruction de l’ARM v6 Thumb 2.

Pour l’Intel, je veux compiler le fichier assembleur.x86.s et donner à gcc les options suivantes : -msse2 -m32 -masm=intel

  • -msse2 : utiliser les instructions sse2 (bon j’aurai pu mettre sse4 ça coutait rien !)
  • -m32 : compiler en Intel 32 bits
  • -masm=intel : Uitliser les conventions de nommage de instructions recommandées par Intel. Le but étant ici d’avoir comme sur ARM le registre destination à gauche.

La question est donc : “Comment compiler dans un seul fichier Android.mk plusieurs code source assembleur en utilisant des paramètres différents ?”
La solution se trouve en fait dans les exemples du NDK !

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE := module_assembleur

ifeq ($(TARGET_ARCH),arm)
	LOCAL_ARM_MODE		:= arm
	LOCAL_CFLAGS		:= -mfpu=neon -march=armv6t2
	LOCAL_SRC_FILES	:= assembleur.arm.s
endif

ifeq ($(TARGET_ARCH),x86)
	LOCAL_CFLAGS		:= -msse2 -m32 -masm=intel
	LOCAL_SRC_FILES	:= assembleur.x86.s
endif

include $(BUILD_SHARED_LIBRARY)

C’est pas franchement compliqué. Enfin une fois qu’on sait faire évidement.

Il ne nous reste plus qu’à réécrire notre fonction assembleur pour l’architecture x86.
Je rappelle que le code de la fonction pour l’ARM était le suivant :

	.global		Java_com_example_helloandroid_HelloAndroid_sommeAsm
	.type			Java_com_example_helloandroid_HelloAndroid_sommeAsm, %function
Java_com_example_helloandroid_HelloAndroid_sommeAsm:
	add			r0, r2, r3
	mov			pc, lr
	.size			Java_com_example_helloandroid_HelloAndroid_sommeAsm, .-Java_com_example_helloandroid_HelloAndroid_sommeAsm

pour l’Intel, le code équivalent sera :

	.intel_syntax noprefix
	.global		Java_com_example_helloandroid_HelloAndroid_sommeAsm
	.type			Java_com_example_helloandroid_HelloAndroid_sommeAsm, %function
Java_com_example_helloandroid_HelloAndroid_sommeAsm:
	mov			eax, DWORD PTR [esp+12]
	add			eax, DWORD PTR [esp+16]
	ret
	.size			Java_com_example_helloandroid_HelloAndroid_sommeAsm, .-Java_com_example_helloandroid_HelloAndroid_sommeAsm

A l’exception des instructions x86 qui sont différentes, le programme reste le même et surtout le nom de la fonction reste le même
On a juste ajouté la définition de la syntaxe Intel

	.intel_syntax noprefix

Il faut savoir que la syntaxe par défaut de l’assembleur x86 (celle de masm si je ne me trompe) est ultra pénible, on utilise donc la syntaxe Intel qui entre autre permet de spécifier les opérandes dans le “bon ordre”, c’est à dire, l’opérande de destination en premier.

Remarque: Comme je l’avais mentionné précédemment, le code Intel nécessite 3 instructions là ou le même code ARM n’en n’utilise que 2! Cela est due à la capacité des instructions ARM d’avoir 3 opérandes.

Bon allez hop. on compile tout ça, on exécute et on constate que ça fonctionne très bien !
Le seul problème, c’est que comme Android x86 est capable d’émuler le code ARM, on n’est pas trop sur que ça fonctionne et qu’on utilise bien le bon code !
Seule solution pour en être vraiment sur (si on ne fait pas confiance à google), c’est d’essayer de mettre deux codes différents dans les fichiers assembleur.arm.s et assembleur.x86.s. A ce moment là, on constate qu’effectivement, c’est bien la bonne fonction qui est appelé sur chaque terminal !

Conclusion

Alors, Apres ce petit tour d’horizon, il y a pas mal de chose à dire !!!

Je suis pas un grand fan d’Intel, et pourtant…

Il faut tout d’abord remarquer que le z2460 d’Intel est un bon processeur, qui ne chauffe pas trop, qui n’est pas ultra énergivore et qui fonctionne plutôt bien !

Le code Java s’exécute indifféremment sur toutes les machines Android, ce n’est pas une surprise.
Le code compilé, lui aussi est capable de fonctionner grâce à un compilateur Just In Time plutôt discret qui rend du coup un grand nombre d’applications compatible avec le téléphone, sans qu’on se rende compte de quoique ce soit.
Pour être honnête, à aucun moment (avant bien sur que je ne le découvre) j’ai pensé que le code ARM était converti à la volée ! Je pensais plutôt que la majeure partie des développeurs compilait leur code pour toutes les plateformes !

L’expérience utilisateur est donc plutôt satisfaisante. On constate pourtant que le téléphone n’est pas non plus un foudre de guerre, mais pour le prix on pouvait craindre pire !
Il faut dire aussi que la re-compilation à la volée du code ARM doit non seulement prendre du temps, mais en plus ne dois pas générer un code optimal. Pour véritable connaître les performances du processeur, il faudrait approfondir les tests car à ce stade, on a juste effleuré la surface ! Il est bien possible que le processeur Intel dispose d’un potentiel bien plus élevé.

Enfin, attention aux benchmarks !
Quadrant à propulsé le téléphone d’Orange dans le top du classement car (entre autre) les accès à la mémoire interne de stockage était très rapide ! Sans aller jusqu’à dire que ce paramètre n’est pas important, il faut bien comprendre que rare sont les programmes (je ne parle même pas des jeux qui eux n’y accèdent pour ainsi dire jamais) qui accèdent en permanence à la mémoire de stockage !!!

Je milite pour les britanniques

Inutile de se voiler la face, Le z2460 est une belle démonstration du savoir faire d’Intel dans le domaine des processeurs!

Malheureusement pour eux, ils arrivent un peu tard!
Les processeurs ARM le Cortex A8 (et A9) s’ils sont déjà de très bon processeurs (enfin surtout le A8 !!!) qui peuvent faire jeu égale avec le z2460 d’Intel.
Les Cortex A15, quant-à eux semblent promettre un vrai bon dans le domaine des performances!

Mais surtout, le parc applicatif est entièrement tourné vers l’ARM ! Il va falloir du temps pour que les développeurs prennent l’habitude de compiler leur code pour l’architecture x86!
Il faudra d’autant plus de temps que visiblement le code ARM peut être “exécuté” sur les Android x86 de façon plutôt satisfaisante!

Bref c’est pas gagné pour Intel d’après moi ! Même si l’histoire montre qu’ils ont toujours su optimiser encore et encore leur architecture vieillissante, il doit bien exister un moment ou cela ne sera plus possible non ?
ARM a déjà fait son choix en matière d’évolution à long terme! Ils ont décidé de repartir sur une nouvelle architecture (qui reste quand même dans la continuité de l’existant) en présentant l’Aarch64 qui pourrait d’ici quelques années (enfin une bonne dizaine quand même) prendre l’ascendant sur l’architecture actuelle.

Ce choix, Intel l’avait fait avec l’Itanium avant de mettre un frein au projet. Il faut dire aussi que dans leur cas, le changement était vraiment radical, l’Itanium n’ayant vraiment rien à voir avec l’architecture x86.

Au final

Et bien au final, difficile de savoir ce qui va advenir !
Intel à de gros moyens, et un premier processeur plutôt honnête. Il faut bien voir que cette première mouture n’est pas multi-core et tourne déjà plutôt bien ! Il faut voir si les constructeurs vont s’engouffrer dans la brèche !

A mon humble avis, il ne faudrait pas qu’ARM s’endorme trop sur leur lauriers, sinon le réveille risque d’être difficile (en tout cas sur Android) !

Bon dans tous les cas de figure, il nous reste au minimum encore quelques bonnes années de codes NEON devant nous avant de devoir nous convertir à autres choses de moins… ludique…

 | Tags:

Répondre

Human control : 4 + 2 =