Witam !
Dziś odpowiedziałem koledze w tym wątku http://4programmers.net/Forum/viewtopic.php?id=162998, lecz potem zacząłem się zastanawiać nad zapisem tego kodu który mu podesłałem.
Pierwotny kod :
srand ( time(NULL) );
int tab[20];
int pom,j;
pom=j=0;
for(int i=0 ; i<10 ; i++)
{
pom=rand()%10;
tab[j]=pom;
tab[j+1]=pom;
j+=2;
}
Generalnie kod losuje 10 par losowych liczb w przedziale do 10.
Pomyślałem jednak, że użyłem niepotrzebne zmienne,i postanowiłem to zoptymalizować. Żeby podjąć się tej próby postanowiłem zmierzyć czas wykonania się programu. Jak się pewnie już domyślacie wyszło mi 0.00sec. Postanowiłem w takim razie zwiększyć liczbę losowanych liczb, wszakże w projektach o dużej ilości pętli tak śmiecąc zmiennymi można by teoretycznie nieźle go zwolnić. Problem polega na tym, że ta pętla nie potrafi wykonać się więcej niż 1000 razy.
Moje pierwsze pytanie - dlaczego ? Próbowałem powiększać liczbę iteracji samej pętli oraz rzecz jasna rozmiar tablicy i wywala mi błąd pamięci i odwołuje się do jakiejś wstawki aseamblerowej - dlaczego ?
Tak czy siak rozwiązałem ten problem zapisując ten kod tak :
KOD 1
for(int q=0;q<1000;q++)
{
for(int i=0 ; i<1000 ; i++)
{
pom=rand()%10;
tab[j]=pom;
tab[j+1]=pom;
j+=2;
}
j=0;
}
Tak zapisana operacja wykonuje się w :
0.172sec
Ok! Usuwam zbędne zmienne i o dziwo okazuje się,że program potrafi wykonać się więcej niż 1000 razy a mianowicie potrafi wykonać się 100000 razy ,a wygląda tak :
KOD 2
for(int q=0;q<10;q++)
for(int i=0;i<100000;i+=2)
tab[i]=tab[i+1]=rand()%10;
Co lepsze wykonuje się w :
0.078sec
Pomyślałem, że skoro pętla może wykonać się 100000 razy to mogę po prostu zapisać to 10 razy zamiast pakować to w pętle ( wiem wbrew DRY ). Chodzi mi o taki zapis :
KOD 3
for(int i=0;i<100000;i+=2) //1 petla
tab[i]=tab[i+1]=rand()%10;
for(int i=0;i<100000;i+=2)//2 petla
tab[i]=tab[i+1]=rand()%10;
...
for(int i=0;i<100000;i+=2)//10 petla
tab[i]=tab[i+1]=rand()%10;
Takie cudo wykonało się w :
0.094 sec
W zasadzie zamiast w pętli która powtarza napisałem 10 razy to samo i czym to się różni, że czas wykonania jest inny ?
Podążając tropem wymyśliłem że zapiszę inaczej operacje na tablicach i wyszło mi takie coś :
KOD 4
for(int q=0;q<10;q++)
for(int i=0;i<100000;i+=2)
*(tab+i)=*(tab+i+1)=rand()%10;
Wykonało się w:
0.078sec
Wynik mnie nie zdziwił ponieważ czytałem, że takie odwołanie się do tablicy jest szybsze, jednak nowe kompilatory same sobie tak robią, w procesie optymalizacji i nie ma sensu tego ręcznie pisać ( wskaźnikowo ).
Jednak skoro poprzedni kod zapisałem bez pętli to i ten postanowiłem tak wypróbować :
KOD5
for(int i=0;i<100000;i+=2) //petla 1
*(tab+i)=*(tab+i+1)=rand()%10;
for(int i=0;i<100000;i+=2)// petla 2
*(tab+i)=*(tab+i+1)=rand()%10;
...
for(int i=0;i<100000;i+=2)//petla 10
*(tab+i)=*(tab+i+1)=rand()%10;
Wynik jest zaskakujący dla mnie ponieważ myślałem, że wykona się zupełnie tak jak KOD3 ponieważ, zaszła analogia między KOD2 a KOD4, jednak tak się nie stało.
Program wykonał się w:
0.078 sec
Co jest takim samym osiągiem jak KOD2 i KOD4 mimo, zastosowania ręcznego powtarzania kodu zamiast pętli. Znów nasuwa się pytanie - dlaczego ?
Na koniec postanowiłem jeszcze coś sprawdzić. Skoro odwołanie do tablicy przez operator indeksacji ,a przez arytmetyke wskaźnika przy tej samej konstrukcji pętli nie ma różnicy w wydajności postanowiłem sprawdzić taki kod :
KOD 6
for(int q=0;q<1000;q++)
{
for(int i=0 ; i<1000 ; i++)
{
pom=rand()%10;
*(tab+j)=pom;
*(tab+(j+1))=pom;
j+=2;
}
j=0;
}
Ten kod wykonał sie w :
0.188sec
Czyli dłużej niż jakikolwiek wcześniej.
Reasumując popatrzmy na wnioski:
KOD1 0.172sec Moim zdaniem najmniej optymalny co jednak jest nieprawdą, jednak nie najlepszy.
KOD2 0.078sec Ten kod należy do trójcy najbardziej optymalnych. Nie zawiera niepotrzebnych zmiennych, 2 zagnieżdżone pętle.
KOD3 0.094sec Jak powyżej jednak zamiast pętli ręczne przeklepywanie co go zwolniło.
KOD4 0.078sec Zastosowane odwołanie jak do wskaźnika , pętle jak w kodzie 2 żadnej zmiany wydajności.
KOD5 0.078sec Kod z odwołaniem wskaźnikowym, oraz ręcznie przeklepany zamiast pętli. Zamiast zwolnić jak KOD3 zachował swoją wydajność.
KOD6 0.188sec Kod który w moim zamyśle miał być szybszy od 1 przez odwołanie się do tablicy jak do wskaźnika, jednak ta operacja znacznie go zwolniła - dlaczego ? Przecież różni się jedynie sposobem odwołania jak kody 2 i 4 a w tamtych nie widać różnic.
W zależności od zastosowanej ilości zmiennych, oraz sposobu odwołań kod zwalnia lub przyspiesza, potrafi ktoś wytłumaczyć mi dlaczego dzieje się tak w poszczególnych przypadkach ?
Nie wiem dlaczego w niektórych przypadkach kod zachowuje się inaczej niż oczekuję ( chodzi o prędkość wykonania ), jednak widzę zmiany. Zastanawia mnie czy warto dla takich zmian w odpowiednich sytuacjach zmieniać konwencje odwołania przez operator indeksacji na rzecz wskaźnikowego i odwrotnie aby zyskać parę sekund, kosztem zaciemnienia kodu ?
Dalej - w zasadzie przedstawiłem 2 konwencje główne - długą i krótką, nie wspominając o różnym sposobie odwołania do tablicy.
W konwencji długiej widać w zasadzie która zmienna odpowiedzialna jest za losowanie kolejnej liczby a która za zapis do odpowiedniej komórki tablicy. Można przed zapisem zmiennej do tablicy sprawdzić jaka wartość zostanie zapisana.
Moim zdaniem ten kod jest czytelniejszy, bezpieczniejszy i łatwiejszy do ewentualnego debuggowania czy dopisania testu. Stan ten jest okupiony znacznie gorszą wydajnością , niemal 0.1sec.
Czy warto zredukować ilość zmiennych jedynie do sterujących pętlami i zyskać tą wydajność kosztem bezpieczeństwa, łatwości diagnozy i utrzymania wiedząc, że ten kod prawie na pewno jest prawidłowy i raczej nie będzie się go testować/zmieniać ?
Wiem, że problem jest trywialny jednak chodzi mi o analogie do dużych problemów. Jak zachować się wówczas i jakie konwencje przyjąć.
Gwoli ścisłości powyższe kody wykonują się 1kk razy - myślę, że jest to dostateczna ilość, żeby sprawdzić ich wydajność.
Ponadto mam nadzieje, że komuś będzie się chciało poświęcić mi parę chwil na wyjaśnienie problemu.</cpp></cpp></cpp>