Porównanie paradygmatów programowania
W tym artykule podjęto próbę przedstawienia różnych podobieństw i różnic między różnymi paradygmatami programowania w postaci podsumowania zarówno w formacie graficznym, jak i tabelarycznym, z linkami do oddzielnych dyskusji dotyczących tych podobieństw i różnic w zachowanych artykułach w Wikipedii.
Główne podejścia paradygmatu
Istnieją dwa główne podejścia do programowania:
- Programowanie imperatywne – koncentruje się na sposobie wykonywania, definiuje przepływ sterowania jako instrukcje zmieniające stan programu .
- Programowanie deklaratywne – koncentruje się na tym, co należy wykonać, definiuje logikę programu, ale nie szczegółowo kontroluje przebieg .
Następujące są powszechnie uważane za główne paradygmaty programowania, co widać podczas pomiaru popularności języka programowania :
- Programowanie proceduralne – określa kroki, które program musi wykonać, aby osiągnąć pożądany stan.
- Programowanie funkcyjne - traktuje programy jako oceniające funkcje matematyczne i unika danych stanu i zmiennych .
- Programowanie zorientowane obiektowo (OOP) – organizuje programy jako obiekty : struktury danych składające się z atrybutów i metod wraz z ich interakcjami.
Poniżej przedstawiono typowe typy programowania, które można zaimplementować przy użyciu różnych paradygmatów:
- Programowanie sterowane zdarzeniami – przepływ sterowania programem jest określany przez zdarzenia , takie jak wejścia czujników lub działania użytkownika ( kliknięcia myszą , naciśnięcia klawiszy) lub komunikaty z innych programów lub wątków .
- Programowanie oparte na automatach – program lub część jest traktowana jako model maszyny skończonej lub dowolnego innego automatu formalnego.
- Programowanie reaktywne to paradygmat programowania deklaratywnego dotyczący strumieni danych i propagacji zmian.
Podprogramy, które implementują metody OOP, mogą być ostatecznie zakodowane w stylu imperatywnym, funkcjonalnym lub proceduralnym, który może, ale nie musi, bezpośrednio zmieniać stan w imieniu wywołującego programu. Nieuchronnie pewne nakładanie się paradygmatów, ale główne cechy lub możliwe do zidentyfikowania różnice podsumowano w tej tabeli:
Różnice w terminologii
Pomimo wielu (typów) paradygmatów programowania istniejących równolegle (z czasami pozornie sprzecznymi definicjami), wiele podstawowych komponentów pozostaje mniej więcej takich samych ( stałe , zmienne , pola danych , wywołania podprogramów itp.) i nieuchronnie muszą być włączone do każdego odrębny paradygmat o jednakowo podobnych atrybutach lub funkcjach. Powyższa tabela nie ma służyć jako przewodnik po dokładnych podobieństwach, ale raczej jako indeks, gdzie szukać więcej informacji, w oparciu o różne nazewnictwo tych podmiotów w ramach każdego paradygmatu. Dalszą komplikacją są niestandardowe implementacje każdego paradygmatu w wielu językach programowania , zwłaszcza w językach obsługujących wiele paradygmatów , z których każdy ma swój własny żargon .
Wsparcie językowe
Cukier składniowy to dosładzanie funkcjonalności programu poprzez wprowadzanie cech językowych ułatwiających dane użycie, nawet jeśli bez nich można by osiągnąć efekt końcowy. Jednym z przykładów cukru syntaktycznego mogą być prawdopodobnie klasy używane w obiektowych językach programowania. Imperatywny język C może obsługiwać programowanie zorientowane obiektowo za pomocą wskaźników funkcji , rzutowania typów i struktur. Jednak języki takie jak C++ mają na celu uczynienie programowania obiektowego wygodniejszym poprzez wprowadzenie składni specyficznej dla tego stylu kodowania. Ponadto wyspecjalizowana składnia działa w celu podkreślenia podejścia obiektowego. Podobnie funkcje i składnia pętli w C (i innych proceduralnych i strukturalnych językach programowania) można uznać za cukier składniowy. Język asemblera może wspierać programowanie proceduralne lub strukturalne poprzez swoje możliwości modyfikowania wartości rejestrów i wykonywania rozgałęzień w zależności od stanu programu. Jednak języki takie jak C wprowadziły składnię specyficzną dla tych stylów kodowania, aby uczynić programowanie proceduralne i strukturalne wygodniejszym. Funkcje języka C# (C Sharp), takie jak właściwości i interfejsy, podobnie nie udostępniają żadnych nowych funkcji, ale mają na celu uczynienie dobrych praktyk programistycznych bardziej widocznymi i naturalnymi.
Niektórzy programiści uważają, że te funkcje są nieważne, a nawet niepoważne. Na przykład Alan Perlis zażartował kiedyś, odnosząc się do języków rozdzielonych nawiasami kwadratowymi , że „cukier składniowy powoduje raka średnika ” (patrz Epigramy o programowaniu ).
Rozszerzeniem tego jest syntaktyczna sacharyna lub nieuzasadniona składnia, która nie ułatwia programowania.
Porównanie wydajności
pod względem całkowitej długości ścieżki instrukcji program zakodowany w stylu imperatywnym, bez podprogramów, miałby najniższą liczbę. Jednak binarny takiego programu może być większy niż ten sam program zakodowany przy użyciu podprogramów (jak w programowaniu funkcjonalnym i proceduralnym) i odwołuje się do większej liczby nielokalnych fizycznych , które mogą zwiększać instrukcji braki w pamięci podręcznej i narzut pobierania instrukcji w nowoczesnych procesorach .
Paradygmaty, które intensywnie wykorzystują podprogramy (w tym funkcjonalne, proceduralne i zorientowane obiektowo) i nie używają również znaczącego rozszerzenia wbudowanego (wstawianie poprzez optymalizacje kompilatora ), będą w konsekwencji zużywać większą część całkowitych zasobów na powiązania podprogramów. Programy zorientowane obiektowo, które celowo nie zmieniają stanu programu , zamiast tego używają metod mutator (lub setterów ) do enkapsulacji tych zmian stanu, będą, w bezpośredniej konsekwencji, mieć większy narzut. Dzieje się tak, ponieważ przekazywanie komunikatów jest zasadniczo wywołaniem podprogramu, ale z trzema dodatkowymi kosztami: dynamiczną alokacją pamięci , kopiowaniem parametrów i dynamiczną wysyłką . Pobieranie pamięci ze sterty i kopiowanie parametrów do przekazywania komunikatów może wymagać znacznych zasobów, znacznie przekraczających te potrzebne do zmiany stanu. Akcesory (lub gettery ), które jedynie zwracają wartości prywatnych zmiennych składowych, również zależą od podobnych podprogramów przekazywania komunikatów, zamiast używać bardziej bezpośredniego przypisania (lub porównania), dodając do całkowitej długości ścieżki.
Zarządzany kod
W przypadku programów uruchamianych w środowisku kodu zarządzanego , takim jak .NET Framework , wiele problemów wpływa na wydajność, na które znacząco wpływa paradygmat języka programowania i różne używane funkcje języka.
Przykłady pseudokodów porównujące różne paradygmaty
Pseudokodowe porównanie podejść imperatywnych, proceduralnych i obiektowych stosowanych do obliczania pola koła (πr²), przy założeniu braku wstawiania podprogramów , żadnych preprocesorów makr , arytmetyki rejestrów i ważenia każdej instrukcji „krok” jako tylko 1 instrukcja - jako przybliżona miara długości ścieżki instrukcji – przedstawiono poniżej. Krok instrukcji, który koncepcyjnie przeprowadza zmianę stanu, jest w każdym przypadku wyróżniony pogrubioną czcionką. Operacje arytmetyczne używane do obliczania pola koła są takie same we wszystkich trzech paradygmatach, z tą różnicą, że paradygmaty proceduralne i zorientowane obiektowo owijają te operacje w wywołanie podprogramu, dzięki czemu obliczenia są ogólne i wielokrotnego użytku. Ten sam efekt można osiągnąć w czysto imperatywnym programie używającym preprocesora makr tylko kosztem zwiększonego rozmiaru programu (tylko w każdym miejscu wywołania makra) bez odpowiedniego proporcjonalnego kosztu czasu wykonania (proporcjonalnego do n wywołań – które mogą znajdować się w na przykład wewnętrzna pętla ). I odwrotnie, wstawianie podprogramów przez kompilator może zredukować programy proceduralne do czegoś podobnego pod względem rozmiaru do kodu czysto imperatywnego. Jednak w przypadku programów zorientowanych obiektowo, nawet z wstawianiem, komunikaty nadal muszą być budowane (z kopii argumentów) do przetwarzania metodami obiektowymi. Narzut wywołań, wirtualnych lub innych, nie jest zdominowany przez przepływu sterowania – ale przez otaczające koszty konwencji wywołania , takie jak kod prologu i epilogu , konfiguracja stosu i przekazywanie argumentów (zobacz tutaj, aby uzyskać bardziej realistyczną długość ścieżki instrukcji, stos i inne koszty związane z połączeniami na platformie x86 ). Zobacz także tutaj prezentację slajdów autorstwa Erica S. Robertsa („The Allocation of Memory to Variables”, rozdział 7) - ilustrującą użycie pamięci stosu i sterty podczas sumowania trzech liczb wymiernych w obiektowym języku Java .
Pilny | Proceduralny | Zorientowany obiektowo |
---|---|---|
załaduj r; 1 r2 = r * r; 2 wynik = r2 * "3,142"; 3 . . . . . . . . . . . . . . . . . . ....pamięć............zmienna wynikowa stała "3,142" |
area proc(r2,res): push stack 5 load r2; 6 r3 = r2 * r2; 7 res = r3 * "3,142"; 8 pop stos 9 powrót; 10 ....................................................... główny proc : obciążenie r; 1 obszar wywołania(r,wynik); +load p = adres listy parametrów; 2 +load v = adres podprogramu 'obszar'; 3 +goto v z powrotem; 4 . . . . ....pamięć ...............zmienna wynikowa stała "3.142" lista parametrów zmienna wskaźnik funkcji (==>obszar) pamięć stosu |
metoda okręgu.obszar (r2): wciśnij stos 7 załaduj r2; 8 r3 = r2 * r2; 9 res = r3 * "3,142"; 10 pop stack 11 return(res); 12,13 ......................................................... główny proces: załaduj r; 1 wynik = okrąg.obszar(r); +przydzielanie pamięci sterty; 2 + skopiuj r do wiadomości; 3 +load p = adres wiadomości; 4 +obciążenie v = adres. metody 'circle.area' 5 +goto v z powrotem; 6 . . .... storage ............... zmienna wyniku (zakłada się, że jest wstępnie przydzielona) niezmienna zmienna "3.142" (końcowa) (sterta) zmienna komunikatu dla wywołania metody okręgu vtable(==>area) przechowywanie stosu |
Zalety abstrakcji proceduralnej i polimorfizmu zorientowanego obiektowo słabo ilustruje mały przykład, taki jak ten powyżej. Ten przykład został zaprojektowany głównie w celu zilustrowania pewnych wewnętrznych różnic w wydajności, a nie abstrakcji lub ponownego użycia kodu.
Podprogram, narzut wywołania metody
Obecność (nazywanego) podprogramu w programie nie wnosi nic więcej do funkcjonalności programu, niezależnie od paradygmatu, ale może znacznie przyczynić się do ustrukturyzowania i ogólności programu, znacznie ułatwiając pisanie, modyfikowanie i rozszerzanie. Stopień, w jakim różne paradygmaty wykorzystują podprogramy (i wynikające z nich wymagania dotyczące pamięci) wpływa na ogólną wydajność całego algorytmu, chociaż, jak Guy Steele w artykule z 1977 r., Dobrze zaprojektowana implementacja języka programowania może mieć bardzo niskie koszty ogólne abstrakcji proceduralnej (ale ubolewa, że w większości wdrożeń rzadko osiągają to w praktyce - będąc „raczej bezmyślnymi lub nieostrożnymi w tym względzie”). W tym samym artykule Steele rozważa również programowanie oparte na automatach (używając wywołań procedur z rekurencją ogona ) i stwierdza, że „powinniśmy mieć zdrowy szacunek dla wywołań procedur” (ponieważ są one potężne), ale zasugerował „używaj ich oszczędnie "
W częstotliwości wywołań podprogramów:
- W przypadku programowania proceduralnego ziarnistość kodu jest w dużej mierze określana przez liczbę dyskretnych procedur lub modułów .
- W przypadku programowania funkcjonalnego częste są wywołania podprogramów bibliotecznych , [ potrzebne źródło ] , ale często mogą być wstawiane przez kompilator optymalizujący
- W przypadku programowania zorientowanego obiektowo liczba wywoływanych metod jest również częściowo określona przez ziarnistość struktur danych, a zatem może obejmować wiele dostępów tylko do odczytu do obiektów niskiego poziomu, które są hermetyzowane, a zatem dostępne w żadnym innym, bardziej bezpośrednim, sposób. Ponieważ zwiększona ziarnistość jest warunkiem wstępnym większego ponownego użycia kodu , istnieje tendencja w kierunku drobnoziarnistych struktur danych i odpowiedniego wzrostu liczby dyskretnych obiektów (i ich metod), aw konsekwencji wywołań podprogramów. Aktywnie odradza się tworzenie boskich obiektów . Konstruktory również zwiększają liczbę, ponieważ są również wywołaniami podprogramów (chyba że są wstawiane). Problemy z wydajnością spowodowane nadmierną szczegółowością mogą nie być widoczne, dopóki skalowalność nie stanie się problemem.
- W przypadku innych paradygmatów, w których można zastosować mieszankę powyższych paradygmatów, użycie podprogramu jest mniej przewidywalne.
Alokacja pamięci dynamicznej do przechowywania komunikatów i obiektów
W wyjątkowy sposób paradygmat zorientowany obiektowo obejmuje dynamiczną alokację pamięci z magazynu sterty zarówno do tworzenia obiektów, jak i przekazywania komunikatów. Test porównawczy z 1994 r. — „Koszty alokacji pamięci w dużych programach C i C++”, przeprowadzony przez Digital Equipment Corporation na różnych programach przy użyciu narzędzia do profilowania na poziomie instrukcji, zmierzył liczbę instrukcji wymaganych na dynamiczną alokację pamięci. Wyniki pokazały, że najniższa bezwzględna liczba wykonanych instrukcji wynosiła średnio około 50, ale inne osiągnęły nawet 611. Zobacz także „Heap: Pleasures and pains” autorstwa Murali R. Krishnan, który stwierdza: „Implementacje sterty zwykle pozostają ogólne dla wszystkich platform i stąd mają duże koszty ogólne”. Artykuł IBM z 1996 r. „Scalability of Dynamic Storage Allocation Algorithms” autorstwa Aruna Iyengara z IBM demonstruje różne algorytmy dynamicznej pamięci masowej i odpowiadające im liczby instrukcji. Nawet zalecany algorytm MFLF I (HS Stone, RC 9674) pokazuje liczbę instrukcji w zakresie od 200 do 400. Powyższy przykład pseudokodu nie zawiera realistycznego oszacowania długości ścieżki alokacji pamięci ani narzutów związanych z prefiksem pamięci i późniejszych powiązanych śmieci koszty poboru. Sugerując mocno, że alokacja sterty nie jest trywialnym zadaniem, jeden oprogramowania typu open source , autorstwa twórcy gier Johna W. Ratcliffa , składa się z prawie 1000 linii kodu.
Dynamicznie wysyłane wywołania komunikatów a bezpośrednie narzuty wywołań procedur
W swoim streszczeniu „ Optymalizacja programów obiektowych przy użyciu statycznej analizy hierarchii klas ” Jeffrey Dean, David Grove i Craig Chambers z Wydziału Informatyki i Inżynierii Uniwersytetu Waszyngtońskiego twierdzą, że „duże wykorzystanie dziedziczenia i dynamiczne komunikaty powiązane prawdopodobnie sprawią, że kod będzie bardziej rozszerzalny i będzie można go ponownie wykorzystać, ale powoduje to również znaczny wzrost wydajności w stosunku do równoważnego, ale nierozszerzalnego programu napisanego w sposób nie zorientowany obiektowo. W niektórych domenach, takich jak pakiety grafiki strukturalnej , koszt wydajności związany z dodatkową elastycznością zapewnianą przez stosowanie stylu silnie zorientowanego obiektowo jest akceptowalny.Jednakże w innych dziedzinach, takich jak biblioteki podstawowych struktur danych, pakiety obliczeń numerycznych, biblioteki renderowania i ramy symulacji oparte na śledzeniu, koszt przekazywanie komunikatów może być zbyt duże, zmuszając programistę do unikania programowania obiektowego w „gorących punktach” ich aplikacji”.
Serializacja obiektów
Serializacja nakłada duże koszty ogólne podczas przekazywania obiektów z jednego systemu do drugiego, zwłaszcza gdy transfer odbywa się w formatach czytelnych dla człowieka, takich jak Extensible Markup Language ( XML ) i JavaScript Object Notation ( JSON ). Kontrastuje to z kompaktowymi formatami binarnymi dla danych niezorientowanych obiektowo. Zarówno kodowanie, jak i dekodowanie wartości danych obiektu i jego atrybutów jest zaangażowane w proces serializacji, który obejmuje również świadomość złożonych zagadnień, takich jak dziedziczenie, enkapsulacja i ukrywanie danych.
Równoległe obliczenia
Profesor Uniwersytetu Carnegie-Mellon, Robert Harper , napisał w marcu 2011 r.: „W tym semestrze Dan Licata i ja wspólnie prowadzimy nowy kurs programowania funkcjonalnego dla przyszłych kierunków CS na pierwszym roku… Programowanie obiektowe jest całkowicie wyeliminowane z programu wprowadzającego , ponieważ jest z natury zarówno antymodułowy, jak i antyrównoległy, a zatem nie nadaje się do nowoczesnego programu nauczania CS Proponowany nowy kurs metodologii projektowania zorientowanego obiektowo będzie oferowany na poziomie drugiego stopnia dla tych studentów, którzy chcą studiować ten temat."
Zobacz też
- Porównanie języków programowania
- Porównanie języków programowania (podstawowe instrukcje)
- Szczegółowość # Obliczanie
- Przekazywanie wiadomości
- Podprogram
Dalsza lektura
- „Alokator pamięci” Douga Lea
- „Dynamiczna alokacja pamięci i połączone struktury danych” autorstwa ( Szkockiego Urzędu ds. Kwalifikacji )
- „Inside A Storage Alocator” dr Newcomer Ph.D
Linki zewnętrzne
- Porównanie paradygmatów programowania autorstwa dr Rachel Harrison i pana Linsa Samaraweery
- Porównywanie paradygmatów programowania: ocena programów funkcjonalnych i obiektowych przez Harrison, R. , Samaraweera, LG, Dobie, MR i Lewis, PH (1996) s. 247–254. ISSN 0268-6961
- „Główne paradygmaty programowania” Peter Van Roy
- „Koncepcje, techniki i modele programowania komputerowego” (2004) autorstwa Petera Van Roya i Seifa Haridiego, ISBN 0-262-22069-5
- Prawdziwy koszt połączeń — z bloga „Harder, Better, Faster, Stronger” autorstwa informatyka Stevena Pigeona