Posts tagged ‘POSIX’

Niekoniecznie długie nazwy

2011-10-09 23:22

Porzekadło głosi, że w informatyce są tylko dwa trudne problemy: nazewnictwo, mechanizmy cache‘owania i pomyłki o jedynkę. Jeśli chodzi o jeden dwa ostatnie, to może przyjrzymy się im przy innej okazji… Dzisiaj chciałbym za to zająć się pierwszym z nich: dobieraniem odpowiednich nazw dla konstrukcji programistycznych, takich jak funkcje czy klasy.

Nie każda nazwa jest właściwa. O tej trywialnej prawdzie każdy pewnie przekonał się już dawno, zwłaszcza jeśli przechodził przez fazę zmiennych a, b, c lub funkcji fun1 i fun2. Zdaje się zresztą, że kiedyś była to powszechna przypadłość, co widać zwłaszcza w przypadku starych API *niksowych. Najwyraźniej jednak poszliśmy kolektywnie po rozum do głowy i dziś już nikt nie nazwie funkcji wait3 czy wait4.

Nietrudno jest oczywiście wskazać podstawowy problem tego rodzaju nazw. Jakkolwiek jest on ściśle związany z długością, nie uzasadnia to automatycznie stwierdzenia, że wszystkie krótkie nazwy są złe. Żeby nie odchodzić daleko, wystarczy tylko spojrzeć na POSIX-owe, uniwersalne funkcje read i write. Ich nazwom nie brakuje dokładnie niczego; przeciwnie, próba dodania czegoś więcej wprowadzałaby tylko zamieszanie. readFromFileDescriptor może i wskazywałaby wyraźnie na źródło danych, ale czy zupełnie poprawne użycie takiej funkcji na uchwycie sieciowego gniazda (socket) nie byłoby mylące? O jakim pliku wtedy mówimy?
Naturalnie, w *niksach “wszystko jest plikiem” i należy o tym wiedzieć. Lecz skoro tak jest, to co zyskujemy przez dłuższą nazwę funkcji read? Niby z czego innego niż z pliku mielibyśmy czytać?…

Dywagując na ten temat muszę koniecznie zaznaczyć, że nazwy zaśmiecone oczywistymi informacjami nie są wcale hipotetycznym problemem. Moim ulubionym przykładem – ze względu na swoją groteskową wręcz ekstremalność – jest poniższe wywołanie:

  1. NSString* someString = @"Ala ma kota";
  2. NSString* otherString = [someString
  3.                          stringByReplacingOccurrencesOfString:@"kota"
  4.                          withString:@"psa"];

Ja wcale nie żartuję – tak w Objective-C (OSX/iOS) wygląda zastępowanie jednego ciągu w tekście innym. Zawsze chętnie wysłucham argumentów przekonujących o ekspresywności i opisowości podobnych nazw, ale nigdy nie uwierzę, że koderzy je rzeczywiście czytają. Zgaduję, że ich percepcja u statystycznego programisty polega na dostrzeżeniu “Repl” lewym okiem i “ccurr” prawym – albo coś w tym rodzaju. Jeśli mam rację, to nazwa ta jest znacząca w zaledwie 22 procentach; trudno to uznać za dobry stosunek sygnału do szumu.

Czy można było zrobić to lepiej? Zapewne – weźmy chociażby Javę:

  1. String someString = "Ala ma kota";
  2. String otherString = someString.replaceAll("kota", "psa");

Wygląda to całkiem dobrze. Okazuje się, że można użyć trzy razy krótszej nazwy i osiągnąć niemalże ten sam efekt, posługując się po prostu samą składnią języka (kolejnością parametrów i wartością zwracaną) zamiast długich tekstowych opisów.

Rozwlekłe nazwy – nawet jeśli camelCase czyni je znośnymi – nie zawsze są więc dobrą odpowiedzią. Czasami mogą one być równie nadmiarowe co niepotrzebne komentarze.

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

Dokładny czas na wielu platformach

2010-06-26 23:54

Gry i inne podobne aplikacje wymagają precyzyjnego pomiaru czasu, aby realizować obliczenia związane z fizyką, animacją, itp. Jak niemal wszystko, API potrzebne do tego celu jest różne w zależności od platformy, czyli (głównie) systemu operacyjnego. Dzisiaj chciałbym pokazać, jak wygląda to na dwóch najpopularniejszych systemach: Windows i POSIX (a więc między innymi na wszelkiego typu Linuksach).

