to jest fragment kodu asemblera
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
Biorąc pod uwagę konkretny system komputerowy, czy można dokładnie przewidzieć rzeczywisty czas działania fragmentu kodu asemblera.
Odpowiedzi:
Mogę jedynie zacytować z podręcznika dość prymitywnego procesora, procesora 68020 z około 1986 roku: „Obliczenie dokładnego czasu wykonywania sekwencji instrukcji jest trudne, nawet jeśli masz dokładną wiedzę na temat implementacji procesora”. Których nie mamy. W porównaniu z nowoczesnym procesorem ten procesor był prymitywny .
Nie mogę przewidzieć czasu działania tego kodu i ty też nie. Ale nie można nawet zdefiniować, co to jest „środowisko wykonawcze” fragmentu kodu, gdy procesor ma ogromne pamięci podręczne i potężne funkcje poza kolejnością. Typowy nowoczesny procesor może mieć 200 instrukcji „w locie”, czyli na różnych etapach wykonania. Czas od próby odczytania pierwszego bajtu instrukcji do wycofania ostatniej instrukcji może być dość długi. Ale faktyczne opóźnienie wszystkich innych zadań, które procesor musi wykonać, może być (i zwykle jest) znacznie mniejsze.
Oczywiście wykonanie dwóch połączeń z systemem operacyjnym czyni to całkowicie nieprzewidywalnym. Nie wiesz, co właściwie robi „pisanie na standardowe wyjście”, więc nie możesz przewidzieć czasu.
I nie możesz znać szybkości zegara komputera w momencie uruchomienia kodu. Może być w pewnym trybie oszczędzania energii, komputer może mieć zmniejszoną prędkość zegara, ponieważ się rozgrzał, więc nawet ta sama liczba cykli zegara może zająć różną ilość czasu.
Podsumowując: Całkowicie nieprzewidywalny.
źródło
Nie możesz tego ogólnie robić, ale pod pewnymi względami bardzo możesz, i było kilka historycznych przypadków, w których naprawdę musiałeś .
Atari 2600 (lub Atari wideo Computer System) był jednym z pierwszych systemów do gier wideo do domu i został po raz pierwszy wydany w 1978 roku W przeciwieństwie do późniejszych systemach ery Atari nie może sobie pozwolić, aby dać urządzeniem z bufora ramki, co oznacza, że procesor miał aby uruchomić kod na każdej linii skanowania, aby określić, co ma zostać wyprodukowane - jeśli ten kod zabrałby 17.08 mikrosekund do uruchomienia (interwał HBlank), grafika nie byłaby odpowiednio ustawiona, zanim linia skanowania zacznie je rysować. Co gorsza, jeśli programista chciał narysować bardziej złożoną zawartość niż normalnie zezwalały Atari, musiał mierzyć dokładne czasy instrukcji i zmieniać rejestry graficzne podczas rysowania wiązki, z rozpiętością 57,29 mikrosekund dla całej linii skanowania.
Jednak Atari 2600, podobnie jak wiele innych systemów opartych na 6502, miał bardzo ważną funkcję, która umożliwiła staranne zarządzanie czasem wymagane w tym scenariuszu: procesor, pamięć RAM i sygnał telewizyjny działały w oparciu o zegary oparte na tym samym urządzeniu głównym zegar. Sygnał telewizyjny pobiegł z zegarem 3,98 MHz, dzieląc powyższe czasy na całkowitą liczbę „kolorowych zegarów”, które zarządzały sygnałem telewizyjnym, a cykl procesora i zegarów pamięci RAM wynosił dokładnie trzy kolorowe zegary, co pozwala na ustawienie zegara procesora dokładna miara czasu w stosunku do bieżącego sygnału telewizyjnego postępu. (Aby uzyskać więcej informacji na ten temat, zapoznaj się z Przewodnikiem programisty Stella , napisanym dla emulatora Stella Atari 2600 ).
Ponadto to środowisko operacyjne oznaczało, że każda instrukcja CPU miała określoną liczbę cykli, którą zajmie w każdym przypadku, a wielu programistów 6502 opublikowało te informacje w tabelach referencyjnych. Rozważmy na przykład ten wpis instrukcji
CMP
(Porównaj pamięć z akumulatorem) zaczerpnięty z tej tabeli :Wykorzystując wszystkie te informacje, Atari 2600 (i inni programiści 6502) byli w stanie dokładnie określić, ile czasu zajmuje ich wykonanie kodu, i zbudować procedury, które zrobiły to, czego potrzebowały i nadal spełniały wymagania dotyczące czasu sygnału telewizyjnego Atari. A ponieważ czas był tak dokładny (szczególnie w przypadku marnujących czas instrukcji, takich jak NOP), byli w stanie nawet użyć go do modyfikacji grafiki podczas rysowania.
Oczywiście 6502 Atari jest bardzo specyficznym przypadkiem, a wszystko to jest możliwe tylko dlatego, że system miał wszystkie następujące elementy:
Wszystkie te rzeczy połączyły się, aby stworzyć system, w którym można było stworzyć zestawy instrukcji, które zajęły dokładnie tyle czasu - i do tego zastosowania dokładnie tego wymagano. Większość systemów nie ma tego stopnia precyzji po prostu dlatego, że nie jest to konieczne - obliczenia albo są wykonywane, gdy są one wykonane, albo jeśli potrzebna jest dokładna ilość czasu, można zapytać o niezależny zegar. Ale jeśli potrzeba jest odpowiednia (na przykład w niektórych systemach wbudowanych), może się nadal pojawiać, a Ty będziesz w stanie dokładnie określić, ile czasu zajmuje uruchomienie kodu w tych środowiskach.
Powinienem również dodać duże, masowe zastrzeżenie, że wszystko to dotyczy tylko konstruowania zestawu instrukcji asemblacyjnych, które zajmą dokładnie tyle czasu. Jeśli chcesz zrobić dowolny zestaw, nawet w tych środowiskach, i zapytać: „Jak długo trwa to wykonanie?”, Kategorycznie nie możesz tego zrobić - to jest problem zatrzymania , który okazał się nierozwiązywalny.
EDYCJA 1: W poprzedniej wersji tej odpowiedzi stwierdziłem, że Atari 2600 nie ma możliwości poinformowania procesora o tym, gdzie jest w sygnale telewizyjnym, co zmusiło go do utrzymania liczenia i synchronizacji całego programu od samego początku. Jak wskazano mi w komentarzach, dotyczy to niektórych systemów, takich jak ZX Spectrum, ale nie jest tak w przypadku Atari 2600, ponieważ zawiera rejestr sprzętowy, który zatrzymuje procesor do momentu wystąpienia następnego interwału wygaszania poziomego, a także funkcja pozwalająca na rozpoczęcie pionowego interwału wygaszania do woli. W związku z tym problem zliczania cykli jest ograniczony do każdej linii skanowania i staje się dokładny tylko wtedy, gdy programista chce zmienić zawartość podczas rysowania linii skanowania.
źródło
Grają tutaj dwa aspekty
Jak wskazuje @ gnasher729, jeśli znamy dokładne instrukcje do wykonania, nadal trudno jest oszacować dokładny czas działania z powodu takich rzeczy, jak buforowanie, przewidywanie gałęzi, skalowanie itp.
Sytuacja jest jednak jeszcze gorsza. Biorąc pod uwagę część zestawu, nie można wiedzieć, które instrukcje zostaną uruchomione, ani nawet wiedzieć, ile instrukcji będzie działać. Wynika to z twierdzenia Rice'a: gdybyśmy mogli to dokładnie ustalić, moglibyśmy wykorzystać te informacje do rozwiązania problemu zatrzymania, co jest niemożliwe.
Kod asemblera może zawierać skoki i rozgałęzienia, które wystarczą, aby pełny ślad programu był nieskończony. Pracowano nad konserwatywnymi przybliżeniami czasu wykonania, które dają górne granice wykonania, poprzez takie rzeczy jak semantyka kosztów lub systemy typów z adnotacjami. Nie znam się na konkretnym montażu, ale nie zdziwiłbym się, gdyby coś takiego istniało.
źródło
mov
jest Turing-Completesys_exit
a tym samym zatrzymamy stoper. Jeśli ograniczymy się do zamykania programów, co jest uzasadnione w przypadku takiego praktycznego pytania, wówczas odpowiedź jest w rzeczywistości tak (pod warunkiem, że masz doskonałą migawkę stanu, hw i sw, systemu tuż przed uruchomieniem programu).int
s mogą wykonywać dowolny kod, czekać na dowolne operacje we / wy itp.Czy wybór „systemu komputerowego” obejmowałby mikrokontrolery? Niektóre mikrokontrolery mają bardzo przewidywalne czasy wykonania, na przykład 8-bitowa seria PIC ma cztery cykle zegarowe na instrukcję, chyba że instrukcja rozgałęzia się na inny adres, czyta z pamięci flash lub jest specjalną instrukcją składającą się z dwóch słów.
Przerwania będą oczywiście zakłócać tego rodzaju timimg, ale można wiele zrobić bez obsługi programu obsługi przerwań w konfiguracji „bez metalu”.
Używając asemblera i specjalnego stylu kodowania, można napisać kod, którego wykonanie zawsze zajmie tyle samo czasu. Teraz nie jest tak powszechne, że większość wariantów PIC ma wiele timerów, ale jest to możliwe.
źródło
W erze komputerów 8-bitowych niektóre gry zrobiły coś takiego. Programiści wykorzystali dokładny czas potrzebny na wykonanie instrukcji, w oparciu o czas, jaki zajęli i znaną częstotliwość taktowania procesora, aby zsynchronizować się z dokładnymi czasami sprzętu wideo i audio. W tamtych czasach wyświetlacz był monitorem z lampą elektronopromieniową, który cyklicznie przesuwał się po każdej linii ekranu ze stałą szybkością i malował ten rząd pikseli poprzez włączanie i wyłączanie promienia katodowego w celu aktywacji lub dezaktywacji luminoforów. Ponieważ programiści musieli powiedzieć sprzętowi wideo, co wyświetlać tuż przed dotarciem wiązki do tej części ekranu, i dopasować resztę kodu do pozostałego czasu, dlatego nazwali to „ściganiem wiązki”.
Absolutnie nie działałoby na żadnym nowoczesnym komputerze ani w kodzie takim jak twój przykład.
Dlaczego nie? Oto kilka rzeczy, które zepsułyby prosty, przewidywalny czas:
Szybkość procesora i pobieranie pamięci są wąskimi gardłami w czasie wykonywania. Stratą pieniędzy jest szybsze uruchamianie procesora niż jest w stanie pobrać instrukcje do wykonania lub zainstalować pamięć, która może dostarczyć bajty szybciej, niż procesor może je zaakceptować. Z tego powodu stare komputery działały na tym samym zegarze. Nowoczesne procesory działają znacznie szybciej niż pamięć główna. Zarządzają tym dzięki pamięci podręcznej instrukcji i danych. Procesor nadal się zatrzymuje, jeśli kiedykolwiek będzie musiał czekać na bajty, które nie znajdują się w pamięci podręcznej. Te same instrukcje będą zatem działać znacznie szybciej, jeśli są już w pamięci podręcznej, niż jeśli ich nie ma.
Ponadto nowoczesne procesory mają długie potoki. Utrzymują wysoką przepustowość, zlecając kolejną część układu wstępnym pracom nad kolejnymi kilkoma instrukcjami w przygotowaniu. To się nie powiedzie, jeśli CPU nie wie, jaka będzie następna instrukcja, co może się zdarzyć, jeśli istnieje gałąź. Dlatego procesory próbują przewidzieć skoki warunkowe. (Nie masz żadnego w tym fragmencie kodu, ale być może był to nieprzewidziany warunkowy skok, który zablokował potok. Poza tym dobra wymówka, aby połączyć tę legendarną odpowiedź.) Podobnie, systemy, które wzywają
int 80
pułapkę do trybu jądra używają skomplikowanej funkcji procesora, bramki przerwań, która wprowadza nieprzewidziane opóźnienie.Jeśli system operacyjny korzysta z zapobiegawczej wielozadaniowości, wątek uruchamiający ten kod może w dowolnym momencie utracić swój przedział czasu.
Ściganie wiązki działało również tylko dlatego, że program działał na goły metal i uderzył bezpośrednio w sprzęt. Tutaj dzwonisz,
int 80
aby wykonać połączenie systemowe. To przekazuje kontrolę systemowi operacyjnemu, co nie daje żadnej gwarancji czasu. Następnie powiesz mu, że wykonuje operacje we / wy w dowolnym strumieniu, który mógł zostać przekierowany na dowolne urządzenie. To zbyt abstrakcyjne, żebyś mógł powiedzieć, ile czasu zajmuje We / Wy, ale z pewnością zdominuje czas spędzony na wykonywaniu instrukcji.Jeśli chcesz dokładnego pomiaru czasu w nowoczesnym systemie, musisz wprowadzić pętlę opóźnienia. Musisz sprawić, by szybsze iteracje przebiegały z prędkością najwolniejszą, a odwrotność nie jest możliwa. Jednym z powodów, dla których ludzie to robią w prawdziwym świecie, jest zapobieganie wyciekaniu informacji kryptograficznych do osoby atakującej, która może czas, którego żądania zajmują dłużej niż inne.
źródło
Jest to nieco styczne, ale wahadłowiec kosmiczny miał 4 nadmiarowe komputery, które polegały na dokładnej synchronizacji, tzn. Dokładnie dopasowanym czasie działania.
Pierwsza próba uruchomienia promu kosmicznego została oczyszczona, gdy komputer Backup Flight Software (BFS) odmówił synchronizacji z czterema komputerami Primary Avionics Software System (PASS). Szczegóły w „The Bug Heard Round the World” tutaj . Fascynująca lektura na temat tego, jak oprogramowanie zostało opracowane, aby dopasować cykl do cyklu i może dać interesujące tło.
źródło
Myślę, że mieszamy tutaj dwie różne kwestie. (I tak, wiem, że powiedzieli to inni, ale mam nadzieję, że mogę to wyrazić jaśniej).
Najpierw musimy przejść od kodu źródłowego do sekwencji faktycznie wykonanych instrukcji (która wymaga znajomości danych wejściowych, a także kodu - ile razy okrążasz pętlę? Którą gałąź wykonuje się po teście? ). Z powodu problemu zatrzymania sekwencja instrukcji może być nieskończona (nieterminacja) i nie zawsze można to ustalić statycznie, nawet przy znajomości danych wejściowych.
Po ustaleniu sekwencji instrukcji do wykonania, należy następnie określić czas wykonania. Można to z pewnością oszacować przy pewnej wiedzy na temat architektury systemu. Problem polega jednak na tym, że na wielu współczesnych maszynach czas wykonania zależy w dużej mierze od buforowania pobrań pamięci, co oznacza, że zależy zarówno od danych wejściowych, jak i od wykonanych instrukcji. Zależy to również od prawidłowego odgadnięcia warunkowych miejsc docelowych oddziałów, które ponownie zależą od danych. To będzie tylko szacunek, nie będzie dokładny.
źródło