Wskaźnik na element vectora i poruszanie się za jego pomocą

0

Witam,
chciałem się upewnić w takiej kwestii:
vector to clasa, która w sobie przechowuje m.in. wskaźnik na dynamiczną tablicę. W tej tablicy przechowuje kolejne dane, do których się odnosimy przez operator "[]". Początkiem tej tablicy powinien być element z indexem 0. W związku z powyższym chciałem się upewnić, że jeśli zrobić coś takiego:

	vector<int> vec(10);
	for (int i = 0; i<10; ++i)
		vec[i] = i;
	
	int* wsk = &(vec[0]);

	*wsk = 10;
	for (int i = 1; i<10; ++i)
		*(++wsk) = 10 - i;

Wszystko będzie prawidłowo i bezpiecznie?
Chodzi o drobną optymalizację - podobno poruszając się po tablicy wskaźnikiem jest troszkę wydajniej niż za pomocą "[]".
Natomiast nie chcę rezygnować z vectora...

6

Chodzi o drobną optymalizację - podobno poruszając się po tablicy wskaźnikiem jest troszkę wydajniej niż za pomocą "[]".
G\wwno prawda.
Jeśli zamierzasz iterować po elementach wektora - użyj <font size="9">iteratora</span> aka std::vector<T>::iterator.

1

Iterator jest najlepszym rozwiązaniem, ale dopowiadając:

podobno poruszając się po tablicy wskaźnikiem jest troszkę wydajniej niż za pomocą "[]"

"[]" nie jest magicznym znaczkiem w syntaxie C++, to operator, funkcja, która działa dokładnie tak, jakbyś używał przesunięcia wskaźnika na tablice. Optymalizacja zatem byłaby, gdyby funkcja była "w rzeczywistości wykonywana". Jednak tak się nie dzieje, bo nawet bez optymalizacji kompilator jest na tyle mądry, że sobie to "zamieni".

0

Hey, dzięki za odpowiedzi.
Bardziej chodziło mi o analogię do poniższego przykładu, który bazuje na jednym z tutoriali znalezionych w internecie.

#include <iostream>
#include <vector>
#include <time.h>
#include <cstdlib>

using namespace std;

int _tmain(int argc, _TCHAR* argv[])
	{

	clock_t start,stop;
	double czas;

	int size = 100000000;
	int* tab = new int[size];

	start = clock();
	for(int i = 0; i < size; i++)
		{
		tab[i] = i;
		tab[i] += 50;
		}
	stop = clock();
	delete[] tab;

	czas = (double)(stop - start) / CLOCKS_PER_SEC;
	cout << "wynik1 = " << czas << endl << endl;

	tab = new int[size];
	int* wsk = tab;

	start = clock();
	for(int i = 0; i < size; i++)
		{
		*wsk = i;
		*wsk += 50;
		++wsk;
		}
	stop = clock();
	delete[] tab;


	czas = (double)(stop - start) / CLOCKS_PER_SEC;
	cout << "wynik2 = " << czas << endl << endl;
	
	getchar();
	return 0;
	}

W tym przykładzie wykorzystanie wskaźników jest przeważnie szybsze od tego z wykorzystaniem operatora "[]". Zaraz przetestuję, czy analogicznie będzie z iteratorem.

0

Na O1 różnica nie występuje, a Valgrind twierdzi, ze operator[] nie jest wywoływany.
EDIT:
Zastanawiała mnie jednak różnica na O0, która faktycznie wskazywała przewagę czystych wskaźników. I oto wytłumaczenie:
podczas kompilacji w wersji debug, do kodu zostaje dołączona flaga
#define DEBUG
teraz operator[] wywołuje funkcje

#ifdef DEBUG
/* ===========================================================================
 * Check that the match at match_start is indeed a match.
 */
local void check_match(s, start, match, length)
    deflate_state *s;
    IPos start, match;
    int length;
{
    /* check that the match is indeed a match */
    if (zmemcmp(s->window + match,
                s->window + start, length) != EQUAL) {
        fprintf(stderr, " start %u, match %u, length %d\n",
                start, match, length);
        do {
            fprintf(stderr, "%c%c", s->window[match++], s->window[start++]);
        } while (--length != 0);
        z_error("invalid match");
    }
    if (z_verbose > 1) {
        fprintf(stderr,"\\[%d,%d]", start-match, length);
        do { putc(s->window[start++], stderr); } while (--length != 0);
    }
}
#else
#  define check_match(s, start, match, length)
#endif /* DEBUG */

