Archive for Programming

Koder, istota niepokorna

2007-12-09 21:52

Tę notkę mógłbym zacząć znanym stwierdzeniem: A miało być tak pięknie :) Przecież to w końcu na studiach człowiek zaczyna w pełni kierować własną edukacją i może zająć się poznawaniem tych zagadnień, które go naprawdę ciekawią. Koniec z wyjaśnianiem, dlaczego Słowacki wielkim poetą był i zapamiętywaniem szczegółów budowy mięśni poprzecznie-prążkowanych. Nareszcie przychodzi czas na bardziej przydatne i interesujące umiejętności – czyli programowanie, rzecz jasna.

A jak wygląda przełożenie tej teorii na praktykę? Otóż po dwóch latach mogę stwierdzić, że… dość kiepsko :) W istocie mamy tu do czynienia z wielkim dylematem – może nie natury filozoficznej, ale jednak całkiem ciężkiego kalibru.
Gdy bowiem zajmujemy się kodowaniem czysto hobbystycznie, automatycznie wydaje się nam ono o wiele rzędów wielkości atrakcyjniejsze niż to, nad czym musimy się skupiać przy okazji edukacji szkolnej. Działa tu pewnie prawo człowieka jako istoty przekornej, lubiącej podążać własnymi ścieżkami. Jednocześnie jednak tęskni się do wyidealizowanego czasu przyszłego, kiedy w końcu swoje zainteresowania będzie można realizować także w trakcie nauki i pracy.
Najwyraźniej wspominane prawo jest tak silne, że gdy ów czas w końcu przychodzi, zaczynamy lepiej wspominać wcześniejszy okres. Nie oznacza to oczywiście, że wolałbym dalej “przymusowo” uczyć się czegoś zupełnie niezwiązanego z szeroko pojętą informatyką; wręcz przeciwnie, nie zamieniłbym aktualnego kierunku studiów na żaden inny :) Problem w tym, że gdy ktoś zaczyna nas programowania uczyć i egzekwować wyniki, traci ono część swojej ‘magii’; podejrzewam, że dotyczy to chyba każdej dziedziny. Ponadto nie da się ukryć, że spełnianie stawianych wymagań oznacza konieczność napisania sporych ilości kodu i ostatecznie na własne kodowanie pozostaje mniej czasu i ochoty.

Wnioski są dwa. Pierwszy to – że użyję wychodzącego już na szczęście z mody sformułowania – oczywista oczywistość: każdy etap nauki czy pracy ma swoje dobre i złe strony, i do każdego trzeba się odpowiednio dostosować. Drugi zaś jest taki, że zawsze wierzymy, iż w przyszłości będzie lepiej. W tym przypadku faktycznie się to sprawdza, lecz chyba nigdy nie dzieje się to w sposób całkowicie zgodny z naszymi oczekiwaniami.
Wypada tylko się tym pogodzić i dalej robić swoje. Dla mnie też na pewno będzie to lepsze niż pisanie pseudofilozoficznych notek ;)


Author: Xion, posted under Programming, Studies, Thoughts » 8 comments

Rozpoznawanie możliwości procesora

2007-12-08 17:39

Niektóre funkcje dobrze jest pisać w asemblerze. Tak, wiem że dzisiaj – w epoce języków (zbyt) wysokiego poziomu – brzmi to dziwnie, ale to prawda. To najprostszy sposób na poprawienie wydajności często wykonywanych operacji, np. kalkulacji z użyciem wektorów i macierzy.
Rzecz w tym, że korzystając bezpośrednio z zaawansowanych możliwości oferowanych przez współczesne procesory, jednocześnie uzależniamy się od nich. Przykładowo, transformację wektora przez macierz można naturalnie po prostu przetłumaczyć z odpowiedniego wzoru na instrukcje jednostki zmiennoprzecinkowej i uzyskać kod działający na każdym procesorze. Jeżeli jednak użyjemy np. SSE2, możemy uzyskać kilkakrotny wzrost wydajności – lecz wówczas nasza funkcja będzie działała tylko na nowszych procesorach.

