Wydajność dereferencji wskaźnika a referencji

0

Witam,

Korzystam z małych funkcji, które będą miały postać: zwrot_typ funkcja(arg_typ& zmienna1, arg_typ& zmienna2). Ilość wywołań będzie znaczna(ok. 410001000*(od 3 do 10)), a nie chcę zmuszać użytkownika do zbędnego czekania.

Teraz do rzeczy. Czy jest różnica pomiędzy użyciem zwrot_typ funkcja(arg_typ& zmienna1, arg_typ& zmienna2), a zwrot_typ funkcja(arg_typ zmienna1, arg_typ* zmienna2)* używając nagminnie zapisu *(zmienna) w ciele funkcji? Kompilator przypisując wartości zwykłym zmiennym, też przecież nie używa ich nazw, a adresy ich alokacji. Czy operacja dereferencji wskaźnika trwa dłużej?

0

Różnica między wskaźnikiem a referencją jest tylko w składni, choć może być też w optymalizacji, gdyż referencji nie da się zmienić. Mimo wszystko jeśli dodasz const do wskaźnika to wyjdzie w 100% na to samo.

Jeśli masz zwykłe zmienne to ich ładowanie wygląda np tak:
load [adres] => rejestr
A jeśli masz wskaźnik to wtedy jest tak:
load [adres wskaźnika] => rejestr
load [rejestr] => docelowy rejestr
z tym, że jeśli używasz tego samego wskaźnika w ciągu, to kompilator postara się go nie przeładowywać w kółko bez sensu i różnica w wydajności może nawet się przechylić w drugą stronę, bo instrukcja:
load [rejestr] => docelowy rejestr
zajmuje zwykle mniej bajtów niż instrukcja:
load [adres] => rejestr

0

Potwierdzam co mówi @Wibowit, że zwykle kompilatory C++ to na tyle mądre bestie, że potrafią wskaźnik potraktować nieco mniej zasobożernie od referencji. Referencja to jakby stały wskaźnik na miejsce w pamięci (a właściwie to chyba zawsze na konkretny, stworzony obiekt), a wskaźnik pokazuje gdziekolwiek, jeśli się go nie ustawi i jest bardziej elastyczny. Z mojej wiedzy wynika, że właśnie przez tą elastyczność, wskaźnik może zostać potraktowany przez kompilator bardziej priorytetowo i lepiej zoptymalizować wywołania funkcji, czy odniesienia do obiektów, itd.
Dlatego ja również polecam wskaźnik, bo jest szansa, że zostanie lepiej potraktowany, przy czym jako argument funkcji powinien być stały wskaźnik, będzie pewniejszy.

5

Nie zastanawiaj się nad tym. Użyj tego co pasuje w danej sytuacji, generowanie kodu zostaw kompilatorowi. W kwestii wydajności nie ma to znaczenia, bo kompilator i tak zrobi to po swojemu.

Użyj (stałej) referencji, jeżeli nie masz zamiaru korzystać z właściwości wskaźników: arytmetyki, możliwości zmiany wskazywania, wskaźnika NULL itd.

Zapamiętaj: "We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil".

Zobacz np. tutaj (i w wielu podobnych tematach): http://stackoverflow.com/questions/1650792/c-function-parameters-use-a-reference-or-a-pointer-and-then-dereference - wynikowy kod jest identyczny.

0
Endrju napisał(a):

Nie zastanawiaj się nad tym. Użyj tego co pasuje w danej sytuacji, generowanie kodu zostaw kompilatorowi. W kwestii wydajności nie ma to znaczenia, bo kompilator i tak zrobi to po swojemu.

Prawda.

Endrju napisał(a):

Użyj (stałej) referencji, jeżeli nie masz zamiaru korzystać z właściwości wskaźników: arytmetyki, możliwości zmiany wskazywania, wskaźnika NULL itd.

W tym wypadku użyję jednak wskaźników, bo STL nie pozwala na użycie referencji, a Boost'a nie chcę w tej chwili studiować, wiem, że istnieje coś takiego jak Intrusive Container, ale dzięki Wam będzie niepotrzebny.

Dzięki za przykład i mądre odpowiedzi. Pozdrawiam.

1

Jak najbardziej zgadzam się z tym co @Endrju napisał, należy się kierować jakością kodów źródłowych a nie wynikowych.

Natomiast w jaki sposób kompilator może wykorzystać właściwości referencji w celu utworzenia bardziej optymalnego kodu? Dajmy na to mamy takie struktury:

struct Data {
    char data;
};
struct MoreData {
    char more_data;
};
struct AllData : Data, MoreData {
};

I teraz mając uchwyt (wskaźnik/referencję) do AllData chcemy przekazać jakiejś funkcji jako uchwyt do MoreData.

//1
void Foo(MoreData *data) { ... }
void Bar(AllData *data) { Foo(data); }
//2
void Foo(MoreData &data) { ... }
void Bar(AllData &data) { Foo(data); }

Na obiekcie data będzie wykonany static_cast. W pierwszym przypadku rzutowanie to sprowadzi się do czegoś w tym stylu (pseudokod):

void Bar(AllData *data) { Foo(data == 0 ? 0 : data + 1; }

w drugim do czegoś takiego:

void Bar(AllData &data) { Foo(data + 1); }

tu kompilator nie musi sprawdzać, czy referencja jest zerem.

1 użytkowników online, w tym zalogowanych: 0, gości: 1