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.
Réponses:
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.
la source
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'
CMP
instruction (Comparer la mémoire avec l'accumulateur), extraite de ce tableau :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:
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.
la source
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.
la source
mov
Turing-Completesys_exit
et 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).int
s peuvent exécuter du code arbitraire, attendre des opérations d'E / S arbitraires, etc.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.
la source
À 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 80
pour 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 appelles
int 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.
la source
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.
la source
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.
la source