Jak pisać optymalny kod?

0

Hej!

Zastanawiam sie ostatnio nad optymalnoscia, czytelnoscia etc. moich aplikacji. W zwiazku z tym mam pytanie do Was jako zawodowcow :)

Jak lepiej pisac? Ujmowac jakies wielkosci w zmienne i potem to wykorzystywac czy za kazdym razem uzywac np. funkcji zwracajacej wielkosc tablicy?

Przyklad:

Lepiej:

 
zmienna:=Length(tablica);

for i:=0 to zmienna-1 do

...

czy lepiej:

for i:=0 to Length(tablica)-1 do

zdaje sobie sprawe, ze "to zalezy" ale zalozmy, ze nawet chcialbym wykorzystac wielkosc tablicy w dalszej czesci programu.

Prosze o porade :)

3

Liczba iteracji pętli For obliczana jest raz, więc to zależy od przypadku;

Jeśli długość tablicy potrzebna jest także przed pętlą lub po niej, to należy ową długość wpisać do zmiennej, by nie wywoływać wielokrotnie tej samej funkcji (tu: Length); Natomiast jeśli długość tablicy potrzebna jest jedynie do użycia w pętli For - zmienna nie jest potrzebna i nie powinno się jej w takim przypadku deklarować;

I jeszcze jedna sprawa, jeśli o pętle chodzi; Jeżeli długość tablicy czy ciągu znaków ma posłużyć jedynie pętli, warto skorzystać z funkcji Low i High; Co więcej, jeśli z tej tablicy jedynie odczytujemy dane (choć nie tylko o odczyt chodzi - zależne od przypadku) to warto się też zastanowić nad pętlą For In Do;

Ogólnie rzecz biorąc, dobry kod to taki, który zajmuje mało kodu (czytelnego kodu), wykonuje się szybko oraz rezerwuje we własnym zakresie jak najmniejszą ilość pamięci; Zapoznanie się z zasadami KISS i DRY na pewno pomoże w pisaniu dobrego kodu; No i oczywiście stosowanie się do przyjętej konwencji nazewnictwa.

7

Pisz czytelnie, niezależnie od języka.

Jak wydajność okaże się za mała, wtedy profilujesz i szukasz lepszych algorytmów. Dopiero jak to nie starcza to wtedy zastanawiasz się nad takimi rzeczami jak ww. wymieniona.

5

Kod źródłowy powinien być przede wszystkim czytelny dla człowieka, komputer jest na drugim miejscu.

2

Olej optymalność, jak piszą koledzy. Rób taki kod, żebyś po roku był w stanie zrozumieć o co ci chodziło. Lub żeby inna osoba potrafiła zrozumieć twój kod w kilka minut.

Optymalnością (w rozumieniu: wydajność) martw się dopiero, gdy stanie się problemem. Komputery obecnie są strasznie szybkie i jeśli nie zrobisz czegoś debilnego, to program będzie działał dostatecznie szybko, nawet w wolniej wykonujących się językach.

1

IMHO powinno się brać oba te aspekty pod uwagę. Nie powinno się pisać kodu wyłącznie czytelnego, bądź wyłącznie optymalnego. Kod powinien być na tyle zoptymalizowany na ile nie straci na czytelności. Argument czytelności można w ogóle pominąć dopiero wtedy gdy wydajność kodu jest niewystarczająca, jednak wtedy wypada taki kod opatrzyć komentarzem. Z kolei argumentu wydajności nie powinniśmy nigdy pomijać, ponieważ nie wiemy na jakim sprzęcie będzie wykonywany nasz program oraz nie wiemy czy napisany przez nasz kod lub jego fragment nie będą w przyszłości częścią większej/innej całości w której wydajność będzie miała kluczowe znaczenie.

W tym konkretnym przypadku powinieneś zapisywać długość tablicy do zmiennej, jeżeli ta wartość będzie używana kilkukrotnie.

3

Nam na uczelni prowadzący zawsze mówił, że ważniejsze jest, żeby kod był dobrze działający i w miarę sprawnie napisany. Droższy jest czas pracy programisty od czasu pracy komputera ;d

1
furious programming napisał(a):

Liczba iteracji pętli For obliczana jest raz, więc to zależy od przypadku;

Trochę się tu sam wykluczasz. Albo jest obliczana raz, albo zależy od przypadku.

Jeśli chodzi o prosty: Length(array), to jestem w stanie uwierzyć, że kompilator tak to sobie zoptymalizuje, że wywoła się tylko raz. Ale jeśli jest funkcja bardziej skomplikowana, np:

 
function GetCount(): integer;
var
  i: integer;
begin
    i := Length(arr);
    result := i / 2;
end;

To jestem pewien, że taka funkcja wywoła się przy każdej iteracji. Dlatego moja odpowiedź jest taka:
Jeśli ilość iteracji w pętli może ulec zmianie podczas jej działania, to TYLKO wtedy posługuj się funkcją. W przeciwnym razie bardziej optymalnie będzie przed pętlą ustawić w zmiennej ilość iteracji. Oczywiście pomijam tu wbudowane funkcje typu: "Length(array)", które najpewniej są już odpowiednio zoptymalizowane pod tym kątem. Chociaż też ciężko stwierdzić.

1

