Étant donné un système informatique spécifique, est-il possible d'estimer la durée d'exécution réelle réelle d'un morceau de code d'assemblage


23

c'est un morceau de code d'assemblage

section .text
    global _start       ;must be declared for using gcc
_start:                     ;tell linker entry point
    mov edx, len    ;message length
    mov ecx, msg    ;message to write
    mov ebx, 1      ;file descriptor (stdout)
    mov eax, 4      ;system call number (sys_write)
    int 0x80        ;call kernel
    mov eax, 1      ;system call number (sys_exit)
    int 0x80        ;call kernel

section .data

msg db  'Hello, world!',0xa ;our dear string
len equ $ - msg         ;length of our dear string

Étant donné un système informatique spécifique, est-il possible de prédire avec précision le temps d'exécution réel d'un morceau de code d'assemblage.


30
Est-ce que "exécuter le code sur cet ordinateur et utiliser un chronomètre" est une réponse valide?
Draconis

4
Je soupçonne que la majorité du temps passé à exécuter ce morceau de code attend les E / S. Le temps qu'il faut pour exécuter les instructions individuelles est quelque peu prévisible si vous connaissiez l'emplacement mémoire du code et tous les détails sur le processeur (qui sont extrêmement complexes de nos jours), mais la vitesse est également affectée par la mémoire et le disque, donc vous '' Je dois également connaître une très grande quantité de détails à leur sujet. Donc, à moins de tenir compte des phénomènes physiques (qui affectent également le temps), vous pourriez dire que c'est prévisible, mais incroyablement difficile à faire.
IllidanS4 veut que Monica revienne le

4
il est toujours possible d'estimer ...
sudo rm -rf slash

3
N'est-ce pas également impossible en raison du problème d'arrêt? Nous pouvons prouver pour un code s'il s'arrêtera, mais nous ne pouvons pas avoir d'algorithme qui le détermine pour tous les codes possibles.
kutschkem

2
@Falco Ce serait une propriété du système donné. Certaines implémentations C autonomes n'ont pas de système d'exploitation; tout ce qui est en cours d'exécution est une boucle principale (ou même pas une boucle ;-)) qui peut ou non lire des adresses matérielles pour l'entrée.
Peter - Rétablir Monica le

Réponses:


47

Je ne peux que citer le manuel d'un CPU plutôt primitif, un processeur 68020 datant d'environ 1986: "Calculer le temps d'exécution exact d'une séquence d'instructions est difficile, même si vous avez une connaissance précise de l'implémentation du processeur". Ce que nous n'avons pas. Et comparé à un processeur moderne, ce CPU était primitif .

Je ne peux pas prédire la durée d'exécution de ce code, et vous non plus. Mais vous ne pouvez même pas définir ce qu'est le «temps d'exécution» d'un morceau de code, lorsqu'un processeur a des caches massifs et des capacités hors service massives. Un processeur moderne typique peut avoir 200 instructions "en vol", c'est-à-dire à différents stades d'exécution. Ainsi, le délai entre la lecture du premier octet d'instruction et la suppression de la dernière instruction peut être assez long. Mais le délai réel pour tous les autres travaux que le processeur doit faire peut être (et est généralement) beaucoup moins.

Bien sûr, faire deux appels au système d'exploitation rend cela complètement imprévisible. Vous ne savez pas ce que fait réellement "écrire sur stdout", vous ne pouvez donc pas prédire l'heure.

Et vous ne pouvez pas connaître la vitesse d'horloge de l'ordinateur au moment précis où vous exécutez le code. Il peut être dans un mode d'économie d'énergie, l'ordinateur peut avoir une vitesse d'horloge réduite car il fait chaud, donc même le même nombre de cycles d'horloge peut prendre différentes quantités de temps.

Dans l'ensemble: totalement imprévisible.


12
Je pense que vos conclusions sont trop fortes. La latence et le débit sont des mesures courantes pour mesurer le «temps d'exécution» d'un programme. Vous pouvez également vous contenter d'une définition appropriée de "runtime". De plus, si vous avez un instantané complet de l'état du système, hw et sw, et une parfaite connaissance des composants internes du CPU, vous pouvez prédire le temps d'exécution. Chez Intel, ils peuvent probablement estimer le temps d'exécution, même ici sur SO, nous pouvons prédire les latences et les sorties avec une précision de cycle. Dans ce cas, outre les appels système, ce n'est même pas si difficile.
Margaret Bloom

