Dies ist ein Teil des Assembly-Codes
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
Bei einem bestimmten Computersystem ist es möglich, die tatsächliche Laufzeit eines Assembly-Codes genau vorherzusagen.
Antworten:
Ich kann nur aus dem Handbuch einer eher primitiven CPU, eines 68020-Prozessors aus der Zeit um 1986, zitieren: "Die exakte Berechnung der Laufzeit einer Befehlsfolge ist schwierig, auch wenn Sie genaue Kenntnisse über die Prozessorimplementierung haben." Was wir nicht haben. Und im Vergleich zu einem modernen Prozessor war diese CPU primitiv .
Ich kann die Laufzeit dieses Codes nicht vorhersagen, und Sie können es auch nicht. Aber Sie können nicht einmal definieren, was "Laufzeit" eines Codeteils ist, wenn ein Prozessor über massive Caches und massive Funktionen für fehlerhafte Reihenfolge verfügt. Ein typischer moderner Prozessor kann 200 Anweisungen "im Flug" haben, dh in verschiedenen Phasen der Ausführung. Die Zeit vom Versuch, das erste Befehlsbyte zu lesen, bis zur Beendigung des letzten Befehls kann also ziemlich lang sein. Die tatsächliche Verzögerung für alle anderen Arbeiten, die der Prozessor ausführen muss, ist jedoch möglicherweise (und in der Regel) viel geringer.
Durch zwei Aufrufe des Betriebssystems ist dies natürlich völlig unvorhersehbar. Sie wissen nicht, was "Schreiben an stdout" tatsächlich bewirkt, daher können Sie die Zeit nicht vorhersagen.
Und Sie können die Taktrate des Computers nicht genau in dem Moment kennen, in dem Sie den Code ausführen. Es kann sein, dass sich der Computer in einem Energiesparmodus befindet und die Taktrate aufgrund der Hitze verringert ist, sodass selbst die gleiche Anzahl von Taktzyklen unterschiedlich viel Zeit in Anspruch nehmen kann.
Alles in allem: Völlig unvorhersehbar.
quelle
Sie können dies im Allgemeinen nicht tun, aber in gewisser Hinsicht können Sie sehr viel, und es gab einige historische Fälle, in denen Sie tatsächlich mussten .
Der Atari 2600 (oder Atari Video Computer System) war eines der frühesten Heimvideospielsysteme und wurde erstmals 1978 veröffentlicht. Im Gegensatz zu späteren Systemen der damaligen Zeit konnte Atari es sich nicht leisten, dem Gerät einen Bildpuffer zu geben, was bedeutet, dass die CPU dies hatte Code an jeder Scanlinie ausführen, um zu bestimmen, was zu produzieren ist - wenn dieser Code mehr als 17,08 Mikrosekunden zum Ausführen benötigt (das HBlank-Intervall), werden die Grafiken nicht richtig eingestellt, bevor die Scanlinie mit dem Zeichnen beginnt. Schlimmer noch, wenn der Programmierer komplexere Inhalte zeichnen wollte, als es der Atari normalerweise zuließ, musste er die genauen Zeiten für Anweisungen messen und die Grafikregister während des Zeichnens des Strahls mit einer Zeitspanne von 57,29 Mikrosekunden für die gesamte Scanlinie ändern.
Der Atari 2600 hatte jedoch wie viele andere Systeme auf Basis des 6502 eine sehr wichtige Funktion, die ein sorgfältiges Zeitmanagement ermöglichte, das für dieses Szenario erforderlich war: Die CPU, der RAM und das TV-Signal liefen alle mit Uhren, die auf demselben Master basierten Uhr. Das TV-Signal lief von einem 3,98-MHz-Takt ab, wobei die obigen Zeiten in eine ganze Zahl von "Farbtakten" aufgeteilt wurden, die das TV-Signal handhabten, und ein Zyklus der CPU- und RAM-Takte bestand aus genau drei Farbtakten, so dass der CPU-Takt erhalten blieb ein genaues Zeitmaß in Bezug auf das aktuelle TV-Fortschrittssignal. (Weitere Informationen hierzu finden Sie im Stella-Programmierhandbuch , das für den Emulator Stella Atari 2600 geschrieben wurde. )
Diese Betriebsumgebung bedeutete außerdem, dass jeder CPU-Befehl in jedem Fall eine bestimmte Anzahl von Zyklen benötigte, und viele 6502-Entwickler veröffentlichten diese Informationen in Referenztabellen. Betrachten Sie zum Beispiel diesen Eintrag für die
CMP
Anweisung (Speicher mit Akku vergleichen) aus der folgenden Tabelle :Mit all diesen Informationen konnten Atari 2600 (und andere 6502-Entwickler) genau bestimmen, wie lange die Ausführung ihres Codes dauerte, und Routinen erstellen, die genau das taten, was sie brauchten und dennoch die Timing-Anforderungen des Atari für TV-Signale erfüllten. Und weil dieses Timing so genau war (insbesondere für zeitraubende Anweisungen wie NOP), konnten sie es sogar verwenden, um die Grafiken beim Zeichnen zu ändern.
Natürlich ist der 6502 des Atari ein sehr spezieller Fall, und all dies ist nur möglich, weil das System über Folgendes verfügt:
All diese Dinge haben sich zu einem System zusammengeschlossen, in dem es möglich war, Anweisungen zu erstellen, die genau so viel Zeit in Anspruch nahmen - und für diese Anwendung war genau das erforderlich. Die meisten Systeme verfügen nicht über diese Genauigkeit, nur weil sie nicht benötigt werden. Berechnungen werden entweder ausgeführt, wenn sie ausgeführt werden, oder wenn eine genaue Zeitdauer erforderlich ist, kann eine unabhängige Uhr abgefragt werden. Wenn die Anforderungen jedoch erfüllt sind (z. B. auf einigen eingebetteten Systemen), wird dies möglicherweise weiterhin angezeigt, und Sie können genau bestimmen, wie lange der Code in diesen Umgebungen ausgeführt werden muss.
Und ich möchte noch den großen, massiven Haftungsausschluss hinzufügen, dass all dies nur für die Erstellung einer Reihe von Montageanleitungen gilt, die eine genaue Zeitspanne in Anspruch nehmen. Wenn das, was wollen Sie einige beliebige Stück Montage tun nehmen, auch in diesen Umgebungen und fragen : „Wie lange dauert diese nehmen ausführen“ können Sie kategorisch das nicht tun - das ist das ist Halting Problem , das unlösbare bewiesen wurde.
EDIT 1: In einer früheren Version dieser Antwort gab ich an, dass der Atari 2600 keine Möglichkeit hatte, den Prozessor darüber zu informieren, wo er sich im TV-Signal befand, was ihn zwang, das gesamte Programm von Anfang an gezählt und synchronisiert zu halten. Wie in den Kommentaren erwähnt, gilt dies für einige Systeme wie das ZX Spectrum, nicht jedoch für das Atari 2600, da es ein Hardware-Register enthält, das die CPU anhält, bis das nächste horizontale Austastintervall auftritt eine Funktion, um das vertikale Austastintervall nach Belieben zu starten. Daher ist das Problem des Zählens von Zyklen auf jede Scanlinie beschränkt und wird nur dann genau, wenn der Entwickler den Inhalt ändern möchte, während die Scanlinie gezeichnet wird.
quelle
Hier spielen zwei Aspekte eine Rolle
Wie @ gnasher729 betont, ist es immer noch schwierig, die exakte Laufzeit zu schätzen, wenn wir die genauen auszuführenden Anweisungen kennen, beispielsweise aufgrund von Caching, Verzweigungsvorhersage, Skalierung usw.
Die Situation ist jedoch noch schlimmer. Bei einem Teil der Assembly ist es unmöglich zu wissen, welche Anweisungen ausgeführt werden oder wie viele Anweisungen ausgeführt werden. Dies liegt am Satz von Rice: Wenn wir das genau bestimmen könnten, könnten wir diese Informationen verwenden, um das Halteproblem zu lösen, das unmöglich ist.
Assembly-Code kann Sprünge und Verzweigungen enthalten, die ausreichen, um die vollständige Ablaufverfolgung eines Programms möglicherweise unendlich zu machen. Es wurde an konservativen Annäherungen der Ausführungszeit gearbeitet, die Obergrenzen für die Ausführung ergeben, beispielsweise durch Kostensemantik oder kommentierte Typsysteme. Ich kenne nichts spezielles für die Montage, aber ich wäre nicht überrascht, wenn es so etwas gäbe.
quelle
mov
Turing-Completesys_exit
drücken und somit die Stoppuhr anhalten. Wenn wir uns auf das Beenden von Programmen beschränken, was für eine solche praktische Frage angemessen ist, lautet die Antwort tatsächlich Ja (vorausgesetzt, Sie haben eine perfekte Momentaufnahme des Zustands, hw und sw, des Systems, kurz bevor Sie das Programm starten).int
s können beliebigen Code ausführen, auf beliebige I / O-Operationen warten usw.Würde die Wahl des "Computersystems" Mikrocontroller beinhalten? Einige Mikrocontroller haben sehr vorhersagbare Ausführungszeiten, zum Beispiel haben die 8-Bit-PIC-Reihen vier Taktzyklen pro Befehl, es sei denn, der Befehl verzweigt zu einer anderen Adresse, liest aus dem Flash oder ist ein spezieller Zwei-Wort-Befehl.
Interrupts unterbrechen diese Art von Timimg offensichtlich, aber es ist möglich, in einer "Bare-Metal" -Konfiguration viel ohne einen Interrupt-Handler zu tun.
Mit Hilfe von Assembly und einem speziellen Codierungsstil ist es möglich, Code zu schreiben, dessen Ausführung immer die gleiche Zeit in Anspruch nimmt. Es ist nicht mehr so verbreitet, dass die meisten PIC-Varianten mehrere Timer haben, aber es ist möglich.
quelle
In der Ära der 8-Bit-Computer haben einige Spiele so etwas getan. Programmierer verwenden die genaue Zeit, die für die Ausführung von Befehlen benötigt wird, basierend auf der Zeit, die sie benötigt haben, und der bekannten Taktrate der CPU, um sich mit den genauen Timings der Video- und Audio-Hardware zu synchronisieren. Damals war das Display ein Kathodenstrahlröhrenmonitor, der jede Bildschirmzeile mit einer festgelegten Geschwindigkeit abwechselte und diese Pixelreihe durch Ein- und Ausschalten des Kathodenstrahls zum Aktivieren oder Deaktivieren der Leuchtstoffe malte. Da Programmierer der Videohardware mitteilen mussten, was angezeigt werden soll, bevor der Strahl diesen Teil des Bildschirms erreichte, und den Rest des Codes in die verbleibende Zeit einpassen mussten, nannten sie dies „Rennen um den Strahl“.
Es würde auf keinem modernen Computer oder für Code wie Ihrem Beispiel absolut funktionieren.
Warum nicht? Hier sind einige Dinge, die das einfache, vorhersehbare Timing durcheinander bringen würden:
CPU-Geschwindigkeit und Speicherabruf sind beide Engpässe bei der Ausführungszeit. Es ist eine Geldverschwendung, eine CPU schneller laufen zu lassen, als Anweisungen zum Ausführen abgerufen werden können, oder Speicher zu installieren, der Bytes schneller liefern kann, als die CPU sie akzeptieren kann. Aus diesem Grund liefen alte Computer mit derselben Uhr. Moderne CPUs laufen wesentlich schneller als der Hauptspeicher. Sie verwalten das, indem sie Instruktions- und Datencaches haben. Die CPU bleibt immer noch stehen, wenn sie auf Bytes warten muss, die sich nicht im Cache befinden. Dieselben Anweisungen werden daher viel schneller ausgeführt, wenn sie sich bereits im Cache befinden, als wenn sie nicht vorhanden sind.
Darüber hinaus haben moderne CPUs lange Pipelines. Sie halten ihren hohen Durchsatz aufrecht, indem ein anderer Teil des Chips die nächsten Befehle in der Pipeline vorarbeitet. Dies schlägt fehl, wenn die CPU die nächste Anweisung nicht kennt. Dies kann passieren, wenn eine Verzweigung vorliegt. Daher versuchen CPUs , bedingte Sprünge vorherzusagen. (In diesem Code-Snippet ist kein Code-Snippet enthalten, aber möglicherweise gab es einen falsch vorhergesagten bedingten Sprung, der die Pipeline verstopfte. Außerdem eine gute Entschuldigung, um diese legendäre Antwort zu verknüpfen.) Ebenso die Systeme, die
int 80
tatsächlich aufrufen , um in den Kernel-Modus einzufangen verwenden eine komplizierte CPU-Funktion, ein Interrupt-Gate, das zu einer unvorhersehbaren Verzögerung führt.Wenn Ihr Betriebssystem preemptives Multitasking verwendet, kann der Thread, der diesen Code ausführt, jederzeit seine Zeitscheibe verlieren.
Racing the Beam funktionierte auch nur, weil das Programm auf dem Bare Metal lief und direkt auf die Hardware knallte. Hier rufen Sie
int 80
an, um einen Systemaufruf zu tätigen. Das übergibt die Kontrolle an das Betriebssystem, was Ihnen keine Zeitgarantie gibt. Anschließend teilen Sie ihm mit, dass E / A in einem beliebigen Stream ausgeführt werden soll, der möglicherweise auf ein beliebiges Gerät umgeleitet wurde. Es ist viel zu abstrakt, als dass Sie sagen könnten, wie viel Zeit die E / A benötigt, aber es wird sicherlich die Zeit dominieren, die für die Ausführung von Anweisungen aufgewendet wird.Wenn Sie in einem modernen System ein genaues Timing wünschen, müssen Sie eine Verzögerungsschleife einführen. Sie müssen dafür sorgen, dass die schnelleren Iterationen mit der Geschwindigkeit der langsamsten ausgeführt werden. Das Gegenteil ist nicht möglich. Ein Grund, warum dies in der realen Welt der Fall ist, besteht darin, zu verhindern, dass kryptografische Informationen an einen Angreifer weitergegeben werden, der die Zeit für diese Anforderungen länger als andere beanspruchen kann.
quelle
Dies ist etwas tangential, aber das Space Shuttle verfügte über 4 redundante Computer, bei denen es darauf ankam, dass sie genau synchronisiert wurden, dh dass ihre Laufzeit genau übereinstimmte.
Der allererste Startversuch des Space Shuttles wurde abgebrochen, als der Backup Flight Software-Computer (BFS) die Synchronisierung mit den vier PASS-Computern (Primary Avionics Software System) verweigerte. Details in "The Bug Heard Round the World" hier . Faszinierende Lektüre darüber, wie die Software entwickelt wurde, um Zyklus für Zyklus übereinzustimmen und Ihnen interessante Hintergrundinformationen zu geben.
quelle
Ich denke, wir mischen hier zwei verschiedene Themen. (Und ja, ich weiß, dass dies von anderen gesagt wurde, aber ich hoffe, ich kann es klarer ausdrücken.)
Zuerst müssen wir vom Quellcode zu der Abfolge von Anweisungen gelangen, die tatsächlich ausgeführt wird (die Kenntnis der Eingabedaten sowie des Codes erfordert - wie oft durchlaufen Sie eine Schleife? Welche Verzweigung wird nach einem Test ausgeführt?). ). Aufgrund des Halteproblems kann die Reihenfolge der Anweisungen unendlich sein (Nichtbeendigung), und Sie können dies auch bei Kenntnis der eingegebenen Daten nicht immer statisch feststellen.
Nachdem Sie die Reihenfolge der auszuführenden Anweisungen festgelegt haben, möchten Sie die Ausführungszeit bestimmen. Das kann man sicherlich mit einigem Wissen über die Systemarchitektur abschätzen. Das Problem ist jedoch, dass bei vielen modernen Maschinen die Ausführungszeit stark vom Caching von Speicherabrufen abhängt, was bedeutet, dass dies sowohl von den Eingabedaten als auch von den ausgeführten Befehlen abhängt. Es kommt auch darauf an, bedingte Verzweigungsziele richtig zu erraten, was wiederum datenabhängig ist. Es wird also nur eine Schätzung sein, es wird nicht genau sein.
quelle