Trochę się tu sam wykluczasz. Albo jest obliczana raz, albo zależy od przypadku.

Nie - liczba iteracji pętli For obliczana jest raz, natomiast druga część zdania dotyczy pytania o wykorzystanie dodatkowej zmiennej lub nie, do przechowywania obliczonej długości;

Jeśli o Twój kod chodzi, to nawet się nie skompiluje; I jest za długi:

function GetCount(): Integer;
begin
  Result := Length(arr) div 2;
end;

Mniejsza o to; Ogólna zasada pisania dobrego kodu, tak jak wspomniałem wcześniej, zakłada stosowanie jak najmniejszej liczby instrukcji oraz alokacji jak najmniejszej ilości pamięci;

Więc jeśli omawiana długość macierzy potrzebna jest tylko i wyłącznie do określenia liczby iteracji pętli to nie deklarujemy zmiennej i w nagłówku pętli wywołujemy odpowiednią funkcję - np. Length czy High;

Niczego nie zmienia fakt, iż wyliczenie długości czy wielkości jest bardziej skomplikowane; Bo nawet najdłużej i najbardziej skrupulatnie obliczony wynik, jeśli ma zostać użyty tylko w jednym miejscu - zmiennej nie potrzebuje; Chyba że ze względu na czytelność;


@karpov - tytuł wątku sugeruje pytanie o ogólne zasady pisania optymalnego kodu; Natomiast podany przykład sięga aż do podstaw podstaw programowania, czyli ograniczania ilości kodu i potrzebnych instrukcji;

W razie czego polecam ten artykuł na początek - http://www.dzyszla.aplus.pl/porada-13.html

PS: Pasowałoby też zorientować się, co używany kompilator robi podczas optymalizacji; Bo może się okazać, że kompilator i tak wszystko zrobi po swojemu.

0
furious programming napisał(a):

Jeśli o Twój kod chodzi, to nawet się nie skompiluje; I jest za długi:

Ciekawe rzeczy mówisz. Zwłaszcza z tym, że jest za długi. Kompiluje się normalnie. Niemniej jednak masz część racji. Otóż, kod e Delphi:

var
  arr: array of integer;
  g_count: integer;

function GetCount(): Integer;
begin
  g_count := g_count + 1;
  Result := Length(arr)
end;

procedure SetArray();
var
  i: integer;
begin
  setLength(arr, 10);
  for i := 0 to 9 do
    arr[i] := i;
end;

procedure DoJob();
var
  i: integer;
begin
  for I := low(arr) to GetCount() - 1 do
    writeln(intToStr(arr[i]));
end;

begin
  g_count := 0;
  SetArray();
  DoJob();

  writeln(g_count);

  readln;

end.

pokazuje, że GetCount faktycznie wykona się tylko raz. Ale. Analogiczny kod w C++ już działa zupełnie inaczej:

int g_count;
std::vector<int> arr;

size_t getCount()
{
	g_count++;
	return arr.size();
}

void setArray()
{
	for (int i = 0; i < 10; ++i)
		arr.push_back(i);
}

void doJob()
{
	for (size_t i = 0; i < getCount(); ++i)
		cout << arr[i] << endl;
}

int _tmain(int argc, _TCHAR* argv[])
{
	g_count = 0;
	setArray();
	doJob();

	cout << "g_count: " << g_count;

	getchar();
	return 0;
}
 

W tym wypadku funkcja getCount() jest wywoływana za KAŻDYM razem.

1
Juhas napisał(a):

W tym wypadku funkcja getCount() jest wywoływana za KAŻDYM razem.

Bo w C++ musi się tak stać. getCount modyfikuje globalną zmienną, więc nie może zostać to zmienione. To się nazywa efekt uboczny (side effect), gdyby go nie było, loop invariant code motion mogło by zostać zastosowane.

Zobacz też atrybuty funkcji const oraz pure w GCC np.

3
Juhas napisał(a)

pokazuje, że GetCount faktycznie wykona się tylko raz. Ale. Analogiczny kod w C++ już działa zupełnie inaczej:

Przypominam, że wątek zawiera tag Delphi i jednocześnie nie zawiera tagu C++, więc ciągniesz off-top;

Pomiędzy kompilatorami Delphi a C++ jest bardzo duża różnica, tak samo jak między samymi językami, więc porównywanie ich nie ma sensu - za dużo je różni; Chyba że jako ciekawostka, w celu uwidocznienia właśnie tych różnic.

1

To, że w C/C++ zadziała inaczej - jest to kwestia innego sposobu działania pętli for w C/C++ niż w delphi. Pętla for pętli for nie jest równa.

1

"premature optimization is the root of all evil" :)
http://c2.com/cgi/wiki?PrematureOptimization

w większości przypadków jak coś działa wolno, to jest jakieś wąskie gardło (np. rendering wielu obiektów na ekranie, dostęp do bazy danych, dostęp do dysku, jeden konkretny niewydajny algorytm), a nie dlatego, że same pętle są niewydajne (to się nazywa mikrooptymalizacja i czasem się opłaca, ale zwykle można to olać).

0

Nie obliczać dwa razy tego samego tylko trzymać w pamieci i odczytywać przy pomocy jak najszybszych algorytmów/struktur danych.

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