10
@MargaretBloom même pas alors. Je place mon téléphone trop près du four, le CPU sous-horloge pour gérer la température, votre estimation d'autonomie est soudainement trop faible. Et même si vous comptez en cycles et ne faites pas d'appels système, d'autres threads et processeurs peuvent bien jouer avec le contenu de la RAM, ou ils peuvent vider votre mémoire sur le disque dur pendant que vous êtes échangé, en fonction de circonstances imprévisibles, allant de l'alimentation les surtensions ralentissent le disque dur juste assez pour qu'un thread concurrent ait suffisamment de mémoire à temps pour détruire le vôtre, jusqu'aux threads qui roulent directement pour voir combien de temps à perdre.
John Dvorak

6
En plus de cela, "une connaissance complète de l'état du système, hw et sw" est une tâche assez difficile, je pense. Ajoutez "10 ms à l'avance", et vous demandez déjà l'impossible. Et si la mise en œuvre par votre CPU de la génération de nombres aléatoires matériels utilise des phénomènes quantiques (c'est probablement le cas), et qu'un thread sur le CPU l'appelle, alors même ne pas connaître l'état complet de l'univers à 3000 km autour de l'ordinateur vous sauvera. Et dans MWI, vous ne pouvez même pas deviner correctement.
John Dvorak

8
@Nat: Même en cryptographie, "temps constant" ne signifie pas vraiment absolument constant - cela signifie simplement que le temps d'exécution n'a pas de variations systématiques qui dépendent des données secrètes et pourraient être statistiquement corrélées avec elles. Et dans la pratique, on suppose souvent que si le chemin de code emprunté et le modèle d'accès à la mémoire exécutés ne dépendent pas de données secrètes, et si des instructions spécifiques connues pour prendre un temps variable sont évitées (ou leurs entrées masquées pour si tout va bien éliminer la corrélation), c'est probablement assez bon. Au-delà de cela, il suffit vraiment de le mesurer.
Ilmari Karonen

2
Un 68020 est une bête complexe ... essayez un MCS51 ....
rackandboneman

30

Vous ne pouvez pas le faire en général, mais dans certains sens, vous le pouvez beaucoup, et il y a eu quelques cas historiques dans lesquels vous avez dû le faire.

L' Atari 2600 (ou Atari Video Computer System) était l'un des premiers systèmes de jeux vidéo à domicile et a été publié pour la première fois en 1978. Contrairement aux systèmes ultérieurs de l'époque, Atari ne pouvait pas se permettre de donner à l'appareil un tampon de trame, ce qui signifie que le processeur avait pour exécuter du code à chaque ligne de balayage afin de déterminer ce qu'il faut produire - si ce code prenait plus de 17,08 microsecondes pour s'exécuter (intervalle HBlank), les graphiques ne seraient pas correctement définis avant que la ligne de balayage ne commence à les dessiner. Pire encore, si le programmeur voulait dessiner un contenu plus complexe que ce qu'Atari autorisait normalement, il devait mesurer les heures exactes des instructions et modifier les registres graphiques au fur et à mesure du tracé du faisceau, avec une durée de 57,29 microsecondes pour toute la ligne de balayage.

Cependant, l'Atari 2600, comme de nombreux autres systèmes basés sur le 6502, avait une caractéristique très importante qui permettait la gestion minutieuse du temps requise pour ce scénario: le processeur, la RAM et le signal TV fonctionnaient tous sur des horloges basées sur le même maître l'horloge. Le signal TV a fonctionné sur une horloge de 3,98 MHz, divisant les temps ci-dessus en un nombre entier d '"horloges de couleur" qui ont géré le signal TV, et un cycle des horloges CPU et RAM était exactement de trois horloges couleur, permettant à l'horloge du CPU d'être une mesure précise du temps par rapport au signal TV de progression actuel. (Pour plus d'informations à ce sujet, consultez le Guide du programmeur Stella , écrit pour l' émulateur Stella Atari 2600 ).

Cet environnement d'exploitation, en outre, signifiait que chaque instruction CPU avait une quantité définie de cycles qu'elle prendrait dans chaque cas, et de nombreux développeurs 6502 ont publié ces informations dans des tableaux de référence. Par exemple, considérez cette entrée pour l' CMPinstruction (Comparer la mémoire avec l'accumulateur), extraite de ce tableau :