Najlepiej byłoby więc mieć kilka wersji takiej funkcji i wybierać odpowiednią dla procesora pracującego na danej maszynie. Jak jednak wykryć, co potrafi dana jednostka? Otóż z pomocą przychodzi nam system operacyjny. W Windows na przykład istnieje funkcja o wiele mówiącej nazwie IsProcessorFeaturePresent, przy pomocy której możemy sprawdzić obecność rozszerzeń MMX, 3DNow!, SSE i SSE2.
Oczywiście, takiego sprawdzenia należy dokonać raz na początku działania programu. Jeśli jednak po prostu zapiszemy jego rezultat w formie globalnych flag boolowskich, to ich odczytywanie np. przy każdym dodawaniu wektorów będzie nie tylko kłopotliwe, ale i nieefektywne.

Lepszym rozwiązaniem jest stworzenie odpowiedniej liczby globalnych wskaźników na funkcje, inicjowanych w czasie uruchamiania programu; tak jak poniżej:

  1. typedef VEC3 (*Vec3Func)(const VEC3*, const VEC3*);
  2. Vec3Func Vec3_Add, Vec3_Sub, Vec3_Cross;
  3.  
  4. // sprawdzenie obecności rozszerzenia SSE2
  5. if (IsProcessorFeaturePresent(PF_XMMI64_INSTRUCTIONS_AVAILABLE))
  6. {
  7.    Vec3_Add = Vec3_Add_SSE2;
  8.    Vec3_Sub = Vec3_Sub_SSE2;
  9.    Vec3_Cross = Vec3_Cross_SSE2;
  10. }
  11. else
  12. {
  13.    // w przypadku jego braku, używamy funkcji zakodowanych
  14.    // na zwykłej jednostce zmiennoprzecinkowej
  15.    Vec3_Add = Vec3_Add_FPU;
  16.    Vec3_Sub = Vec3_Sub_FPU;
  17.    Vec3_Cross = Vec3_Cross_FPU;
  18. }

Dzięki temu zarówno w kodzie asemblerowym poszczególnych wersji (którego litościwie nie pokażę ;D), jak i wywołaniach, nie widać żadnego śladu po ‘magii’ wyboru funkcji dostosowanej do procesora. Narzut to rozwiązanie to naturalnie jedna dereferencja wskaźnika więcej; sprawdzanie flag (porównaniami i skokami) trwałoby znacznie dłużej.

Tags: , ,
Author: Xion, posted under Programming » 5 comments

Synchronizacja wątków w Javie

2007-12-07 23:55

Deweloperzy programujący wielowątkowo zapewne znają klasyczne typy wykorzystywanych przy okazji obiektów. Są to na przykład semafory, sekcje krytyczne (zwane też semaforami binarnymi) czy zdarzenia (events). Wszystkie one służą oczywiście do synchronizacji wątków tak, aby wykluczyć jednoczesny, wykluczający się dostęp do jednego zasobu.

Tego typu obiekty są wykorzystywane jednak głównie wtedy, kiedy mechanizm wątków jest zrealizowany w sposób specyficzny dla systemu operacyjnego – jak choćby poprzez API z Windows lub bibliotekę pthreads z Linuxa. Jeśli jednak mamy szczęście pracować z językiem, którego wielowątkowość jest częścią, wówczas korzysta się zwykle z nieco innych technik.
Taka sytuacja jest na przykład w Javie. Tam każdy obiekt (czyli instancja klasy dziedziczącej z java.lang.Object) może być użyty jako obiekt synchronizujący. Z grubsza działa to w ten sposób, że gdy jeden z wątków zadeklaruje wykorzystanie danego obiektu – przy pomocy słowa kluczowego synchronized – pozostałe nie mogą zrobić tego samego. Taka synchronizacja może odbywać się na wiele (składniowych) sposobów, jak choćby zadeklarowanie całej metody jak synchronizowanej:

  1. class SynchronizedCounter
  2. {
  3.    private int value = 0;
  4.    public synchronized int increment() { return ++value; }
  5. }

W tym prościutkim przykładzie mamy zagwarantowane, że żaden postronny wątek nie wtrąci się w operację inkrementacji ze zwróceniem wartości (która nie jest atomowa) i stan licznika będzie zawsze poprawny.
Tak więc mamy semafory tudzież sekcje krytyczne. A co np. ze zdarzeniami (sygnałami)? Otóż każdy obiekt posiada metody wait i notify, umożliwiające czekanie na powiadomienie z innego wątku i oczywiście wysłanie takiego powiadomienia. Całkiem skuteczne i dosyć proste; naturalnie na tyle, na ile proste może być programowanie wielowątkowe :)

