Wskaźnik do elementów tablicy

0

Witam.
Stworzyłem tablicę n-wieloelementową (n podaje użytkownik) poprzez operator new. Chciałbym teraz dodać drugi wskaźnik, który przyśpieszy mi pracę nad tą tablicą, czyli będzie wskazywał, np. po każdej iteracji na kolejny element tablicy. Jak to zaimplementować?

Mogę zastępczo stosować zapis, np:

int *wskaznik;
wskaznik = new int [n]

for(int i = 0; i < n; i++)
   cin >> wskaznik[i]; //tu mi nie pasuje

ale domyślam się, że - tak samo jak w przypadku zwykłych tablic - przy dużych n-ach będzie to długo trwało.

2

Zmiana indeksu na wskaźnik prawie nic nie zmieni. Ta "szybkość wskaźników" o której słyszałeś to jakiś mit.

0

Słyszałem, że przyśpiesza pracę wczytywania tablic, ponieważ zna typ tej tablicy, więc szukając jej kolejnego elementu przesuwa się od razu o konkretny rozmiar/odległość, zamiast szukać stopniowo. Czy to mit?

0

A obliczasz gdzieś sam przesunięcie? Gdy do tablicy przystawiasz operator subscript ([]) to i tak jest ona niejawnie konwertowana na wskaźnik do pierwszego elementu.

0

to:
for(int i = 0; i < n; i++)
cin >> wskaznik[i]; //tu mi nie pasuje
można zastąpić na:
for(int *i=wskaznik,*e=i+n;i<e;++i)
cin >> *i;
będzie trochę szybciej bo druga wersja eliminuje n operacji dodawania, które niejawnie występują w wskaznik[i] bo to robi się jako *(wskaznik+i).
Z tym że w porównaniu z czasem cin >> wygrasz marne ułamki procenta.

0

Kolejne genialne premature optimization. - @Rev

To nie jest "premature optimization", tylko użycie wskaźników.
To można by tak nazwać:

int *i=wskaznik,*e=i+n;
while(i<e) cin>>*(i++);
0

Testy przeprowadzane na Visual Studio 2012, procesor to Sandy Bridge.
Program testowy:

#include <Windows.h>
#include <cstdio>

__declspec(noinline) int do_magic(int value)
{
    return value * 5;
}

int main()
{
    LARGE_INTEGER start, end, freq;
    QueryPerformanceFrequency(&freq);

    size_t iterations = 100000;
    size_t tests = 5000;
    int* arr = new int[iterations];

    QueryPerformanceCounter(&start);
    for(int testIdx = 0; testIdx < tests; testIdx++)
    {
        // Wariant I
        // for(int i = 0; i < iterations; i++)
        //     arr[i] = do_magic(arr[i]);
        
        // Wariant II
        // for(int *it = arr, *e = it + iterations; it < e; ++it)
        //     *it = do_magic(*it);
    }
    QueryPerformanceCounter(&end);

    double result = (double)(end.QuadPart - start.QuadPart) / freq.QuadPart * 1000;
    printf("%fms\n", result);
}

Kod z włączonymi optymalizacjami kompiluje się do:

         // Wariant I
         for(int i = 0; i < iterations; i++)
00F31060  xor         edx,edx  
         arr[i] = do_magic(arr[i]);
00F31062  mov         ecx,dword ptr [esi+edx*4]  
00F31065  call        do_magic (0F31000h)  
00F3106A  mov         dword ptr [esi+edx*4],eax  
00F3106D  inc         edx  
00F3106E  cmp         edx,186A0h  
00F31074  jb          main+52h (0F31062h)  

        // Wariant II
         for(int *it = arr, *e = it + iterations; it < e; ++it)
00B91060  mov         edx,ebx  
00B91062  cmp         ebx,esi  
00B91064  jae         main+66h (0B91076h)  
             *it = do_magic(*it);
00B91066  mov         ecx,dword ptr [edx]  
00B91068  call        do_magic (0B91000h)  
00B9106D  mov         dword ptr [edx],eax  
00B9106F  add         edx,4  
00B91072  cmp         edx,esi  
00B91074  jb          main+56h (0B91066h)  

Bez optymalizacji (debug), wariant I: 9599ms, wariant II: 9505ms, zysk: 0,97%
Z optymalizacjami (release), wariant I: 750ms, wariant II: 750ms, zysk: 0%

Robi się ciekawiej, gdy wywalimy wywołanie funkcji (arr[i] *= 5; i *it *= 5;). W pierwszym wariancie kompilator użyje SSE i czas wykonywania zmniejszy się do 89ms. Drugi wariant: 269ms. Wniosek: drugi kod wręcz zaciemnił twoje intencje przed kompilatorem, który nie mógł z tego powodu przeprowadzić lepszych, bardziej agresywnych optymalizacji.

0

Zamień wywołanie do_magic na arr[i]<<=1; / (*it)<<=1; a zobaczysz jak wzrośnie zysk.

No zobacz...

        // Wariant I
         for(int i = 0; i < iterations; i++)
00851050  mov         eax,esi  
00851052  mov         ecx,61A8h  
00851057  jmp         main+60h (0851060h)  
00851059  lea         esp,[esp]  
             arr[i] <<= 1;
00851060  movdqu      xmm0,xmmword ptr [eax]  
00851064  lea         eax,[eax+10h]  
00851067  pslld       xmm0,xmm1  
0085106B  movdqu      xmmword ptr [eax-10h],xmm0  
00851070  dec         ecx  
00851071  jne         main+60h (0851060h) 

94ms

        //Wariant II
         for(int *it = arr, *e = it + iterations; it < e; ++it)
00AB1050  mov         eax,esi  
00AB1052  cmp         esi,ecx  
00AB1054  jae         main+5Fh (0AB105Fh)  
             *it <<= 1;
00AB1056  shl         dword ptr [eax],1  
00AB1058  add         eax,4  
00AB105B  cmp         eax,ecx  
00AB105D  jb          main+56h (0AB1056h) 

251ms

Wyłączając możliwość użycia SSE przez kompilator dostajemy:

        // Wariant I
         for(int i = 0; i < iterations; i++)
00C51046  xor         ecx,ecx  
00C51048  jmp         main+50h (0C51050h)  
00C5104A  lea         ebx,[ebx]  
             arr[i] <<= 1;
00C51050  shl         dword ptr [esi+ecx*4],1  
00C51053  inc         ecx  
00C51054  cmp         ecx,186A0h  
00C5105A  jb          main+50h (0C51050h) 

251ms

Wciąż to okrągłe 0%.

0

Różnica między wskaźnikiem a indeksem jest bardzo mało, wręcz czasami jedno będzie szybsze a innym razem drugie. Większa różnica (chodź wciąż mała i nieistotna) powstaje gdy odliczamy do zera a nie do jakiejś konkretnej wartości.

W każdym razie jeśli nie zależy ci na przyspieszeniu algorytmu o 0,0001% to nie zaprzątaj sobie głowy takimi sprawami.

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