CMP  Compare Memory with Accumulator

     A - M                            N Z C I D V
                                    + + + - - -

     addressing    assembler    opc  bytes  cycles
     --------------------------------------------
     immediate     CMP #oper     C9    2     2
     zeropage      CMP oper      C5    2     3
     zeropage,X    CMP oper,X    D5    2     4
     absolute      CMP oper      CD    3     4
     absolute,X    CMP oper,X    DD    3     4*
     absolute,Y    CMP oper,Y    D9    3     4*
     (indirect,X)  CMP (oper,X)  C1    2     6
     (indirect),Y  CMP (oper),Y  D1    2     5*

*  add 1 to cycles if page boundary is crossed

En utilisant toutes ces informations, Atari 2600 (et d'autres développeurs 6502) ont pu déterminer exactement combien de temps leur code mettait à s'exécuter, et construire des routines qui faisaient ce dont elles avaient besoin et respectaient toujours les exigences de synchronisation du signal TV d'Atari. Et parce que ce timing était si précis (en particulier pour les instructions de perte de temps comme NOP), ils ont même pu l'utiliser pour modifier les graphiques au fur et à mesure qu'ils étaient dessinés.


Bien sûr, le 6502 d'Atari est un cas très spécifique, et tout cela n'est possible que parce que le système avait toutes les caractéristiques suivantes:

  • Une horloge maître qui a tout exécuté, y compris la RAM. Les systèmes modernes ont des horloges indépendantes pour le CPU et la RAM, l'horloge RAM étant souvent plus lente et les deux n'étant pas nécessairement synchronisées.
  • Aucune mise en cache d'aucune sorte - le 6502 a toujours accédé directement à la DRAM. Les systèmes modernes ont des caches SRAM qui rendent plus difficile la prédiction de l'état - bien qu'il soit peut-être encore possible de prédire le comportement d'un système avec un cache, c'est certainement plus difficile.
  • Aucun autre programme fonctionnant simultanément - le programme sur la cartouche avait un contrôle complet du système. Les systèmes modernes exécutent plusieurs programmes à la fois en utilisant des algorithmes de planification non déterministes.
  • Une vitesse d'horloge suffisamment lente pour que les signaux puissent traverser le système dans le temps. Sur un système moderne avec des vitesses d'horloge de 4 GHz (par exemple), il faut un photon de lumière 6,67 cycles d'horloge pour parcourir la longueur d'une carte mère d'un demi-mètre - vous ne pourriez jamais vous attendre à ce qu'un processeur moderne interagisse avec autre chose sur la carte en un seul cycle, car il faut plus d'un cycle pour qu'un signal sur la carte atteigne même l'appareil.
  • Une vitesse d'horloge bien définie qui change rarement (1,19 MHz dans le cas de l'Atari) - les vitesses du processeur des systèmes modernes changent tout le temps, tandis qu'un Atari ne pourrait pas le faire sans affecter également le signal TV.
  • Délais de cycle publiés - le x86 ne définit pas la durée de ses instructions.

Toutes ces choses se sont réunies pour créer un système où il était possible de créer des ensembles d'instructions qui ont pris un temps exact - et pour cette application, c'est exactement ce qui était demandé. La plupart des systèmes n'ont pas ce degré de précision simplement parce que cela n'est pas nécessaire - les calculs sont effectués lorsqu'ils sont terminés, ou si une durée exacte est nécessaire, une horloge indépendante peut être interrogée. Mais si le besoin est bon (comme sur certains systèmes embarqués), il peut toujours apparaître, et vous pourrez déterminer avec précision le temps nécessaire à l'exécution de votre code dans ces environnements.


Et je devrais également ajouter le grand avertissement massif que tout cela ne s'applique qu'à la construction d' un ensemble d'instructions d'assemblage qui prendra un temps exact. Si ce que vous voulez faire est de prendre un assemblage arbitraire, même dans ces environnements, et de demander "Combien de temps cela prend-il pour s'exécuter?", Vous ne pouvez catégoriquement pas le faire - c'est le problème de l' arrêt , qui s'est avéré insoluble.