Ale czy oryginalne? Otóż dziwnym trafem na platformie .NET cała sprawa wygląda niemal dokładnie tak samo :) Odwzorowania przytoczonych elementów Javy w C# to odpowiednio: lock (z dokładnością do kilku niuansów), Monitor.Wait i Monitor.Pulse. Sam sposób tworzenia wątków jest zresztą też bardzo bardzo podobny.
Wszelka zbieżność przypadkowa? Zdecydowanie nie. Lecz dobre rozwiązania warto jest przecież rozpowszechniać :]

Tags: , ,
Author: Xion, posted under Programming » 2 comments

Jak ASP.NET naprawia HTTP

2007-12-06 12:42

Protokół HTTP i World Wide Web powstały już bardzo dawno temu, chociaż od tamtego czasu doczekały się całego mnóstwa usprawnień. Pewnie największym była możliwość generowania stron HTML dynamicznie przy użyciu skryptów CGI, PHP, i tak dalej. Dzięki temu strony umieją już całkiem sporo i nie są statycznymi dokumentami. Ich tworzenie zaczęło też bardziej przypominać pisanie normalnych aplikacji.

Pewne ograniczenia HTTP ciężko jest jednak przezwyciężyć. Największym jest chyba to, że serwery każde zgłoszenie (request), które do nich napływa, traktują zupełnie odrębnie i w oderwaniu od wszystkich innych. Działają więc na zasadzie pytanie-odpowiedź. Gdyby poszukać odpowiednika tego modelu w zwyczajnych programach, to byłyby nim narzędzia konsolowe obsługiwane w całości przełącznikami wiersza poleceń. W dobie interfejsów graficznych mieniących się wszystkimi kolorami palety RGB nie wyglądają one zbyt imponująco…
Dlatego też wszelkie platformy służące uruchamianiu aplikacji webowych starają się tę niedogodność jakoś obejść i umożliwić powiązanie poszczególnych żądań oraz przechowywanie danych pomiędzy nimi. Standardem jest mechanizm sesji, ale np. w PHP nigdy nie udało mi się do końca go zrozumieć :)

Logo ASP.NETOstatnio jednak z konieczności (nietrudno się domyślić, jakiej :)) zajmuję się ASP.NET i muszę przyznać, że tam cała sprawa została potraktowana nieporównywalnie lepiej. Przede wszystkim serwis jest tam rzeczywiście traktowany jako aplikacja, której instancja uruchamia się w momencie pierwszego requestu z danego hosta. Poszczególne strony przypominają też raczej okienka zwykłych programów, na których zamieszcza się kontrolki specyficzne dla ASP (przerabiane na wyjściu na HTML). I to ze wszystkimi konsekwencjami: one rzeczywiście istnieją na serwerze. To sprawia, że możemy np. pobrać dane formularza wypełnionego przed chwilą na poprzedniej stronie, po prostu czytając zawartość jej kontrolek.
Cała ta trwałość nie jest oczywiście aż tak daleko posunięta jak w tradycyjnych programach. Zdarza się (i to dość często), że musimy zwracać uwagę na to, kiedy strona może być przeładowywana. Ale i tak stanowi to spory postęp na przykład w stosunku do wspomnianego mechanizmu sesji (który naturalnie też jest dostępny).

Naturalnie wszystko to są jednak tylko obejścia do starego protokołu, który pierwotnie nie został zaprojektowany do tak złożonych zadań, do których się go obecnie wykorzystuje.

Tags: ,
Author: Xion, posted under Internet, Programming » 1 comment

Kartka papieru

2007-12-03 19:02

Teoretycznie wszystko zakodować można “na żywioł”, po prostu siadając do ulubionego środowiska programistycznego, pisząc i kompilując. Proste dzieła może i można w ten sposób stworzyć, ale do czegoś bardziej skomplikowanego zawsze pomaga chociaż pobieżny projekt.
W jakiej formie? To już nie jest sprawą taką prostą. Obecnie mamy sporo różnych narzędzi, umożliwiających formowanie naszych pomysłów w miejscu ich docelowego egzystencji – czyli programów komputerowych. I nie chodzi mi tu o aplikacje wysoce wyspecjalizowane, bo do zaprojektowania programu może się okazać przydatny edytor tekstu typu Notatnika. Każde z tych narzędzi będzie użyteczne dla określonego celu i każde ma też swoje ograniczenia.

