Difference between revisions of "Tworzenie klas pochodnych: port programu SciMark2"

From MorphOS Library

(Code Inspection: Translation in progress.)
m (Another link redirected to Polish version.)
 
(13 intermediate revisions by the same user not shown)
Line 19: Line 19:
  
 
To doświadczenie pokazuje jak bardzo ważne jest korzystanie z optymalizacji kodu, zwłaszcza w programach wykonujących dużo obliczeń. Zoptymalizowan kod jest '''ponad 4 razy szybszy'''!
 
To doświadczenie pokazuje jak bardzo ważne jest korzystanie z optymalizacji kodu, zwłaszcza w programach wykonujących dużo obliczeń. Zoptymalizowan kod jest '''ponad 4 razy szybszy'''!
 +
  
 
==Przegląd kodu==
 
==Przegląd kodu==
Oryginalny kod źródłowy jest logicznie podzielony na moduły. Pięć plików: ''FFT.c'', ''LU.c'', ''MonteCarlo.c'', ''SOR.c'' i ''SparseCompRow.c'' zawiera pojedyncze testy obliczeniowe. Pliki ''array.c'' i ''Random.c'' zawierają funkcje pomocnicze używane w testach. W plik ''Stopwatch.c" z kolei znajdują się procedury pomiaru czasu. Plik ''scimark2.c'' zawiera funkcję ''main()'' i tekstowy interfejs programu.
+
Oryginalny kod źródłowy jest logicznie podzielony na moduły. Pięć plików: ''FFT.c'', ''LU.c'', ''MonteCarlo.c'', ''SOR.c'' i ''SparseCompRow.c'' zawiera pojedyncze testy obliczeniowe. Pliki ''array.c'' i ''Random.c'' zawierają funkcje pomocnicze używane w testach. W plik ''Stopwatch.c'' z kolei znajdują się procedury pomiaru czasu. Plik ''scimark2.c'' zawiera funkcję ''main()'' i tekstowy interfejs programu.
  
Planowany interfejs graficzny powinien pozwalać użytkownikowi na uruchomienie każdego testu oddzielnie, albo wszystkich po kolei. Program posiada też opcję ''-large'', która zwiększa rozmiary danych dla poszczególnych testów tak, że nie mieszczą się w pamięci podręcznej procesora. Dobrą zasadą przy portowaniu programów jest ograniczenie modyfikacji oryginalnych plików do minimum.  
+
Planowany interfejs graficzny powinien pozwalać użytkownikowi na uruchomienie każdego testu oddzielnie, albo wszystkich po kolei. Program posiada też opcję ''-large'', która zwiększa rozmiary danych dla poszczególnych testów tak, że nie mieszczą się w pamięci podręcznej procesora. Dobrą zasadą przy portowaniu programów jest ograniczenie modyfikacji oryginalnych plików do minimum. Dzięki temu znacznie ułatwiona jest aktualizacja portu, kiedy ukaże się nowa wersja oryginału. W przypadku SciMarka wystarczy zastąpić tylko jeden plik, mianowicie ''scimark2.c''. W zaawansowanym porcie możnaby też zastąpić ''Stopwatch.c'' kodem bezpośrednio używającym ''timer.device'', co zwiększyłoby dokładność pomiaru czasu. To zagadnienie jednakże nie mieści się w zakresie tematycznym artykułu.
+
 
The planned MUI interface should allow the user to run every benchmark separately or run all of them. There is also a ''-large'' option, which increases memory sizes for calculated problems, so they do not fit into the processor cache. A general rule of porting is that as few files as possible should be modified. The rule makes it easier to upgrade the port when a new version of the original program is released. In the case of SciMark, only one file, ''scimark2.c'' has to be replaced. An advanced port may also replace ''Stopwatch.c'' with code using ''timer.device'' directly for improved accuracy of time measurements however, this is out of scope of this tutorial.
+
Bliższe przyjrzenie się plikowi ''scimark2.c'' ujawnia, że zawiera on obiekt ''Random'' (jest to struktura zdefiniowana w pliku ''Random.h''), który jest używany przez wszystkie 5 testów. W oryginalnym kodzie jest on tworzony funkcją ''new_Random_seed()'' na początku programu i usuwany funkcją ''delete_Random()'' na jego końcu. Najlepszym miejscem na ten obiekt w zMUI-fikowanej wersji są dane obiektu aplikacji. Wtedy można go zainicjalizować w konstruktorze aplikacji (metoda ''OM_NEW()'') a usunąć w destruktorze (metoda ''OM_DISPOSE()''). Te dwie metody należy więc w klasie pochodnej od ''Application'' przeciążyć.
  
A closer look at "scimark2.c" reveals that there is a ''Random'' object (a structure defined in "Random.h"), which is required for all the benchmarks. In the original code it is created with ''new_Random_seed()'' at the program start and disposed with ''delete_Random()'' at exit. The best place for it in the MUI-ified version is the instance data area of the subclassed ''Application'' class. Then it can be created in ''OM_NEW()'' of the class and deleted in ''OM_DISPOSE()''. These two methods should then be overridden.
 
  
==GUI Design==
+
==Projekt intrerfejsu graficznego==
[[File:scimark_gui.png|left]] Of course there is no one and only proper GUI design for SciMark. A simple design, using a limited set of MUI classes is shown on the left. There are five buttons for individual benchmarks and one for running all of them. All these buttons are instances of the ''Text'' class. On the right there are gadgets for displaying benchmark results. These gadgets also belong to the ''Text'' class, just having different attributes. The "Large Data" button, of the ''Text'' class of course, is a toggle button. Surprisingly the status bar (displaying "Ready.") is not an instance of the ''Text'' class, but instead the ''Gauge'' class. Then it will be able to display a progress bar when running all five tests. Spacing horizontal bars above the "All Benchmarks" button are instances of the ''Rectangle'' class. There are also three invisible objects of the ''Group'' class. The first is a vertical, main group, being the root object of the window. It contains two sub-groups. The upper one is the table group with two columns and contains all the benchmark buttons and result display gadgets. The lower group contains the "Large Data" toggle button and the status bar.
+
[[File:scimark_gui.png|left]] Nie ma oczywiście "jedynie słusznego" projektu GUI dla SciMarka. Prosty projekt, używający ograniczonej ilości klas MUI, pokazany jest na ilustracji po lewej. Mamy tam 5 przycisków do uruchamiania poszczególnych testów i szósty do automatycznego wykonania wszystkich po kolei. Wszystkie te przyciski są obiektami klasy ''Text''. Po prawej znajdują się gadżety wyświetlające wyniki testów. One również są klasy ''Text'', mają jedynie inny zestaw atrybutów. Przycisk "Large Data", oczywiście również klasy ''Text'', jest przyciskiem dwustanowym. O dziwo pasek stanu na dole nie jest klasy ''Text'' ale klasy ''Gauge'', dzięki czemu będzie można w nim wyświetlać postęp wykonywania testów w czasie wykonywania całego zestawu. Poziome paski oddzielające pojedyncze testy od testu zbiorczego są instancjami klasy ''Rectangle''. Do tego mamy trzy niewidoczne obiekty klasy ''Group''. Pierwszy to grupa pionowa będąca głównym obiektem okna. Ma ona dwie podgrupy. Górna to grupa tablicowa o dwóch kolumnach, w której znajdują się przyciski testów i pola wyników. Grupa dolna, będąca grupą poziomą, zawiera przycisk "Large Data" i pasek stanu.
  