EDIT 1: Dans une version précédente de cette réponse, j'ai déclaré que l'Atari 2600 n'avait aucun moyen d'informer le processeur de l'endroit où il se trouvait dans le signal TV, ce qui l'a forcé à garder le programme entier compté et synchronisé dès le début. Comme je l'ai souligné dans les commentaires, cela est vrai pour certains systèmes comme le ZX Spectrum, mais ce n'est pas le cas de l'Atari 2600, car il contient un registre matériel qui arrête le processeur jusqu'à ce que le prochain intervalle de suppression horizontal se produise, ainsi que une fonction pour commencer à volonté l'intervalle de suppression verticale. Par conséquent, le problème des cycles de comptage est limité à chaque ligne de balayage et ne devient exact que si le développeur souhaite modifier le contenu pendant le tracé de la ligne de balayage.


4
Il convient également de noter que la plupart des jeux ne fonctionnaient pas parfaitement - vous pouviez voir de nombreux artefacts dans la sortie vidéo en raison d'une synchronisation inadéquate du signal vidéo, soit en raison d'une erreur de programmation (estimation incorrecte de la synchronisation du processeur) ou simplement en ayant trop Travail à faire. Il était également très fragile - si vous deviez corriger un bogue ou ajouter de nouvelles fonctionnalités, vous casseriez très probablement le timing, parfois inévitablement. C'était amusant, mais aussi un cauchemar :) Je ne sais même pas si la vitesse d'horloge était toujours exactement correcte - par exemple en cas de surchauffe, d'interférences, etc. Mais cela montre clairement que c'était difficile même à ce moment-là.
Luaan

1
Bonne réponse, bien que j'aimerais souligner que vous n'avez pas à compter le nombre de cycles pour chaque instruction sur l'Atari 2600. Il a deux fonctionnalités pour vous aider à ne pas avoir à le faire: un compte à rebours que vous initialisez et puis interrogez pour voir s'il a atteint 0, et un registre qui arrête le CPU jusqu'au début de la prochaine suppression horizontale. De nombreux autres appareils, comme le ZX Spectrum, n'ont rien de tel, et vous devez réellement compter chaque cycle passé après l'interruption de suppression verticale pour savoir où vous êtes sur l'écran.
Martin Vilcans

1
Je dirais que le problème de l'arrêt ne s'applique pas strictement à l'Atari. Si vous excluez les capacités d'E / S de l'Atari et que vous la limitez à une ROM de cartouche typique, il y a une quantité de stockage limitée. À ce moment-là, vous avez une machine à états finis, donc tout programme dessus doit soit s'arrêter, soit entrer dans un état dans lequel il est entré auparavant, conduisant à une boucle infinie prouvable en temps fini.
user1937198

2
@ user1937198 128 octets d'état (plus tout ce qui se trouve dans les registres) est PLUS qu'un espace d'état suffisant pour faire la différence entre cela et la bande infinie théorique de la machine de Turing une distinction qui n'a d'importance qu'en théorie. Enfer, nous ne pouvons pratiquement pas rechercher les 128 BITS de quelque chose comme une clé AES .... L'espace d'état augmente rapidement lorsque vous ajoutez des bits. N'oubliez pas que l'équivalent de «Désactiver les interruptions; l'arrêt aurait été presque certainement possible.
Dan Mills

1
"c'est le problème de l'arrêt, qui s'est avéré insoluble. Si vous rencontrez cela, alors vous devez sortir le chronomètre et exécuter votre code." - Cela n'a aucun sens. Vous ne pouvez pas échapper à la preuve de Turing en exécutant "réellement" le code au lieu de le simuler. S'il s'arrête, vous pouvez chronométrer le temps qu'il faut pour arrêter. Si cela ne s'arrête pas, vous ne pouvez jamais être sûr (en général) si cela va s'arrêter à l'avenir ou durer indéfiniment. C'est le même problème avec un chronomètre réel ou simulé. Au moins dans une simulation, vous pouvez plus facilement inspecter l'état interne pour des signes de boucle.
benrg

15

Il y a deux aspects en jeu ici

Comme le souligne @ gnasher729, si nous connaissons les instructions exactes à exécuter, il est toujours difficile d'estimer le temps d'exécution exact en raison de choses comme la mise en cache, la prédiction de branche, la mise à l'échelle, etc.

Cependant, la situation est encore pire. Étant donné un bloc d'assemblage, il est impossible de savoir quelles instructions seront exécutées, ni même combien d'instructions seront exécutées. C'est à cause du théorème de Rice: si nous pouvions le déterminer précisément, alors nous pourrions utiliser cette information pour résoudre le problème de l'arrêt, ce qui est impossible.

Le code assembleur peut contenir des sauts et des branches, ce qui est suffisant pour rendre la trace complète d'un programme infinie. Il y a eu des travaux sur des approximations conservatrices du temps d'exécution, qui donnent des limites supérieures à l'exécution, à travers des choses comme la sémantique des coûts ou les systèmes de type annotés. Je ne connais rien de spécifiquement pour l'assemblage mais je ne serais pas surpris si quelque chose comme ça existait.


4
Je veux dire, le problème de l'arrêt s'applique directement ici, car si nous connaissions le temps d'exécution, nous saurions s'il s'arrête. De plus, le fait qu'il n'y ait pas de conditions n'aide même pas ici, car en x86, movTuring-Complete
BlueRaja - Danny Pflughoeft

7
Rice et le problème de l'arrêt sont des déclarations sur des programmes arbitraires (n'importe lesquels) - mais l'OP ici a spécifié un morceau de code spécifique dans la question. Vous pouvez déterminer les propriétés sémantiques et d'arrêt des catégories individuelles ou limitées de programmes, non? C'est juste qu'il n'y a pas de procédure générale qui couvre tous les programmes.
Daniel R. Collins,

2
Nous pouvons définitivement savoir quelle instruction sera exécutée ensuite, ce que nous ne pouvons pas dire, c'est si nous frappons un sys_exitet arrêtons ainsi le chronomètre. Si nous nous limitons à mettre fin aux programmes, ce qui est raisonnable pour une question aussi pratique, alors la réponse est en fait oui (à condition que vous ayez un instantané parfait de l'état, hw et sw, du système juste avant de démarrer le programme).
Margaret Bloom

1
@ BlueRaja-DannyPflughoeft Mov est complet, mais pas dans le code que l'OP a ici. Mais c'est d'ailleurs le point de toute façon - les ints peuvent exécuter du code arbitraire, attendre des opérations d'E / S arbitraires, etc.
Luaan

2

Le choix du "système informatique" comprendrait-il des microcontrôleurs? Certains microcontrôleurs ont des temps d'exécution très prévisibles, par exemple, la série PIC 8 bits a quatre cycles d'horloge par instruction, à moins que l'instruction ne se ramifie vers une adresse différente, ne lise à partir du flash ou ne soit une instruction spéciale de deux mots.

Les interruptions perturberont de manière évidente ce type de timimg mais il est possible de faire beaucoup sans gestionnaire d'interruption dans une configuration "bare metal".

En utilisant l'assemblage et un style de codage spécial, il est possible d'écrire du code qui prendra toujours le même temps à exécuter. Ce n'est plus si courant maintenant que la plupart des variantes PIC ont plusieurs temporisateurs, mais c'est possible.


2

À l'époque des ordinateurs 8 bits, certains jeux ont fait quelque chose comme ça. Les programmeurs utiliseraient le temps exact qu'il a fallu pour exécuter les instructions, en fonction du temps qu'ils ont pris et de la vitesse d'horloge connue du processeur, pour se synchroniser avec les synchronisations exactes du matériel vidéo et audio. À l'époque, l'écran était un moniteur à tube cathodique qui parcourait chaque ligne d'écran à un taux fixe et peignait cette rangée de pixels en allumant et éteignant le rayon cathodique pour activer ou désactiver les luminophores. Parce que les programmeurs devaient dire au matériel vidéo ce qu'il fallait afficher juste avant que le faisceau n'atteigne cette partie de l'écran, et adapter le reste du code au temps restant, ils ont appelé cela "courir le faisceau".

Cela ne fonctionnerait absolument pas sur un ordinateur moderne ou pour du code comme votre exemple.

Pourquoi pas? Voici quelques éléments qui gâcheraient le timing simple et prévisible:

La vitesse du processeur et les récupérations de mémoire sont deux goulots d'étranglement sur le temps d'exécution. C'est un gaspillage d'argent d'exécuter un processeur plus rapidement qu'il ne peut récupérer les instructions à exécuter, ou d'installer une mémoire qui peut fournir des octets plus rapidement que le processeur ne peut les accepter. Pour cette raison, les anciens ordinateurs fonctionnaient tous les deux sur la même horloge. Les processeurs modernes fonctionnent beaucoup plus rapidement que la mémoire principale. Ils gèrent cela en disposant d'instructions et de caches de données. Le CPU sera toujours bloqué s'il a besoin d'attendre des octets qui ne sont pas dans le cache. Les mêmes instructions s'exécuteront donc beaucoup plus rapidement si elles sont déjà dans le cache que si elles ne le sont pas.

De plus, les processeurs modernes ont de longs pipelines. Ils maintiennent leur débit élevé en demandant à une autre partie de la puce d'effectuer un travail préliminaire sur les prochaines instructions du pipeline. Cela échouera si le CPU ne sait pas quelle sera la prochaine instruction, ce qui peut se produire s'il y a une branche. Par conséquent, les processeurs essaient de prédire les sauts conditionnels. (Vous n'en avez pas dans cet extrait de code, mais il y a peut-être eu un saut conditionnel imprévu qui a obstrué le pipeline. En outre, bonne excuse pour lier cette réponse légendaire.) De même, les systèmes qui appellent int 80pour intercepter en mode noyau utilisent une fonction CPU compliquée, une porte d'interruption, qui introduit un retard imprévisible.

Si votre système d'exploitation utilise le multitâche préemptif, le thread exécutant ce code peut perdre sa tranche de temps à tout moment.

La course à la poutre ne fonctionnait également que parce que le programme fonctionnait sur le métal nu et frappait directement sur le matériel. Ici, tu appellesint 80 pour passer un appel système. Cela passe le contrôle au système d'exploitation, ce qui ne vous donne aucune garantie de synchronisation. Vous lui dites ensuite d'effectuer des E / S sur un flux arbitraire, qui peut avoir été redirigé vers n'importe quel périphérique. Il est beaucoup trop abstrait pour vous de dire combien de temps les E / S prennent, mais cela dominera sûrement le temps passé à exécuter les instructions.

Si vous voulez une synchronisation exacte sur un système moderne, vous devez introduire une boucle de retard. Vous devez exécuter les itérations les plus rapides à la vitesse de la plus lente, l'inverse n'étant pas possible. L'une des raisons pour lesquelles les gens le font dans le monde réel est d'empêcher la fuite d'informations cryptographiques à un attaquant qui peut chronométrer les demandes plus longtemps que les autres.


1

C'est quelque peu tangentiel mais la navette spatiale avait 4 ordinateurs redondants qui dépendaient d'être synchronisés avec précision, c'est-à-dire que leur temps d'exécution correspond exactement.

La toute première tentative de lancement de la navette spatiale a été annulée lorsque l'ordinateur Backup Flight Software (BFS) a refusé de se synchroniser avec les quatre ordinateurs PASS (Primary Avionics Software System). Détails dans "Le bug entendu autour du monde" ici . Une lecture fascinante sur la façon dont le logiciel a été développé pour correspondre cycle à cycle et pourrait vous donner un arrière-plan intéressant.


0

Je pense que nous mélangeons deux problèmes différents ici. (Et oui, je sais que cela a été dit par d'autres, mais j'espère pouvoir l'exprimer plus clairement.)

Nous devons d'abord passer du code source à la séquence d'instructions qui est réellement exécutée (qui nécessite la connaissance des données d'entrée ainsi que du code - combien de fois faites-vous le tour d'une boucle? Quelle branche est prise après un test? ). En raison du problème d'arrêt, la séquence d'instructions peut être infinie (non-terminaison) et vous ne pouvez pas toujours le déterminer statiquement, même avec la connaissance des données d'entrée.

Après avoir établi la séquence d'instructions à exécuter, vous souhaitez ensuite déterminer le temps d'exécution. Cela peut certainement être estimé avec une certaine connaissance de l'architecture du système. Mais le problème est que sur de nombreuses machines modernes, le temps d'exécution dépend fortement de la mise en cache des récupérations de mémoire, ce qui signifie qu'il dépend autant des données d'entrée que des instructions exécutées. Cela dépend également de la supposition correcte des destinations de branchement conditionnelles, qui dépend à nouveau des données. Ce ne sera donc qu'une estimation, ce ne sera pas exact.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.