2012-10-07 25 views
24

Pytanie:Koszt naciśnięciem vs. stosu mov (vs. najbliższej pamięci), i napowietrznej wywołania funkcji

korzysta stos z taką samą prędkością jak dostęp do pamięci?

Na przykład mógłbym wykonać pracę w stosie lub mógłbym pracować bezpośrednio z etykietowaną lokalizacją w pamięci.

Tak, konkretnie: czy push ax ma tę samą prędkość, co mov [bx], ax? Podobnie jest pop ax z tą samą szybkością, co mov ax, [bx]? (Zakładamy bx posiada lokalizację w near pamięci).

motywacja do pytanie:

powszechne jest w C, aby zniechęcić banalne funkcje, które mają parametry.

Zawsze uważałem, że to dlatego, że nie tylko parametry muszą zostać przekazane na stos, a następnie zrzucone ze stosu po powrocie funkcji, ale także dlatego, że samo wywołanie funkcji musi zachować kontekst procesora, co oznacza więcej stosu stosowanie.

Ale zakładając, że zna się odpowiedź na pytanie z nagłówka, powinno być możliwe określenie ilościowego obciążenia, które funkcja wykorzystuje do ustawienia się (kontekst push/pop/zachowania itp.) W kategoriach równoważnej liczby bezpośrednich dostęp do pamięci. Stąd naglące pytanie.


( Edit: Wyjaśnienie: near użyte powyżej jest w przeciwieństwie do far w segmented memory model z 16-bitowej architektury x86).

+5

Wow. Jestem odkrywcą. Właśnie znalazłem dobre, nie-n00b pytanie na StackOverflow. Świętowałem moje poszukiwania szampanem i przegłosowaniem! –

+1

Zawsze uważałem operacje dekompresowania/zwiększania wywołania za naciśnięcie/naciśnięcie na ESP jako obciążenie w porównaniu do mov .... ale myślę, że powinno być o wiele więcej. – loxxy

Odpowiedz

17

Obecnie Twój kompilator C może cię przechytrzyć. Może on wstawiać proste funkcje, a jeśli to robi, nie będzie wywoływania ani zwracania funkcji, i być może nie będzie żadnych dodatkowych manipulacji stosami związanych z przekazywaniem i dostępem do formalnych parametrów funkcji (lub równoważną operacją, gdy funkcja jest inline, ale dostępne rejestry są wyczerpane), jeśli wszystko może być wykonane w rejestrach lub, jeszcze lepiej, jeśli wynik jest wartością stałą i kompilator może to zobaczyć i wykorzystać.

Same wywołania funkcji mogą być stosunkowo tanie (ale niekoniecznie zerowe) na nowoczesnych procesorach, jeśli są powtarzane i jeśli istnieje osobna pamięć podręczna instrukcji i różne mechanizmy przewidywania, pomagając w wydajnym wykonywaniu kodu.

Poza tym, spodziewam się, że implikacje wydajności wyboru "lokalny var vs globalny var" zależą od wzorców użycia pamięci. Jeśli w procesorze znajduje się pamięć podręczna pamięci, stos prawdopodobnie znajduje się w tej pamięci podręcznej, chyba że przydzielisz i zwolnisz duże tablice lub struktury na niej lub będziesz mieć głębokie wywołania funkcji lub głęboką rekurencję, powodując chybienia pamięci podręcznej. Jeśli globalna zmienna będąca przedmiotem zainteresowania jest dostępna często lub jeśli jej sąsiedzi są często odwiedzani, spodziewałbym się, że ta zmienna będzie również w pamięci podręcznej przez większość czasu. Ponownie, jeśli uzyskujesz dostęp do dużej rozpiętości pamięci, która nie mieści się w pamięci podręcznej, będziesz mieć luki w pamięci podręcznej i prawdopodobnie zmniejszoną wydajność (być może dlatego, że może być lepszy, ale może nie, lepszy sposób, aby zrobić to, co Ty chcę zrobić).

Jeśli sprzęt jest dość głupi (brak bufora, brak podpowiedzi, brak porządkowania instrukcji, brak wykonywania spekulacji, nic), wyraźnie chcesz zmniejszyć ciśnienie w pamięci i liczbę wywołań funkcji, ponieważ każdy i każdy będzie liczyć .

Jeszcze innym czynnikiem jest długość instrukcji i dekodowanie. Instrukcje dostępu do położenia na stosie (względem wskaźnika stosu) mogą być krótsze niż instrukcje dostępu do dowolnej lokalizacji pamięci pod danym adresem. Krótsze instrukcje mogą być dekodowane i wykonywane szybciej.

Powiedziałbym, że nie ma jednoznacznej odpowiedzi dla wszystkich przypadków, ponieważ wydajność zależy od:

  • Twój sprzęt
  • kompilator
  • program i jego pamięć dostępu wzory
+0

Dzięki Alexey - dobry punkt o lokalnym var (stos, prawda?) Vs globalny var (pamięć, prawda?) - nie myślał o tym w ten sposób. –

+0

Re: dowolna lokalizacja pamięci - dlatego ograniczam rozważanie do pamięci "blisko". Czy to robi różnicę? –

+0

Odp: twój punkt dotyczący zmiany długości instrukcji i czasu dekodowania - masz na myśli różnicę między np. "Mov [bx], ax' a" mov [loc], ax', zakładając 'loc equ 0xfffd' (lub w pobliżu przesunięcia)? (Dzięki, jak zawsze, za twoje naprawdę świetne odpowiedzi!) –

11

Dla ciekłokrystalicznego zegara ...

Dla tych, którzy chcieliby zobaczyć określone cykle zegara, instruction/latency tables dla różnych nowoczesnych procesorów x86 i x86-64 są dostępne here (dzięki hirschhornsalz za wskazanie tych).

wtedy dostać, na Pentium 4 Chip:

  • push ax i mov [bx], ax (red box) są praktycznie identyczne pod względem ich skuteczności z identycznych latencji i przepustowości.
  • pop ax i mov ax, [bx] (blue box) są podobnie skuteczne, z identycznymi przepustowości mimo mov ax, [bx] konieczności dwukrotnej latencji pop ax

Pentium 4 Instruction Timing Table

miarę obserwacji na pytanie w komentarzach (3rd komentarz):

  • adresowanie pośrednie (tj. mov [bx], ax) nie różni się istotnie od bezpośredniego adresowania (tj. mov [loc], ax), gdzie loc jest zmienną trzymającą wartość bezpośrednią, np. loc equ 0xfffd.

Wniosek: W połączeniu z Alexey's thorough answer, a tam całkiem solidne przypadku efektywności wykorzystania stosu i pozwalając kompilator zdecydować, kiedy funkcja powinna być wstawiane.

(uwaga Side: W rzeczywistości, nawet już w 8086 od 1978 roku, korzystając z stos wciąż nie był mniej skuteczny niż odpowiadające MOV do pamięci, jak widać z these old 8086 instruction timing tables.)


Zrozumienie czasu oczekiwania & Przepustowość

Potrzebne może być trochę więcej, aby zrozumieć tabele czasowe dla nowoczesnych procesorów.Powinny one pomóc: