Wskaźnik a zmienna prędkość w C++

Na rozmowie kwalifikacyjnej zadano mi pytanie "w C++ jak uzyskać szybszy dostęp do zmiennej, chociaż zwykły identyfikator zmiennej lub chociaż wskaźnik". Muszę powiedzieć, że nie miałem dobrej odpowiedzi technicznej na to pytanie, więc zgadłem.

powiedziałem, że czas dostępu będzie prawdopodobnie taki sam jak normalna zmienna / identyfikator jest wskaźnik do adresu pamięci, gdzie wartość jest przechowywana, podobnie jak wskaźnik. Innymi słowy, że pod względem prędkości obie mają takie same wydajność, a wskaźniki są różne tylko dlatego, że możemy określić adres pamięci, na który chcemy, aby wskazywały.

Rozmówca nie wydawał się zbyt przekonany/zadowolony z mojej odpowiedzi (chociaż nic nie powiedział, po prostu pytał o coś innego), dlatego pomyślałem, że przyjdę i zapytam tak, żeby moja odpowiedź była dokładna, a jeśli nie to dlaczego (z teorii i technicznego POV).

Author: Jerry, 2011-08-03

10 answers

Zmienna nie musi żyć w pamięci głównej. W zależności od okoliczności kompilator może przechowywać go w rejestrze przez cały lub część jego życia, a dostęp do rejestru jest znacznie szybszy niż dostęp do pamięci RAM.

 32
Author: LaC,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-02 22:21:16

Kiedy uzyskujesz dostęp do "zmiennej", wyszukujesz adres, a następnie pobierasz wartość.

Pamiętaj-wskaźnik jest zmienną. Tak właściwie to Ty:

A) wyszukaj adres (zmiennej kursora),

B) pobiera wartość (adres zapisany w danej zmiennej)

... i wtedy ...

C) pobiera wartość pod wskazanym adresem.

Więc tak, dostęp przez "wskaźnik" (a nie bezpośrednio) wymaga (trochę) dodatkowej pracy i (nieco) dłuższego czas.

Dokładnie to samo dzieje się niezależnie od tego, czy jest to zmienna wskaźnikowa (C lub c++), czy zmienna referencyjna (tylko C++).

Ale różnica jest ogromnie mała.

 37
Author: paulsm4,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-04 21:40:44

Zignorujmy przez chwilę optymalizację i zastanówmy się, co maszyna abstrakcyjna musi zrobić, aby odwołać się do zmiennej lokalnej vs. zmiennej za pomocą (lokalnego) wskaźnika. Jeśli mamy zmienne lokalne zadeklarowane jako:

int i;
int *p;

Kiedy odwołujemy się do wartości i, nieoptymalizowany kod musi przejść wartość, która jest (powiedzmy) 12 poza bieżącym wskaźnikiem stosu i załadować go do rejestru, abyśmy mogli z nim pracować. Natomiast gdy odwołujemy się do *p, ten sam nieoptymalizowany kod musi przejść do wartości p od 16 poza bieżącym wskaźnikiem stosu, załaduj go do rejestru, a następnie pobierz wartość, na którą wskazuje rejestr i załaduj do innego rejestru, abyśmy mogli z nim pracować jak wcześniej. Pierwsza część pracy jest taka sama, ale dostęp do wskaźnika koncepcyjnie obejmuje dodatkowy krok, który należy wykonać, zanim będziemy mogli pracować z wartością.

To był, myślę, punkt pytania wywiadu - aby zobaczyć, czy rozumiesz zasadniczą różnicę między dwoma rodzajami dostęp. Myślałeś, że lokalna zmienna access wymaga pewnego rodzaju wyszukiwania, i tak jest - ale dostęp do wskaźnika wymaga tego samego rodzaju wyszukiwania, aby dostać się do wartości wskaźnika, zanim zaczniemy szukać rzeczy, na którą wskazuje. W prostych, nieoptymalizowanych terminach, dostęp do wskaźnika będzie wolniejszy z powodu tego dodatkowego kroku.

Teraz z optymalizacji, może się zdarzyć, że dwa razy są bardzo bliskie lub identyczne. Prawdą jest, że jeśli inny ostatni kod ma już używane wartość p do odwołania się do innej wartości, można już znaleźć p w rejestrze, tak, że wyszukiwanie *P przez P zajmuje ten sam czas, jak wyszukiwanie i przez wskaźnik stosu. Z tego samego powodu, jeśli ostatnio użyłeś wartości i, możesz już znaleźć w rejestrze. I chociaż to samo może dotyczyć wartości *p, optymalizator może ponownie użyć swojej wartości z rejestru tylko wtedy, gdy jest pewien, że p nie zmieniła się w tym czasie. Nie ma takiego problemu ponowne użycie wartości i. w skrócie, podczas gdy dostęp do obu wartości może zająć ten sam czas podczas optymalizacji, dostęp do lokalnej zmiennej prawie nigdy nie będzie wolniejszy( z wyjątkiem naprawdę patologicznych przypadków) i może być bardzo szybszy. To sprawia, że jest to prawidłowa odpowiedź na pytanie rozmówcy.

W obecności hierarchii pamięci różnica w czasie może być jeszcze wyraźniejsza. Zmienne lokalne będą znajdować się blisko siebie na stosie, co oznacza, że bardzo prawdopodobne, że adres, którego potrzebujesz, znajdzie się już w pamięci głównej i w pamięci podręcznej przy pierwszym dostępie do niego (chyba że jest to pierwsza zmienna lokalna, do której uzyskasz dostęp w tej rutynie). Nie ma takiej gwarancji z adresem, na który wskazuje wskaźnik. O ile nie został niedawno otwarty, może być konieczne poczekać na brak pamięci podręcznej lub nawet Błąd strony, aby uzyskać dostęp do adresu wskazanego do, co może spowolnić go o rząd wielkości w porównaniu do zmiennej lokalnej. Nie, to się nie zdarza cały czas - ale jest to potencjalny czynnik, który może mieć znaczenie w niektórych przypadkach, i to też jest coś, co może zostać podniesione przez kandydata w odpowiedzi na takie pytanie.

A co z pytaniem innych komentatorów: jakie to ma znaczenie? To prawda, dla pojedynczego dostępu, różnica będzie niewielka w kategoriach absolutnych, jak ziarnko piasku. Ale wystarczy ziarenek piasku i dostaniesz plażę. I chociaż (kontynuując metaforę) jeśli szukasz ktoś, kto potrafi szybko biec plażową drogą, nie chcesz kogoś, kto ma obsesję na punkcie zamiatania każdego ziarnka piasku z drogi, zanim zacznie biegać, chcesz kogoś, kto będzie świadomy, kiedy niepotrzebnie biegnie przez po kolana wydmy. Profilerzy nie zawsze cię tu ratują - w tych metaforycznych terminach są znacznie lepsi w rozpoznawaniu jednej wielkiej skały, którą musisz biegać, niż zauważaniu wielu małych ziarenek piasku, które Cię pogrążają. Chciałbym więc, aby w moim zespole ludzie, którzy rozumieją te kwestie na podstawowym poziomie, nawet jeśli rzadko wychodzą z tego, aby korzystać z tej wiedzy. Nie przestawaj pisać jasnego kodu w dążeniu do mikrooptymalizacji, ale pamiętaj o rzeczach, które mogą kosztować wydajność, zwłaszcza podczas projektowania struktur danych, i miej poczucie, czy otrzymujesz dobrą wartość za cenę, którą płacisz. Dlatego uważam, że było to rozsądne pytanie, aby zbadać zrozumienie tych zagadnień przez kandydata.

 21
Author: dewtell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-03 04:22:50

Co powiedział paulsm4 i LaC + trochę asm:

    int y = 0;
mov         dword ptr [y],0  
    y = x;
mov         eax,dword ptr [x]   ; Fetch x to register
mov         dword ptr [y],eax   ; Store it to y
    y = *px;
mov         eax,dword ptr [px]  ; Fetch address of x 
mov         ecx,dword ptr [eax] ; Fetch x 
mov         dword ptr [y],ecx   ; Store it to y

Z drugiej strony nie ma to większego znaczenia, również prawdopodobnie trudniej jest to zoptymalizować (fe. nie moĹźna zachowaÄ ‡ wartoĹ "ci w rejestrze cpu, poniewaĹź wskaĺşnik wskazuje tylko na jakieĹ" miejsce w pamiÄ ™ ci). Tak zoptymalizowany kod dla y = x; może wyglądać tak:

mov dword ptr [y], ebx - jeśli założymy, że lokalny var x był przechowywany w ebx

 20
Author: Marcin Deptuła,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-02 22:28:00

Myślę, że rozmówca szukał ciebie, aby wspomnieć słowo Zarejestruj . Jeśli zadeklarujesz zmienną jako zmienną rejestru, kompilator dołoży wszelkich starań, aby upewnić się, że jest ona przechowywana w rejestrze na CPU.

Trochę czatu wokół dostępu do magistrali i negocjacji dla innych typów zmiennych i wskaźników pomogłoby w oprawieniu go.

 3
Author: Adrian Regan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-03 09:44:10

Zmienna posiada wartość określonego typu, A dostęp do zmiennej oznacza uzyskanie tej wartości z pamięci lub z rejestru. Podczas pobierania wartości z pamięci musimy skądś pobrać jej adres-przez większość czasu musi ona zostać załadowana do rejestru (czasami może być częścią samej komendy load, ale jest to dość rzadkie).

Wskaźnik przechowuje adres wartości; ta wartość musi być w pamięci, sam wskaźnik może być w pamięci lub w rejestrze.

I spodziewamy się, że średnio dostęp przez wskaźnik będzie wolniejszy niż dostęp do wartości przez zmienną.

 2
Author: MaximG,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-02 22:33:47

Myślę, że kluczową częścią pytania jest "dostęp do zmiennej". Dla mnie, jeśli zmienna jest w zakresie, dlaczego tworzysz wskaźnik do niej (lub odniesienie), aby uzyskać do niej dostęp? Użycie wskaźnika lub odniesienia miałoby sens tylko wtedy, gdy zmienna sama w sobie jest jakąś strukturą danych lub gdy używasz jej w jakiś niestandardowy sposób (jak interpretacja int jako float).

Użycie wskaźnika lub odniesienia byłoby szybsze tylko w bardzo szczególnych okolicznościach. W Warunkach ogólnych, wydaje mi się, że próbowałbyś ponownie odgadnąć kompilator, jeśli chodzi o optymalizację, a moje doświadczenie mówi mi, że jeśli nie wiesz, co robisz, to zły pomysł.

To zależy nawet od słowa kluczowego. Słowo kluczowe const może bardzo dobrze oznaczać, że zmienna jest całkowicie zoptymalizowana w czasie kompilacji. To jest szybsze niż wskaźnik. Słowo kluczowe register nie gwarantuje , że zmienna jest przechowywana w rejestrze. Więc skąd wiesz, czy jego szybciej czy nie? Myślę, że odpowiedź jest, że to zależy, ponieważ nie ma jeden rozmiar pasuje do wszystkich odpowiedzi.

 2
Author: Carl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:30

Myślę, że lepszą odpowiedzią może być to, że zależy od tego, gdzie wskaźnik jest "wskazujący na". Uwaga, zmienna może już być w pamięci podręcznej. Jednak wskaźnik może ponieść karę fetch. Jest to podobne do porównania wydajności wektorowej z listą połączoną. Wektor jest przyjazny dla pamięci podręcznej, ponieważ cała pamięć jest ciągła. Jednak lista połączona, ponieważ zawiera wskaźniki, może ponieść karę za pamięć podręczną, ponieważ pamięć jest potencjalnie rozproszona po całym miejscu

 1
Author: sandboxcoder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-02 22:25:36

Twoja analiza ignoruje powszechny scenariusz, w którym sam wskaźnik jest zmienną pamięci, do której należy również uzyskać dostęp.

Istnieje wiele czynników, które wpływają na wydajność oprogramowania, ale jeśli przyjmiesz pewne założenia upraszczające dotyczące zmiennych (zwłaszcza, że nie są one w żaden sposób buforowane), to każdy poziom indirection wskaźnika wymaga dodatkowego dostępu do pamięci.

int a = 1234; // luggage combination
int *b = &a;
int **c = &b;
...
int e = a; // one memory access
int e = *b; // two memory accesses
int e = **c; // three memory accesses

Więc krótka odpowiedź na "co jest szybsze" to: ignorowanie kompilatora i procesora optymalizacje, które mogą wystąpić, szybszy jest bezpośredni dostęp do zmiennej.

W najlepszym przypadku, gdy kod ten jest wykonywany wielokrotnie w ciasnej pętli, wartość wskaźnika prawdopodobnie zostanie zapisana w rejestrze PROCESORA lub w najgorszym przypadku w pamięci podręcznej L1 procesora. W takim przypadku jest prawdopodobne, że przekierowanie wskaźnika pierwszego poziomu jest tak szybkie lub szybsze niż bezpośredni dostęp do zmiennej, ponieważ "bezpośrednio" oznacza prawdopodobnie poprzez rejestr "wskaźnik stosu" (plus pewne przesunięcie). W w obu przypadkach używasz rejestru CPU jako wskaźnika do wartości.

Istnieją inne scenariusze, które mogą mieć wpływ na tę analizę, na przykład dla danych globalnych lub statycznych, w których adres zmiennej jest zakodowany na twardo w strumieniu instrukcji. W takim scenariuszu odpowiedź może zależeć od specyfiki zaangażowanego procesora.

 1
Author: nobar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-02 23:04:27

Paulsm4 i LaC już wyjaśnili to ładnie wraz z innymi członkami. Chcę podkreślić efekt stronicowania, gdy wskaźnik wskazuje na coś w stercie, który został paged out.

=> zmienne lokalne są dostępne w stosie lub w rejestrze
= > podczas gdy w przypadku wskaźnika Wskaźnik może wskazywać adres, który nie znajduje się w pamięci podręcznej, a stronicowanie z pewnością spowolni szybkość.

 1
Author: Abhinav,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-14 18:54:01