The simplest way to start with GUI design is just to copy the "Hello World" [["Hello world!" in MUI|example]]. Then MUI objects may be added to the ''build_gui()'' function. The [http://krashan.ppa.pl/morphzone_tutorials/scimark2_mui.c modified example] is ready to compile and run. It is not a complete program of course, just a GUI model without any functionality added.
+
Najprościej zacząć tworzenie GUI kopiując po prostu [["Hello world!" w MUI|przykład]] "HelloWorld". Nowe obiekty dodajemy wewnątrz funkcji ''build_gui()''. [http://krashan.ppa.pl/morphzone_tutorials/scimark2_mui.c Zmodyfikowany przykład] jest gotowy do skompilowania i uruchomienia. Oczywiście nie jest to kompletny program, a jedynie model interfejsu graficznego.
  
A quick view into the ''build_gui()'' function reveals that it does not contain all the GUI code. Code for some subobjects is placed in functions called from the main ''MUI_NewObject()''. Splitting the GUI building function into many subfunctions has a few important advantages:
+
W kodzie można zauważyć, że funkcja ''build_gui()'' nie zawiera w sobie całego kodu tworzącego interfejs. Kod dla niektórych obiektów został przeniesiony do oddzielnych funkcji wywoływanych z głównego wywołania funkcji ''MUI_NewObject()''. Podzielenie funkcji tworzącej GUI na fragmenty ma szereg zalet:
* Improved code readability and easier modifications. A single ''MUI_NewObject()'' call gets longer and longer quickly as the project evolves. Editing a large function spanning over a few screens is uncomfortable. Adding and removing GUI objects in such a function becomes a nightmare even with indentation used consequently. On the other hand the function can have 10 or more indentation levels, which makes it hard to read as well.
+
* Większa czytelność kodu i prostsze wprowadzanie zmian. Pojedyncze wywołanie ''MUI_NewObject()'' staje się szybko bardzo długie w miarę rozbudowy interfejsu. Edycja tak dużej funkcji rozciągającej się na kilka ekranów edytora jest niewygodna. Dodawanie i usuwanie obiektów staje się koszmarem, nawet jeżeli wcięcia w kodzie są stosowane konsewentnie. Z drugiej strony taka funkcja może mieć 10 i więcej poziomów wcięć, co również czyni ją trudną w czytaniu.
* Code size reduction. Instead of writing very similar code multiple times, for example buttons with different labels, a subroutine may be called with a label as an argument.
+
* Zmniejszenie wielkości kodu. Zamiast powtarzać bardzo podobny kod wiele razy, na przykład przyciski różniące się jedynie etykietami, można wywołać podprogram z tekstem etykiety jako parametrem.  
* Debugging. It happens sometimes that MUI refuses to create the application object because of some buggy tags or values passed to it. If the main ''MUI_NewObject()'' call is split into subfunctions, it is easy to isolate the buggy object by inserting some ''Printf()''-s in subfunctions.
+
* Debugowanie. Bywa, że MUI odmawia stworzenia obiektu aplikacji z powodu jakiegoś błędnego taga albo jego wartości. Jeżeli główne wywołanie ''MUI_NewObject()'', tworzące obiekt aplikacji, jest podzielone na szereg mniejszych funkcji, łatwo wyizolować błąd wstawiając kilka ''Printf()''-ów w tychże funkcjach.
  
  
==Methods and Attributes==
+
==Metody i atrybuty==
The SciMark GUI just designed, defines six actions for the application. There are five actions for running individual benchmarks and the sixth one for running all the tests and calculating the global result. Actions will be directly mapped to ''Application'' subclass methods. There is also one attribute connected with the "Large Data" button, it determines the sizes of problems solved by benchmarks. Methods do not need any parameters, so there is no need to define method messages. An attribute may be applicable at initialisation time (in the object constructor), may be also settable (needs ''OM_SET()'' [[Overriding OM_SET()|method overriding]]) and gettable (needs ''OM_GET()'' [[Overriding OM_GET()|method overriding]]). Our new attribute, named ''APPA_LargeData'' in the code only needs to be settable. In the constructor it can be implicitly set to ''FALSE'', as the button is switched off initially. GET-ability is not needed, because this attribute will be used only inside the ''Application'' subclass.
+
Zaprojektowany właśnie intefrejs graficzny do SciMarka definiuje 6 akcji, jakie można wykonać w tym programie. Mamy pięć akcji polegających na wykonaniu pojedynczego testu, oraz szóstą, która wykonuje wszystkie testy po kolei i oblicza łączny wynik. Akcje te będą bezpośrednio odpowiadały metodom klasy utworzonej z klasy ''Application''. GUI określa nam także jeden atrybut, związany z przyciskiem ''LargeData''. Przypomnę, że określa on rozmiary danych dla testów. Ponieważ metody nie wymagają żadnych parametrów, nie ma potrzeby definiowania ich struktur. Jakikolwiek atrybut może być używany jako wartość początkowa konstruktora, może być ustawialny (wymaga [[Przeciążanie metody OM_SET()|przeciążenia metody OM_SET()]]), może też być odczytywalny (wymaga [[Przeciążanie metody OM_GET()|przeciążenia metody OM_GET()]]). Nasz nowy atrybut, nazwany ''APPA_LargeData'', wymaga jedynie możliwości ustawiania. W konstruktorze możemy go domyślnie ustawić na ''FALSE'', ponieważ na starcie programu przycisk "LargeData" jest wyłączony. Możliwość odczytu wartości atrybutu nie jest potrzebna, bo jest on odczytywany wyłącznie wenątrz klasy aplikacji, można więc bezpośrednio odwołać się do odpowiedniego pola danych obiektu.
  
It is recommended that every subclass in the application is placed in a separate source file. It helps to keep code modularity and also allows for [[Short BOOPSI Overview#Classes|hiding class private data]]. This requires writing a makefile, but one is needed anyway, as the original SciMark code consists of multiple files. Implementing the design directions discussed above a [http://krashan.ppa.pl/morphzone_tutorials/scimark2_application.h class header file] and [http://krashan.ppa.pl/morphzone_tutorials/scimark2_application.c class code] can be written. The class still does nothing, just implements six empty methods and overrides ''[[Overriding OM_SET()|OM_SET()]]'', ''[[Overriding Constructors|OM_NEW()]]'' and ''[[Overriding Destructors|OM_DISPOSE()]]''. In fact it is a boring template example and as such it has been generated with the [http://downloads.morphzone.org/find.php?find=chocolatecastle ChocolateCastle] template generator. Unfortunately ChocolateCastle is still beta, so files had to be tweaked manually after generation.
+
Pisząc program warto umieszczać każdą klasę w oddzielnym pliku. Ułatwia to utrzymanie modułowości kodu, pozwala również na [[Rzut oka na BOOPSI#Klasy|ukrycie danych prywatnych klasy]]. Pociąga to sa sobą konieczność napisania ''makefile'', ale oryginalny kod SciMarka również składa się z kilku pilków, więc tak, czy inaczej, jest to nieuniknione. Stosując się do przedstawionych powyżej wskazówek projektowych można napisać [http://krashan.ppa.pl/morphzone_tutorials/scimark2_application.h plik nagłówkowy] klasy i jej [http://krashan.ppa.pl/morphzone_tutorials/scimark2_application.c kod]. Klasa w tej postaci nadal nic nie robi, po prostu zawiera puste metody akcji programu i przeciąża metody ''[[Przeciążanie metody OM_SET()|OM_SET()]]'', ''[[Przeciążanie konstruktorów|OM_NEW()]]'' i ''[[Przeciążanie destruktorów|OM_DISPOSE()]]''. Pisanie takiego szkieletu klasy jest nudną mechaniczną pracą, więc można ją zwalić na komputer. W istocie, przykładowy kod został wygenerowany narzędziem  [http://downloads.morphzone.org/find.php?find=chocolatecastle ChocolateCastle]. Niestety ChocolateCastle jest wciąż w wersji beta, więc kilka drobnych poprawek trzeba było wprowadzić ręcznie.
  
The next step in the application design is to connect methods and attributes with GUI elements using [[Event Driven Programming, Notifications#Notifications in MUI|notifications]]. Notifications must of course be created after both source and target object are created. In the SciMark code they are just set up after executing ''build_gui()''. All the six action buttons have very similar notifications, so only one is shown here:
+
Następnym krokiem w projektowaniu programu jest połączenie metod i atrybutów z elementami interfejsu graficznego używając [[Programowanie sterowane zdarzeniami, notyfikacje#Notyfikacje w MUI|notyfikacji]]. Notyfikacje można ustawiać dopiero gdy zarówno źródło jak i cel notyfikacji są istniejącymi obiektami. W kodzie SciMarka są one ustawiane po wykonaniu funkcji ''build_gui()''. Wszystkie przyciski testów mają bardzo podobne notyfikacje, dlatego tutaj pokazana jest tylko jedna:
  
 
  DoMethod(findobj(OBJ_BUTTON_FFT, App), MUIM_Notify, MUIA_Pressed, FALSE,
 
  DoMethod(findobj(OBJ_BUTTON_FFT, App), MUIM_Notify, MUIA_Pressed, FALSE,
 
   App, 1, APPM_FastFourierTransform);
 
   App, 1, APPM_FastFourierTransform);
  
The "Large Data" button has a notification setting the corresponding attribute:
+
Przycisk "Large Data" ma notyfikację ustawiającą odpowiadający mu atrybut:
  
 
  DoMethod(findobj(OBJ_BUTTON_LDATA, App), MUIM_Notify, MUIA_Selected, MUIV_EveryTime,
 
  DoMethod(findobj(OBJ_BUTTON_LDATA, App), MUIM_Notify, MUIA_Selected, MUIV_EveryTime,
 
   App, 3, MUIM_Set, APPA_LargeData, MUIV_TriggerValue);
 
   App, 3, MUIM_Set, APPA_LargeData, MUIV_TriggerValue);
  
Notified objects are accessed with [[Locating Objects in the Object Tree|dynamic search]] (the ''findobj()'' macro), which saves the programmer from defining global variables for all of them.
+
Obiekty źródłowe notyfikacji są [[Wyszukiwanie obiektów w drzewie|dynamicznie wyszukiwane]] w drzewie obiektów aplikacji (makro ''findobj()''). Zaoszczędza to potrzeby definiowania dla nich globalnych wskaźników.
  
  
==Implementing Functionality==
+
==Implementacja metod==
The five methods implementing single SciMark benchmarks are very similar, so only one, running the Fast Fourier Transform has been shown:
+
Pięć metod, które zawierają główną funkcjonalność SciMarka, jest do siebie bardzo podobnych. Dlatego zacytowano tu tylko jedną, wykonującą test szybkiej transformaty Fouriera:
  
 
  IPTR ApplicationFastFourierTransform(Class *cl, Object *obj)
 
  IPTR ApplicationFastFourierTransform(Class *cl, Object *obj)
Line 85: Line 85:
 
  }
 
  }
  
The code uses [[Locating Objects in the Object Tree|dynamic object tree search]] for accessing MUI objects.
+
Metoda używa [[Wyszukiwanie obiektów w drzewie|dynamicznego wyszukiwania obiektów]] w celu uzyskania dostępu do obiektów MUI.
  
The method sets the benchmark data size first, based on the ''d->LargeData'' switch variable. This variable is set with the ''APPA_LargeData'' attribute, which in turn is bound to the "Large Data" button via a notification. Then the status bar progress is cleared and some text is set to inform the user what is being done. The result textfield for the benchmark is cleared as well.
+
Pierwszym krokiem jest ustalenie rozmiaru danych dla testu, zgodnie z wartością pola ''d->LargeData''. Pole to jest aktualizowane przy zmianie atrybutu ''APPA_LargeData'', który z kolei jest powiązany notyfikacją z przyciskiem "Large Data". Następnie jest kasowana zawartość paska stanu i wpisywana jest tam informacja dla użytkownika o wykonującym się teście. Pole wyniku testu jest również czyszczone.
  
The next step is to put the application in the "busy" state. It should be always done, when the application may not be responding to user input for anything longer than, let's say half a second. Setting ''MUIA_Application_Sleep'' to ''TRUE'' locks the GUI and displays the busy mouse pointer when the application window is active. Of course offloading processor intensive tasks to a subprocess is a better solution in general cases, but for a benchmark it makes little sense. A user has to wait for the benchmark result anyway before doing anything else, like starting another benchmark. The only usability problem is that a benchmark can't be stopped before it finishes. Let's leave it as is for now, for a benchmark, where the computer is expected to use all its computing power for benchmarking, a few seconds of GUI being unresponsive is not such a big problem.
+
Z kolei aplikacja wprowadzana jest w stan zajętości (ang. ''busy''). Należy to czynić zasze, gdy aplikacja może nie reagować na poczynania użytkownika dłużej niż, powiedzmy, pół sekundy. Ustawienie atrybutu ''MUIA_Application_Sleep'' na wartość ''TRUE'' blokuje GUI i wyświetla "zajęty" wskaźnik myszy gdy okno programu jest aktywne. Oczywiście generalnie lepszym wyjściem byłoby uruchomienie testu w podprocesie, ale w teście wydajności nie ma to większego sensu. Tak czy inaczej użytkownik musi zaczekać na wykonanie testu zanim uruchomi kolejny. Jedynym problemem z punktu widzenia użytkownika jest to, że nie da się przerwać testu w trakcie jego wykonywania. Dla prostoty kodu pozostawiono ten problem nierozwiązany. Uruchamiając test mocy procesora oczekujemy, że obciąży on komputer całkowicie, kilka sekund zablokowania GUI programu nie jest w tym przypadku problemem.  
  
The next line of code runs the benchmark, by calling ''kernel_measureFFT()'' function from the original SciMark code. After the benchmark is done, the result is formatted and displayed in the result field using ''NewRawDoFmt()'', which is a low-level string formatting function from ''exec.library'' and with the ''RAWFMTFUNC_STRING'' constant, it works just like ''sprintf()''. It uses a fixed buffer of 128 characters (which is much more than needed, but adds a safety margin) located in the object instance data. Unsleeping the application and setting the status bar text to "Ready." ends the method.
+
Następna linia kodu uruchamia właściwy test, wywołując funkcję ''kernel_measureFFT()'' z oryginalnego kodu SciMarka. Po zakończeniu testu otrzymany rezultat jest formatowany z użyciem funkcji ''NewRawDoFmt()''. Jest to funkcja systemowa z ''exec.library''. Użyta ze stałą ''RAWFMTFUNC_STRING'' działa tak samo jak standardowy ''sprintf()''. Do formatowania zastosowano bufor o stałej długości 128 bajtów, więcej od rzeczywistej możliwej długości tekstu, ale warto zawsze zostawić margines bezpieczeństwa. Sformatowany tekst wyświetlany jest w polu rezultatu testu. Następnie aplikacja jest "budzona z uśpienia" a tekst na pasku stanu jest zmieniany na "Ready."
  
The ''APPM_AllBenchmarks()'' method code is longer so it is not repeated here. The method is very similar to the single benchmark method anyway. The difference is it runs all 5 tests accumulating their results in a table. It also updates the progress bar after every benchmark. Finally it calculates a mean score and displays it.
+
Metoda ''APPM_AllBenchmarks()'' jest znacznie dłuższa, dlatego jej kod nie został zamieszczony w artykule. Metoda ta wywołuje kolejno wszystkie 5 testów, gromadząc ich wyniki w tablicy. Po każdym wykonanym teście aktualizowany jest pasek postępu. Na zakończenie wyliczany jest łączny wynik testów i wyświetlany w polu wyników.
  
  
==Final Port==
+
==Gotowy port programu==
[http://krashan.ppa.pl/morphzone_tutorials/scimark2_mui.lha The complete source of SciMark2 MUI port]
+
[http://krashan.ppa.pl/morphzone_tutorials/scimark2_mui.lha Kompletny kod źródłowy SciMarka2 w wersji MUI]
  
The program may be built by running ''make'' in the source directory.
+
Program można skompilować wydając polecenie ''make'' w katalogu z kodem źródłowym.

Latest revision as of 16:01, 29 January 2011

Grzegorz Kraszewski


Ten artykuł w innych językach: angielski

Program SciMark2

Często poradniki i przewodniki programowania zanudzają czytelników bezużytecznymi teoretycznimi przykładami. W tym przewodniku przeportujemy na MorphOS-a konkretny program, dodając mu interfejs graficzny w MUI. Naszą ofiarą padnie SciMark 2. SciMark2 to jeden z wielu programów mierzących wydajność procesora i pamięci. Aby je zmierzyć SciMark2 przeprowadza serię testów opartych na typowych obliczeniach naukowych, takich jak szybka transformata Fouriera, dekompozycja macierzy na macierze LU, mnożenie macierzy rzadkich i tak dalej. Program został pierwotnie w języku Java w celu porównywania wydajności maszyn wirtualnych tego języka. Następnie przepisano go w języku C (a także w wielu innych językach programowania). Kod źródłowy w C jest dostępny na stronie domowej projektu.

Kod źródłowy używa wyłącznie standardowych funkcji ANSI C, więc kompiluje się "od ręki" na MorphOS-ie po uruchomieniu znajdującego się w archiwum Makefile. Trzeba jedynie zmienić linię $CC = cc na $CC = gcc, bo tę ostatnią nazwę nosi standardowy kompilator w naszym systemie. Rezultatem kompilacji jest typowa konsolowa aplikacja. Oto przykładowe wyniki działania SciMarka2 na Pegasosie 2 z procesorem G4:


Scimark cli noopt.png


Wyniki są niezbyt zachwycające. Bierze się to z faktu, że Makefile nie włącza w kompilatorze żadnych optymalizacji. Najprościej jest dodać linię $CFLAGS = -O3 pod $CC = gcc. Warto też zlinkować program z biblioteką libnix (linkowana statycznie biblioteka emulująca środowisko uniksowe, patrz Standard C and C++ Libraries) dodając -noixemul do CFLAGS i LDFLAGS. Po ponownej kompilacji i uruchomieniu wyniki ulegają znaczącej poprawie (program skompilowano kompilatorem GCC 4.4.4 z oficjalnego SDK):


Scimark cli opt.png


To doświadczenie pokazuje jak bardzo ważne jest korzystanie z optymalizacji kodu, zwłaszcza w programach wykonujących dużo obliczeń. Zoptymalizowan kod jest ponad 4 razy szybszy!


Przegląd kodu

Oryginalny kod źródłowy jest logicznie podzielony na moduły. Pięć plików: FFT.c, LU.c, MonteCarlo.c, SOR.c i SparseCompRow.c zawiera pojedyncze testy obliczeniowe. Pliki array.c i Random.c zawierają funkcje pomocnicze używane w testach. W plik Stopwatch.c z kolei znajdują się procedury pomiaru czasu. Plik scimark2.c zawiera funkcję main() i tekstowy interfejs programu.

Planowany interfejs graficzny powinien pozwalać użytkownikowi na uruchomienie każdego testu oddzielnie, albo wszystkich po kolei. Program posiada też opcję -large, która zwiększa rozmiary danych dla poszczególnych testów tak, że nie mieszczą się w pamięci podręcznej procesora. Dobrą zasadą przy portowaniu programów jest ograniczenie modyfikacji oryginalnych plików do minimum. Dzięki temu znacznie ułatwiona jest aktualizacja portu, kiedy ukaże się nowa wersja oryginału. W przypadku SciMarka wystarczy zastąpić tylko jeden plik, mianowicie scimark2.c. W zaawansowanym porcie możnaby też zastąpić Stopwatch.c kodem bezpośrednio używającym timer.device, co zwiększyłoby dokładność pomiaru czasu. To zagadnienie jednakże nie mieści się w zakresie tematycznym artykułu.

Bliższe przyjrzenie się plikowi scimark2.c ujawnia, że zawiera on obiekt Random (jest to struktura zdefiniowana w pliku Random.h), który jest używany przez wszystkie 5 testów. W oryginalnym kodzie jest on tworzony funkcją new_Random_seed() na początku programu i usuwany funkcją delete_Random() na jego końcu. Najlepszym miejscem na ten obiekt w zMUI-fikowanej wersji są dane obiektu aplikacji. Wtedy można go zainicjalizować w konstruktorze aplikacji (metoda OM_NEW()) a usunąć w destruktorze (metoda OM_DISPOSE()). Te dwie metody należy więc w klasie pochodnej od Application przeciążyć.


Projekt intrerfejsu graficznego

Scimark gui.png
Nie ma oczywiście "jedynie słusznego" projektu GUI dla SciMarka. Prosty projekt, używający ograniczonej ilości klas MUI, pokazany jest na ilustracji po lewej. Mamy tam 5 przycisków do uruchamiania poszczególnych testów i szósty do automatycznego wykonania wszystkich po kolei. Wszystkie te przyciski są obiektami klasy Text. Po prawej znajdują się gadżety wyświetlające wyniki testów. One również są klasy Text, mają jedynie inny zestaw atrybutów. Przycisk "Large Data", oczywiście również klasy Text, jest przyciskiem dwustanowym. O dziwo pasek stanu na dole nie jest klasy Text ale klasy Gauge, dzięki czemu będzie można w nim wyświetlać postęp wykonywania testów w czasie wykonywania całego zestawu. Poziome paski oddzielające pojedyncze testy od testu zbiorczego są instancjami klasy Rectangle. Do tego mamy trzy niewidoczne obiekty klasy Group. Pierwszy to grupa pionowa będąca głównym obiektem okna. Ma ona dwie podgrupy. Górna to grupa tablicowa o dwóch kolumnach, w której znajdują się przyciski testów i pola wyników. Grupa dolna, będąca grupą poziomą, zawiera przycisk "Large Data" i pasek stanu.

Najprościej zacząć tworzenie GUI kopiując po prostu przykład "HelloWorld". Nowe obiekty dodajemy wewnątrz funkcji build_gui(). Zmodyfikowany przykład jest gotowy do skompilowania i uruchomienia. Oczywiście nie jest to kompletny program, a jedynie model interfejsu graficznego.

W kodzie można zauważyć, że funkcja build_gui() nie zawiera w sobie całego kodu tworzącego interfejs. Kod dla niektórych obiektów został przeniesiony do oddzielnych funkcji wywoływanych z głównego wywołania funkcji MUI_NewObject(). Podzielenie funkcji tworzącej GUI na fragmenty ma szereg zalet:

  • Większa czytelność kodu i prostsze wprowadzanie zmian. Pojedyncze wywołanie MUI_NewObject() staje się szybko bardzo długie w miarę rozbudowy interfejsu. Edycja tak dużej funkcji rozciągającej się na kilka ekranów edytora jest niewygodna. Dodawanie i usuwanie obiektów staje się koszmarem, nawet jeżeli wcięcia w kodzie są stosowane konsewentnie. Z drugiej strony taka funkcja może mieć 10 i więcej poziomów wcięć, co również czyni ją trudną w czytaniu.
  • Zmniejszenie wielkości kodu. Zamiast powtarzać bardzo podobny kod wiele razy, na przykład przyciski różniące się jedynie etykietami, można wywołać podprogram z tekstem etykiety jako parametrem.
  • Debugowanie. Bywa, że MUI odmawia stworzenia obiektu aplikacji z powodu jakiegoś błędnego taga albo jego wartości. Jeżeli główne wywołanie MUI_NewObject(), tworzące obiekt aplikacji, jest podzielone na szereg mniejszych funkcji, łatwo wyizolować błąd wstawiając kilka Printf()-ów w tychże funkcjach.


Metody i atrybuty

Zaprojektowany właśnie intefrejs graficzny do SciMarka definiuje 6 akcji, jakie można wykonać w tym programie. Mamy pięć akcji polegających na wykonaniu pojedynczego testu, oraz szóstą, która wykonuje wszystkie testy po kolei i oblicza łączny wynik. Akcje te będą bezpośrednio odpowiadały metodom klasy utworzonej z klasy Application. GUI określa nam także jeden atrybut, związany z przyciskiem LargeData. Przypomnę, że określa on rozmiary danych dla testów. Ponieważ metody nie wymagają żadnych parametrów, nie ma potrzeby definiowania ich struktur. Jakikolwiek atrybut może być używany jako wartość początkowa konstruktora, może być ustawialny (wymaga przeciążenia metody OM_SET()), może też być odczytywalny (wymaga przeciążenia metody OM_GET()). Nasz nowy atrybut, nazwany APPA_LargeData, wymaga jedynie możliwości ustawiania. W konstruktorze możemy go domyślnie ustawić na FALSE, ponieważ na starcie programu przycisk "LargeData" jest wyłączony. Możliwość odczytu wartości atrybutu nie jest potrzebna, bo jest on odczytywany wyłącznie wenątrz klasy aplikacji, można więc bezpośrednio odwołać się do odpowiedniego pola danych obiektu.

Pisząc program warto umieszczać każdą klasę w oddzielnym pliku. Ułatwia to utrzymanie modułowości kodu, pozwala również na ukrycie danych prywatnych klasy. Pociąga to sa sobą konieczność napisania makefile, ale oryginalny kod SciMarka również składa się z kilku pilków, więc tak, czy inaczej, jest to nieuniknione. Stosując się do przedstawionych powyżej wskazówek projektowych można napisać plik nagłówkowy klasy i jej kod. Klasa w tej postaci nadal nic nie robi, po prostu zawiera puste metody akcji programu i przeciąża metody OM_SET(), OM_NEW() i OM_DISPOSE(). Pisanie takiego szkieletu klasy jest nudną mechaniczną pracą, więc można ją zwalić na komputer. W istocie, przykładowy kod został wygenerowany narzędziem ChocolateCastle. Niestety ChocolateCastle jest wciąż w wersji beta, więc kilka drobnych poprawek trzeba było wprowadzić ręcznie.

Następnym krokiem w projektowaniu programu jest połączenie metod i atrybutów z elementami interfejsu graficznego używając notyfikacji. Notyfikacje można ustawiać dopiero gdy zarówno źródło jak i cel notyfikacji są istniejącymi obiektami. W kodzie SciMarka są one ustawiane po wykonaniu funkcji build_gui(). Wszystkie przyciski testów mają bardzo podobne notyfikacje, dlatego tutaj pokazana jest tylko jedna:

DoMethod(findobj(OBJ_BUTTON_FFT, App), MUIM_Notify, MUIA_Pressed, FALSE,
 App, 1, APPM_FastFourierTransform);

Przycisk "Large Data" ma notyfikację ustawiającą odpowiadający mu atrybut:

DoMethod(findobj(OBJ_BUTTON_LDATA, App), MUIM_Notify, MUIA_Selected, MUIV_EveryTime,
 App, 3, MUIM_Set, APPA_LargeData, MUIV_TriggerValue);

Obiekty źródłowe notyfikacji są dynamicznie wyszukiwane w drzewie obiektów aplikacji (makro findobj()). Zaoszczędza to potrzeby definiowania dla nich globalnych wskaźników.


Implementacja metod

Pięć metod, które zawierają główną funkcjonalność SciMarka, jest do siebie bardzo podobnych. Dlatego zacytowano tu tylko jedną, wykonującą test szybkiej transformaty Fouriera:

IPTR ApplicationFastFourierTransform(Class *cl, Object *obj)
{
  struct ApplicationData *d = INST_DATA(cl, obj);
  double result;
  LONG fft_size;

  if (d->LargeData) fft_size = LG_FFT_SIZE;
  else fft_size = FFT_SIZE;

  SetAttrs(findobj(OBJ_STATUS_BAR, obj),
    MUIA_Gauge_InfoText, (LONG)"Performing Fast Fourier Transform test...",
    MUIA_Gauge_Current, 0,
  TAG_END);

  set(findobj(OBJ_RESULT_FFT, obj), MUIA_Text_Contents, "");
  set(obj, MUIA_Application_Sleep, TRUE);
  result = kernel_measureFFT(fft_size, RESOLUTION_DEFAULT, d->R);
  NewRawDoFmt("%.2f MFlops (N = %ld)", RAWFMTFUNC_STRING, d->Buf, result, fft_size);
  set(findobj(OBJ_RESULT_FFT, obj), MUIA_Text_Contents, d->Buf);
  set(obj, MUIA_Application_Sleep, FALSE);
  set(findobj(OBJ_STATUS_BAR, obj), MUIA_Gauge_InfoText, "Ready.");
  return 0;
}

Metoda używa dynamicznego wyszukiwania obiektów w celu uzyskania dostępu do obiektów MUI.

Pierwszym krokiem jest ustalenie rozmiaru danych dla testu, zgodnie z wartością pola d->LargeData. Pole to jest aktualizowane przy zmianie atrybutu APPA_LargeData, który z kolei jest powiązany notyfikacją z przyciskiem "Large Data". Następnie jest kasowana zawartość paska stanu i wpisywana jest tam informacja dla użytkownika o wykonującym się teście. Pole wyniku testu jest również czyszczone.

Z kolei aplikacja wprowadzana jest w stan zajętości (ang. busy). Należy to czynić zasze, gdy aplikacja może nie reagować na poczynania użytkownika dłużej niż, powiedzmy, pół sekundy. Ustawienie atrybutu MUIA_Application_Sleep na wartość TRUE blokuje GUI i wyświetla "zajęty" wskaźnik myszy gdy okno programu jest aktywne. Oczywiście generalnie lepszym wyjściem byłoby uruchomienie testu w podprocesie, ale w teście wydajności nie ma to większego sensu. Tak czy inaczej użytkownik musi zaczekać na wykonanie testu zanim uruchomi kolejny. Jedynym problemem z punktu widzenia użytkownika jest to, że nie da się przerwać testu w trakcie jego wykonywania. Dla prostoty kodu pozostawiono ten problem nierozwiązany. Uruchamiając test mocy procesora oczekujemy, że obciąży on komputer całkowicie, kilka sekund zablokowania GUI programu nie jest w tym przypadku problemem.

Następna linia kodu uruchamia właściwy test, wywołując funkcję kernel_measureFFT() z oryginalnego kodu SciMarka. Po zakończeniu testu otrzymany rezultat jest formatowany z użyciem funkcji NewRawDoFmt(). Jest to funkcja systemowa z exec.library. Użyta ze stałą RAWFMTFUNC_STRING działa tak samo jak standardowy sprintf(). Do formatowania zastosowano bufor o stałej długości 128 bajtów, więcej od rzeczywistej możliwej długości tekstu, ale warto zawsze zostawić margines bezpieczeństwa. Sformatowany tekst wyświetlany jest w polu rezultatu testu. Następnie aplikacja jest "budzona z uśpienia" a tekst na pasku stanu jest zmieniany na "Ready."

Metoda APPM_AllBenchmarks() jest znacznie dłuższa, dlatego jej kod nie został zamieszczony w artykule. Metoda ta wywołuje kolejno wszystkie 5 testów, gromadząc ich wyniki w tablicy. Po każdym wykonanym teście aktualizowany jest pasek postępu. Na zakończenie wyliczany jest łączny wynik testów i wyświetlany w polu wyników.


Gotowy port programu

Kompletny kod źródłowy SciMarka2 w wersji MUI

Program można skompilować wydając polecenie make w katalogu z kodem źródłowym.