Wielokrotna indeksacja a użycie zmiennych pomocniczych

0

Mam pytanie dotyczące używania zmiennych pomocniczych w pętli obsługującej tablice. Poniżej przykład funkcji z książki Prata:

int fill_array(double ar[], int limit)



    using namespace std;

double temp;



int i;

for (i O; i < limit; i++)

    cout << "Podaj wartość nr " << (i + 1) << " · ";

cin >> temp;

if (!cin) {

    cin.elear();

    while (cin.get() != '\n')

        continue;

    cout << "Błędne dane, wprowadzanie danych przerwane.\n";

    break;

    else if (temp < 0) break;

    ar[i] = temp;

    return i;

}

Wartość przypisywana jest do zmiennej Temp a dopiero po wszystkich operacjach warunkowych przypisywana jest do tablicy. Ostatnio ktoś recenzował mój kod i otrzymałem zarzut wielokrotnej indeksacji. Dotyczyło to porównań kolejnych elementów tablicy w pętli for. Zasugerowano mi najpierw w petli przypisanie wartości tablicy do zmiennej pomocniczej a później wykonywanie operacji porównań i warunków na zmiennej pomocniczej a nie na elemencie tablicy. Zastanawiam się w jaki sposób zwiększa to wydajność kodu, bo przy przypisaniu w pętli elementu tablicy za każdym razem trzeba "szperać" po indeksach i do tego trzeba tworzyc nową zmienną, chyba że porównania i warunki szybciej wykonuje się na prostych zmiennych niż na elementach tablic. Jeszcze mam jedno pytanie. Jeżeli znowu potrzebuje robić w pętli operacje na tablicach np. w pętli for taką operacje:

for (int i=0; i<n;i++)
     if (tablica1[i+zmienna1-zmienna2+1]<tablica2[i+zmienna3-zmienna4+1]
  ......

to powinienem te skomplikowane indeksy będące wynikiem działania najpierw przypisać zmiennej pomocniczej, a później jako indeksu tablic użyć tej zmiennej pomocniczej, czyli

ZmiennaPomocnicza=i+zmienna1-zmienna2+1;
.....
 if (tablica[ZmiennaPomocnicza]< itd.....
8

Wydajność może być identyczna, ale czytelność jest szalenie ważną metryką kodu, na którą zdajesz się zupełnie nie zwracać uwagi.

2

Tworząc kod warto pamiętać o tym aby nie był on źródłem późniejszych błędów. Używanie skomplikowanych indeksów w stylu i+zmienna1-zmienna2+1 jest prostą drogą do sięgnięcia poza zakres tablicy. Używanie dodatkowej/pomocniczej zmiennej niczego tutaj nie zmieni i dodatkowo zaciemni kod. Dlatego też, użycie indeksów w tablicach powinno być jak najbardziej proste i czytelne, a wszędzie tam gdzie można to zrobić, należy ich unikać.
Jedna z opcji jest użycie kontenerów, które wspierają użycie pętli zakresowej.

vector<int> data {1,5,3,7,5,24};
for( const auto& element : data )
{ 
    cout << element << " , ";
}
0

czyli wszystko co jest skomplikowane (np indeksy tablic będące działaniem) pakować najpierw w zmienne pomocnicze? a co ze zwykłym indeksem, tak jak w przykładowym kodzie z książki prata, dlaczego mimo, że tablica nie jest nieczytelna ar[i] to i tak wartość najpierw idzie do zmiennej tymczasowej, potem na tej zmiennej sprawdzane są warunki i dopiero na koniec wartość przypisywana jest do tablicy, dlaczego powinno się tak robić, zamiast operowania bezpośrednio na tablicy?

4

Pamiętaj, że w C i C++ obowiązuje zasada "AS IF" (tak jak).
To oznacza, że kompilator ma prawo dowolnie przekształcić kod dopóki wynik działania kodu pozostaje bez zmian.

Czyli nie ma co się zastanawiać nad takimi detalami, gdzie i jak jest zdefiniowana zmienna indeksująca pętlę for. Kompilator i tak zrobi to po swojemu (potrafi różne zaskakujące cuda).
Ergo pisz kod dla ludzi! Czyli posłuchaj kq i pisz tak, żeby ty oraz inni nie mieli kłopotu ze zrozumieniem intencji co kod ma robić.

0

Właśnie dlatego dziwił mnie zarzut o wielokrotne indeksowanie, bo przecież kompilator i tak to sobie wrzuci tam gdzie będzie mu pasowało do wielokrotnego korzystania, albo incydentalnego. Miałem też zarzut o puste miejsca w kodzie, czyli linijka odstępu przed jakimiś bardziej skomplikowanymi sekcjami kodu. Zrobiłem to celowo, żeby łatwo można było wyróżnić poszczególne główne sekcje. Ale okazuje się, że to źle. Mam pytanie bo jestem totalnie na początku i uczę się głównie sam, jakie są dobre praktyki, tak żeby styl był czytelny. Póki co to wiem, że: zmienne mają mieć nazwy wskazujące na ich przeznaczenie, do funkcji zazwyczaj nie przekazuje się więcej niż trzech parametrów (oczywiście są sytuacje że trzeba więcej), konsekwentne marginesy, maksymalna prostota, używanie najmniej pamięciożernych typów liczbowych w kontekście zadania coś byście jeszcze dodali?

3
srk71 napisał(a):
for (int i=0; i<n;i++)
     if (tablica1[i+zmienna1-zmienna2+1]<tablica2[i+zmienna3-zmienna4+1]
  ......

O ile zmienna1, zmienna2, zmienna3, zmienna4 nie zmieniają wartości w trakcie działania pętli to lepiej:

for (int i=0,i1=zmienna1-zmienna2+1,i2=zmienna3-zmienna4+1;i<n;++i,++i1,++i2)
      if (tablica1[i1]<tablica2[i2])

Ale dla procesorów 486 ++ szybsza będzie (wg mnie bez zmiany czytelności):

for (int i=0,i1=zmienna1-zmienna2+1,i2=zmienna3-zmienna4+1;i<n;++i)
      if (tablica1[i+i1]<tablica2[i+i2])
0

Jeśli chodzi o wydajność to także może (choć nie musi) mieć swoje uzasadnienie. Wszystko rozbija się do dostęp do cache procesora. W przypadku gdy czytasz bezpośrednio z tablicy z różnych jej zakresów, możesz nie trafiać w cache (cache miss), przez co dane będą czytane z RAM, a to jest kosztowne. Aczkolwiek nie musi tak być - wszystko zależy od tego, jaką masz konkretnie systuację w kodzie i na jakiej platformie i sprzęcie odpalasz program.
Dodatkowo, jeśli Twój kod jest wielowątkowy, może wystąpić zjawisko zwane "false sharing". I tutaj dobrym remedium w wielu przypadkach jest właśnie wykonywanie operacji na zmiennych lokalnych.

Niemniej wszystko zależy od architektury Twojego programu i do każdego przypadku trzeba podejść indywidualnie. Za mało kodu pokazałeś, żeby można było bardziej konkretnie się wypowiedzieć. Natomiast wydaje mi się, że praktykę przypisywania do zmiennych lokalnych można potraktować jako programowanie defensywne, co w moim odczuciu nie w każdym przypadku musi mieć sens (ale warto mieć na to uzasadnienie - bezrefleksyjne pisanie kodu jest złe)

3
srk71 napisał(a):

Właśnie dlatego dziwił mnie zarzut o wielokrotne indeksowanie, bo przecież kompilator i tak to sobie wrzuci tam gdzie będzie mu pasowało do wielokrotnego korzystania,
albo incydentalnego. Miałem też zarzut o puste miejsca w kodzie, czyli linijka odstępu przed jakimiś bardziej skomplikowanymi sekcjami kodu.

Cały czas piszemy ci o czytelności kodu! A to wpisuje się w te "zarzuty".
Załączony przez ciebie kod to sieczka.

Poleciłby ci Unckle Bob-a, ale chyba jeszcze dla ciebie za wcześnie by zrozumieć o czym ten gość opowiada.
Może powinieneś poczytać o "coding standard" (ma ktoś dobrego linka pod ręką). Może to będzie dobre: https://google.github.io/styleguide/cppguide.html

0

ten pierwszy kod to sieczka?

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