Interfejs programistyczny systemu spod znaku okienek udostępnia dwie funkcje od dokładnego mierzenia czasu. Są to QueryPerformanceFrequency i QueryPerformanceCounter. Tę pierwszą wywołuje się tylko raz, a jej wynikiem jest częstotliwość wewnętrznego systemowego zegara, który służy do precyzyjnego pomiaru upływającego czasu. Wyrażana jest ona w liczbie “tyknięć” na sekundę i na dzisiejszym sprzęcie może być bardzo duża, bo liczona w (kilku(nastu/dziesięciu)) milionach.
Druga funkcja jest używana nieporównywalnie częściej, gdyż to ona zwraca aktualną wartość zegara, czyli liczbę jego “tyknięć”. Stąd wynika, że obliczenie tej wartości w sekundach wymaga podzielenia rezultatu QPC przez uzyskaną wcześniej częstotliwość:

  1. LARGE_INTEGER freq, counter;
  2. QueryPerformanceFrequency (&freq);
  3. // ...
  4. QueryPerformanceCounter (&counter);
  5. double secs = counter.QuadPart / (double)freq.QuadPart;

Używana tu unia LARGE_INTERGER to nic innego, jak zwykła liczba 64-bitowa.

W systemach POSIX-owych rzecz jest odrobinę prostsza, jako że tutaj dokładność zegara jest ściśle ustalona. Funkcja gettimeofday (z nagłówka sys/time.h) zwraca rezultat z precyzją mikrosekundową w postaci struktury timeval:

  1. struct timeval tv;
  2. gettimeofday (&tv, 0);
  3. double sec = tv.tv_sec + tv.tv_usec / 1000000.0;

Część odpowiadającą niepełnym sekundom (pole tv_usec) trzeba więc podzielić przez milion.

Chcąc mieć bardziej uniwersalne rozwiązanie, możemy napisać klasę opakowującą zegar z implementacją kontrolowaną docelową platformą, na którą kompilujemy:

  1. class Clock
  2. {
  3.     #ifdef _WINDOWS
  4.         LARGE_INTEGER freq;
  5.         public: Clock() { QueryPerformanceFrequence (&freq); }
  6.     #endif
  7.  
  8.     double GetTicks() const
  9.     {
  10.         #ifdef _WINDOWS
  11.             LARGE_INTEGER counter;
  12.             QueryPerformanceCounter (&counter);
  13.             return counter.QuadPart / (double)freq.QuadPart;
  14.         #else
  15.             struct timeval tv; gettimeofday (&tv, 0);
  16.             return tv.tv_sec + tv.tv_usec / 1000000.0;
  17.         #endif
  18.     }
  19. };

Można by na koniec zapytać jeszcze, co tak naprawdę znaczy zwracana wartość zegara. Kiedy wynosi ona zero?… Otóż wbrew pozorom odpowiedź na to pytanie nie jest istotna, bo w praktyce interesuje nas tylko interwał czasowy, tj. różnica między dwoma wskazaniami timera. To na jej podstawie liczymy zmianę w prędkościach obiektów, klatkach animacji czy w końcu niezbędnie konieczną do wyświetlenia wartość FPS :)

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

POSIX-owanie

2008-02-23 20:34

POSIX (Portable Operating System Interface) to taki śmieszny “standard dla systemów operacyjnych”, opracowany przez znane skądinąd konsorcjum IEEE. Celem jego stworzenia było zapewnienie jak największej zgodności w działaniu (lub niedziałaniu, rzecz jasna) dla aplikacji pracujących pod kontrolą różnych wariantów Uniksa. W tym celu określone jest pokaźnych rozmiarów API, które zajmować ma się takimi rzeczami jak procesy, wątki, sygnały, I/O, gniazda sieciowe, i tak dalej.
To, co POSIX w tym zakresie teoretycznie oferuje, jest w gruncie rzeczy całkiem zadowalające. Standard nie zabrania zresztą, by implementujące go systemy operacyjne dodawały do tego jakąś własną funkcjonalność.

Linuksowa maskotkaFreeBSD-owa maskotkaDlaczego więc zgodność poszczególnych systemów z POSIX-em jest w przybliżeniu odwrotnie proporcjonalna do ich popularności? :-) Wbrew pozorom te co bardziej znane, jak różnego rodzaju BSD i dystrybucje Linuksa, nie są pod tym względem doskonałe. Jedynie znacznie bardziej specyficzne Solarisy, QNX-y oraz, co ciekawe, Mac OS X spełniają standard POSIX-a w pełni.
A co z naszymi ulubionymi okienkami? W ich przypadku jesteśmy oczywiście bardzo, bardzo daleko… ale tylko do czasu. Windows można bowiem dość prosto doprowadzić do pełnej zgodności przy pomocy takich pakietów jak Microsoft Windows Services for UNIX czy Cygwin. Może to być dobra pomoc dla tych, którzy chcą pisać przenośne aplikacje bez opuszczania przyjaznego środowiska okienek.

Tags: , ,
Author: Xion, posted under Computer Science & IT » 1 comment
 


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