znajdujące się w C++/src/util/compress/zlib/deflate.c
i to właśnie pożera czas procesora. Dowód:
-valgrind mówi, ze check_match jest wywoływane
-skompiluj na wersji Realase

0

@pingwindyktator moje testy robiłem właśnie na release.
W tym filmiku: od godziny mniej więcej 8:30 autor wyjaśnia dlaczego to powinno działać szybciej.

Co ciekawe zrobiłem kolejne testy (kod poniżej) i okazuje się, że praca na vectorze jest jeszcze szybsza O_o.
Nic z tego nie rozumiem...

Tutaj kod:

#include <iostream>
#include <vector>
#include <time.h>
#include <cstdlib>

using namespace std;

int _tmain(int argc, _TCHAR* argv[])
	{

	clock_t start,stop;
	double czas;

	int size = 100000000;
	int* tab = new int[size];

	start = clock();
	for(int i = 0; i < size; i++)
		{
		tab[i] = i;
		tab[i] += 50;
		}
	stop = clock();
	delete[] tab;

	czas = (double)(stop - start) / CLOCKS_PER_SEC;
	cout << "wynik1 = " << czas << endl << endl;

	tab = new int[size];
	int* wsk = &(tab[0]);

	start = clock();
	for(int i = 0; i < size; i++)
		{
		*wsk = i;
		*wsk += 50;
		++wsk;
		}
	stop = clock();
	delete[] tab;

	czas = (double)(stop - start) / CLOCKS_PER_SEC;
	cout << "wynik2 = " << czas << endl << endl;
	
	vector<int> vec(size);

	start = clock();
	for(int i = 0; i < size; i++)
		{
		vec[i] = i;
		vec[i] += 50;
		}
	stop = clock();

	czas = (double)(stop - start) / CLOCKS_PER_SEC;
	cout << "wynik3 = " << czas << endl << endl;



	wsk = &(vec[0]);

	start = clock();
	for(int i = 0; i < size; i++)
		{
		*wsk = i;
		*wsk += 50;
		++wsk;
		}
	stop = clock();

	czas = (double)(stop - start) / CLOCKS_PER_SEC;
	cout << "wynik4 = " << czas << endl << endl;


	start = clock();
	auto iter = vec.begin();
	auto end = vec.end();

	int i = 0;
	while(iter != end)
		{
		*iter = i;
		++i;
		++iter;
		}

	stop = clock();

	czas = (double)(stop - start) / CLOCKS_PER_SEC;
	cout << "wynik5 = " << czas << endl << endl;


	getchar();
	return 0;
	}
0

No to ja nie wiem, jakie Ty te testy robiłeś...
http://ideone.com/Xw6wLJ

EDIT:
Niestety, ale autor nie ma racji. operator[](size_t id) wywołuje właśnie taką funkcje, która zwraca *(tablica+id). Kropka.

0

@pingwindyktator też mi się wydaje, że operator[] dla typów wbudowanych rządzi się troszkę innymi prawami. Typ wbudowany nie jest klasą i nie korzysta z funkcji/metod.
Chciałem również zauważyć, że operator[] w klasie może być zdefiniowany inline i wówczas również trudno go nazwać funkcją.

8

Operator [] dla typów wbudowanych nie wywołuje żadnej funkcji. Ponadto w 21 wieku, nie ma żadnej różnicy w iterowaniu za pomocą wskaźników i operatora [] w ogromnej większości przypadków. Kompilatory nie są aż takie głupie. Jeżeli nie ma żadnych specjalnych powodów należy używać operatora []. Ponadto std::vector też sprowadza się do takiego samego kodu.

Wysrarczy spojrzeć na to jak wygląda wygenerowy kod dla wersji ze wskaźnikiem, wersji z operatorem [] i dla std::vector. Odpowiedź: wygląda tak samo.

  for (size_t i = 0; i < size; ++i) {
    sink(array[i]); 
  }

  for (int *p = array; p < array + size; ++p) {
    sink(*p); 
  }

  for (auto it = vector.begin(); it != vector.end(); ++it) {
    sink(*it);
  }

Pętla wygląda identycznie dla trzech przypadków (dla std::vector kolejność jest call i add jest inna ale to nie ma żadnego znaczenia):

.L2:
	mov	edi, DWORD PTR [rbx]
	add	rbx, 4
	call	sink(int)
	cmp	rbx, rbp
	jne	.L2

http://goo.gl/VjtT0q

Opowieści z tego "kursu" można włożyć między bajki. Analizowanie szybkości i porównywanie kodu bez optymalizacji nie ma żadnego sensu.

3

-O0 nie służy do porównywania prędkości.

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