Kilka kartek papieruAle mamy również inny sposób. Możemy odejść (albo przynajmniej się odwrócić) od komputera, wziąć kartkę, długopis i… do dzieła. Być może wkrótce posługiwanie się tak “archaicznymi” sprzętami będzie nieco staroświeckie, ale pewnie jeszcze długo będzie miało niezaprzeczalne zalety:

  • Nieograniczone możliwości formatowania :) Tekst w formie zawijasa w środku obrazka? Proszę bardzo. Diagram ze strzałkami biegnącymi między komórkami tabelki? Nie ma problemu. Treści możemy układać tak, jak chcemy i jak tylko pozwala nam na to zręczność palców – zapewne poważnie nadwerężona ich regularnym stukaniem w klawisze ;]
  • Swoboda formy. Programy komputerowe potrafią edytować tylko dokumenty w określonych formatach, jak teksty, arkusze kalkulacyjne, diagramy UML, itd. Tutaj zaś nie mamy takich ograniczeń i możemy tworzyć schematy, zestawienia, wykresy i inne pomocnicze rysunki, które nie śniły się teoretykom inżynierii oprogramowania :)
  • Dostępność. Wprawdzie teraz komputer można zabrać ze sobą niemal wszędzie, ale i tak w tej kategorii trudno o sensowne porównywanie. Papier jest jednak zdecydowanie poręczniejszy.

Każdy jednak wie rzecz jasna, jakie są jego wady. Niektóre alternatywy – jak na przykład skrobanie po tabletowym komputerze – niwelują część z nich, zbijając przy okazji niektóre zalety. Największym mankamentem jest chyba jednak to, że rysunkowe bazgroły na papierze zawsze już pozostaną bazgrołami: nie da się ich przetworzyć na użyteczną formę schematów czy tabel zrozumiałą dla komputera, a i rozpoznawanie czystego tekstu też nie jest jeszcze doskonałe.
Wygląda więc na to, że poczciwa kartka papieru jeszcze długo będzie podstawowym środkiem tworzenia przynajmniej wstępnych – nomen omen – szkiców, zostawiając bardziej szczegółowe projektowanie innym narzędziom. Warto zatem wciąż kultywować starożytną umiejętność ręcznego pisania i rysowania :)

Tags: , ,
Author: Xion, posted under Programming » 10 comments

Funkcje debugujące

2007-12-01 13:29

Kiedy już przekonamy się o korzyściach płynących z regularnego stosowania debuggera (a każdy początkujący programista powinien przekonać się o nich jak najszybciej), być może zaczniemy się zastanawiać, jakaż to “magia” sprawia, że cały ten proces w ogóle jest możliwy. W jaki sposób program śledzący potrafi dostać się do wnętrza naszej aplikacji i wylistować zawartość wszystkich zmiennych, nie mówiąc już o możliwości ustawiania punktów przerwań (breakpoints) czy pracy krokowej. Czy debuggery korzystają z jakichś nieudokumentowanych “wytrychów” do samego jądra systemu operacyjnego?…

Otóż niekoniecznie. Na przykład Windows API udostępnia zwyczajne funkcje systemowe, za pomocą których jeden proces może śledzić zachowanie innego. Obejmuje to przyłączenie się do innego procesu w charakterze debuggera, obsługę przeróżnych interesujących zdarzeń (jak np. załadowanie biblioteki DLL lub stworzenie nowego wątku przez proces, który śledzimy), a także odczytywanie zawartości pamięci procesu (jeśli mamy do tego uprawnienia) lub kontekstu jego wątku.
Naturalnie mało kto będzie pisał swój własny debugger, więc wspomniane funkcje są nieszczególnie przydatne dla większości programistów. Jednak istnieje też kilka takich, które można użytecznie stosować po drugiej stronie – w aplikacji, która jest debugowana:

  • OutputDebugString to prawdopodobnie ta najbardziej znana. Służy ona do wysyłania komunikatów do debuggera, które zwykle są wyświetlane w specjalnym okienku naszego IDE. Zazwyczaj jest to bardziej poręczne rozwiązanie niż chociażby pokazywanie okienek komunikatów, które trzeba przecież potwierdzać kliknięciami w OK. Funkcję tę można aczkolwiek zastąpić wypisywaniem na standardowe wyjście diagnostyczne (stderr, reprezentowane w iostream przez obiekty cerr lub wcerr).
  • DebugBreak działa z kolei jak punkt przerwania. Zasadniczo funkcja ta wywołuje odpowiedni wyjątek systemowy (nie mający za wiele wspólnego z wyjątkami języka C++), który każdy porządny debugger złapie. W środowisku programistycznym rezultatem będzie zawieszenie działania programu i ustawienie się z widokiem kodu źródłowego na miejsce wywołania funkcji, co oczywiście znakomicie ułatwia odnalezienie przyczyny błędu. Jeżeli jednak nikt nie śledzi naszej aplikacji, wówczas rzucony wyjątek spowoduje jej awaryjne zakończenie. Generalnie podobny efekt co DebugBreak powinno dać wywołanie przerwania numer 3 (czyli asm { int 3 }).
  • IsDebuggerPresent pozwala z kolei określić, czy aplikacja jest aktualnie debugowana przez jakiś inny proces. Dzięki temu możemy na przykład przekazywać do komunikatów diagnostycznych większą liczbę informacji, wiedząc, że ktoś “po drugiej stronie” faktycznie je odczytuje :)

