Wcześniej już dużo osób napisało, że nie powinno się na takie rzeczy zwracać uwagi w przypadku 99.99% tworzonych programów - i mają rację. Przychodzą mi do głowy jedynie dwa wyjątki, które sprowadzają się albo do synchronizowania procesora co do cyklu z jakimś zewnętrznym zdarzeniem (kłaniają się czasy Atari 2600 ;>), albo próba manualnego zoptymalizowania czegoś do jak najkrótszego/najszybszego kodu (co się robi dla zabawy).
Niemniej jednak, skoro już sobie dywagujemy, to się przyłącze ;)
Jest jedna zasadnicza różnica między operowaniem na "oryginale" (tj. bezpośrednio na obiekcie), a na wskaźnikach/referencjach: w tym drugim wypadku adres obiektu w którymś momencie musi zostać pobrany z pamięci do rejestru.
Zazwyczaj takie coś jest szybkie - 4-7 cykli procesora (te cykle prosiłbym traktować jako jakieś tam poglądowe wartości wzięte z sufitu) żeby coś trafiło z cache L1 do rejestru. Jeśli nie ma w L1, to dochodzi z 10 cykli, żeby to z L2 ściągnąć, a potem kilkanaście-kilkadziesiąt kolejnych, żeby to z L3 ściągnąć.
Jeśli nie było tego w cache, no to pointer musi być ściągnięty z RAM, co może trwać 100-200 cykli w sumie.
Chyba, że mamy całkowitego pecha i procesor w swoim look-up table (TLB) nie ma przetłumaczonego adresu virtualnego na fizyczny, i musi najpierw pobrać base-offset z 3-4 poziomowej tablicy stron, co wiąże się z kolejnymi 3-4 wycieczkami do cache, a w najgorszym wypadku do RAM. W najgorszym razie dochodzimy do 400-800 cykli.
Nooo chyba, że z jakiegoś powodu ten pointer nie był wyrównany w pamięci do naturalnego adresu (tj. podzielnego przez 4 albo 8) i tak jakoś nieszczęśliwie się stało, że leżał na złączeniu dwóch stron - wtedy może nam się z tego i 1500 cykli zrobić (ponownie: liczby z sufitu; ale rząd wielkości afair mam dobry).
Jeszcze gorzej jest jeśli czarny kod przebiegł nam drogę i stłukliśmy jakieś lustro - wtedy obie strony były wyswapowane na dysk twardy, i z cykli przechodzimy na milisekundy laga ;)
W praktyce nie ma co rozpaczać. Po pierwsze, nawet jeśli dany logiczny procesor sobie zawiśnie na czekaniu na te wszystkie dane z RAMu, to współczesne procesory mają Hyper Threading albo podobne technologie, tj. w tym samym czasie na tym samym rdzeniu inny wątek może sobie do woli hulać i nie być blokowanym przez oczekujący wątek (co normalnie by miało co jakiś czas miejsce z uwagi na to jak HT działa).
Po drugie, w zależności od tego co tam dalej sobie leży w kodzie, procesor może sobie to od razu wykonać nie czekając na wynik danej operacji (ot taka specyfika procesorów z mechanizmem out-of-order execution z którymi mamy do czynienia). Oczywiście za daleko pewnie w kodzie nie zajdzie, bo pojawią się jakieś wzajemne zależności, ale zawsze jest to trochę cykli, które nie do końca były stracone.
Po trzecie, nawet jeśli tak by się stało raz, to wszystkie kolejne odwołania (aż do wywłaszczenia wątku ofc) będą bardzo szybkie (raz, że pointer pewnie i tak będzie już w rejestrze; a dwa, że nawet jeśli nie będzie, to będzie przynajmniej w którymś cache'u).
Z powyższego w sumie wynika jeszcze jedna rzecz - w przypadku obecnych procesorów desktopowych/serwerowych nie ma co liczyć cykli dla pojedynczych instrukcji. Jak już, to trzeba je liczyć dla całych dużych bloków kodu, a idealnie dla całego programu (z uwagi na HT, superskalarność, out-of-order execution, pre-fetch w cache'u, re-ordering przy dostępie do pamięci, i kilka innych fajnych mechanizmów).
Długością instrukcji też bym się nie przejmował - obecne pipeline'y procesorów są na tyle długie, że instrukcja jest już daawno odczytana i zdekodowana jak dochodzi do jej wykonania.