Wprawdzie samo korzystanie z tych funkcji nie zastąpi dobrego systemu kontroli błędów i ich raportowania (np. zapisywania w dzienniku), lecz z pewnością może pomóc. Bardzo dobrze prezentuje się na przykład okienko z obszernym komunikatem o błędzie i trzema możliwościami decyzji: kontynuacji programu, przejścia do trybu debugowania albo awaryjnego zakończenia aplikacji. Przy takim rozwiązaniu aż chce się popełniać błędy ;)

Tags: , ,
Author: Xion, posted under Programming » Comments Off on Funkcje debugujące

Typy rozróżnialne

2007-11-28 10:11

W większości języków możemy zdefiniować nową nazwę dla istniejącego typu danych; nazywa się ją zwykle aliasem. I tak na przykład w C/C++ jest to możliwe za pomocą słowa kluczowego typedef. Analogicznie w Delphi mamy od tego słowo kluczowe type:
[delphi]type TMyInt = Integer;[/delphi]
Tak powstały typ TMyInt jest faktycznie tylko aliasem. Zmienne należące do tego typu są bowiem całkowicie kompatybilne ze zmiennymi zwykłego typu całkowitego Integer. W razie potrzeby konwersja między nimi może bez problemu zachodzić w obie strony.

Jeżeli jednak użylibyśmy deklaracji w formie:
[delphi]type TMyInt = type Integer;[/delphi]
wówczas TMyInt byłby już zupełnie innym typem niż Integer. Mimo że oba mogłyby przechowywać wartości tego samego rodzaju (liczby całkowite) i z tego samego zakresu, konwersje między nimi wymagałyby rzutowania.
Można by sądzić, że tworzenie takich typów rozróżnialnych “na siłę” jest bezcelowe. Zauważmy jednak, że typy wyliczeniowe (deklarowane przez enum) są przecież także w gruncie rzeczy liczbami z określonego zbioru. Najczęściej jednak nie chcemy, aby możliwa była niejawna konwersja między nimi a normalnymi typami liczbami. Wszystko dlatego, że w enumach liczby nie pełnią funkcji liczb, tylko identyfikatorów pewnych stanów.

Podobnie tutaj w przypadku TMyInt nie chodzi nam zapewne o liczby w sensie ich wartości, tylko o coś w rodzaju uchwytów – identyfikatorów obiektow. Kopalnią typów przeznaczonych do takiego właśnie celu jest oczywiście Windows API, zawierające tak znane i lubiane typy jak HWND, HINSTANCE, HDC, itd. Wszystkie one są w gruncie rzeczy liczbami 32-bitowymi, a mimo to nie są ze sobą kompatybilne. Gdyby API to było obiektowe, obiekty reprezentowane obecnie przez te uchwyty należałyby do różnych klas.
Efekt niezgodności uchwytów osiągnięto, deklarując ich typy nie jako aliasy na DWORD:

  1. typedef DWORD HWND;  // wszystkie tak określone typy będą ze sobą zgodne

lecz jako niezwiązane ze sobą typy wskaźnikowe:

  1. struct __HWND;
  2. typedef __HWND* HWND;

Można to uznać za dość pokrętną sztuczkę, ale na pewno jest ona lepsza niż tworzenie typu wyliczeniowego zawierającego nieco ponad 4 miliardy (232) nazwanych stałych ;]

Tags: , , ,
Author: Xion, posted under Programming » 2 comments
 


© 2021 Karol Kuczmarski "Xion". Layout by Urszulka. Powered by WordPress with QuickLaTeX.com.