Programowanie C++ Pomysły na szybkie C++ Piotr Pszczółkowski Często sie zdarza, że programiści po przejściu z C na C++ (z własnej woli, lub nie), narzekają że C++ jest wolniejsze. Bardzo często tak właśnie jest. Ale z ich własnej winy. Po prostu bywa tak, że eksperci od C staja się nagle nowicjuszami w C++. I stosują książkowe przykłady jako kod użytkowy. Tak naprawdę optymalny kod C++ powinien być tak samo szybki jak optymalny kod C (z wyjątkiem przypadku użycia funkcji wirtualnych). Nieoptymalny kod C będzie tak samo wolny jak nieoptymalny kod C++. dalszej części artykułu chciałbym przed- i jako sumę podanych łańcuchów stosuje się najczęściej stawić jeden z wielu przykładów jak moż- wyrażenie: na optymalizować kod programu napisa- Wnego w C++. Jednym z elementów stano- string txt_suma = txt1 + + txt2 + + txt3; wiących o sile C++ jest klasa string (będąca częścią bibliote- ki STL) lub klasa QString (biblioteka Qt), które pozwalają na Oczywiście wszystko jest w porządku. Aańcuch wyniko- prostą i przyjemną obsługę łańcuchów tekstowych. W C w wy będzie jak najbardziej poprawny, klasa string wykona zasadzie nie istnieje pojęcie łańcucha tekstowego, stosuje sie wszystko zgodnie z oczekiwaniami. tablice ASCIZ, czyli zwykle tablice znakowe ze znacznikiem Ale niestety nie jest to wersja optymalna. Jest wolna. Wy- końca w postaci '\0'. W C++ klasy typu string wewnętrznie nika to ze sposobu wykonania kodu przez kompilator. Otóż implementują obsługę łańcuchów tekstowych także używa- zanim do zmiennej 'txt_suma' zostanie przekazany wyniko- jąc tablic, ale jeśli łańcuch się rozrasta jej ponowna alokacja wy łańcuch tekstowy, proces dodawania kolejnych składni- zachodzi w tle, bez konieczności ingerencji programisty. ków będzie się opierał na wielokrotnym tworzeniu na sto- sie tymczasowych obiektów klasy string, będących tymcza- Wersja książkowa sową sumą dwóch dodawanych do siebie klas. Oczywiście Najprościej łańcuchy dodaje sie tak, jak podaje sie w książ- nie trzeba być ekspertem, aby sobie uświadomić, że każdora- kach (jest to też wersja najbardziej intuicyjna). Rozpatrzmy zowe tworzenie obiektu tymczasowego (i jego usuwanie) to połączenie trzech łańcuchów tekstowych: czas, którego nam szkoda (w większości przypadków). string txt1 = pierwsza czesc lancucha ; Jak można szybciej string txt2 = druga czesc lancucha ; Przede wszystkim należy unikać tworzenia obiektów tym- string txt3 = trzecia czesc lancucha ; czasowych. 38 marzec 2007 linux@software.com.pl Programowanie C++ Listing 1.1 listing programu testowego. #include const int diff = end_time - start_time; #include results2.push_back( diff ); #include cout << "(2. " << diff << ") " << endl; #include } using namespace std; } void strings_2_1( const string&, const string&, const cout << endl << "=====================" << endl; string&, const string& ); cout << "Wyniki wersji nr.1 (sec): " << endl; void strings_2_2( const string&, const string&, const { string&, const string& ); vector::const_iterator it = results1.begin(); void strings_3_1( const string&, const string&, const while( it != results1.end() ) string&, const string& ); { void strings_3_2( const string&, const string&, const cout << *it << " "; string&, const string& ); ++it; } void strings_4_1( const string&, const string&, const cout << endl; } string&, const string& ); cout << "Wyniki wersji nr.2 (sec): " << endl; { void strings_4_2( const string&, const string&, const vector::const_iterator it = results2.begin(); string&, const string& ); while( it != results2.end() ) int main() { { cout << *it << " "; const int MAX_LOOPS_NUMBER = 100000000; // 100,000,000 ++it; const int EXPERIMENTS_NUMBER = 20; } string s1 = "tekst nr. 1"; cout << endl << endl; string s2 = "tekst nr. 2"; } string s3 = "tekst nr. 3"; return 0; string s4 = "tekst nr. 4"; } vector results1; //***************************************************** vector results2; // suma 2 lancuchow cout << endl; //***************************************************** for( int i = 0; i < EXPERIMENTS_NUMBER; ++i ) { void strings_2_1( const string& in_s1, const cout << "Eksperyment nr. " << ( i + 1 ) << endl; string& in_s2, const string&, const string& ) { // ver.1 ================================ { int counter = 0; string str = in_s1 + in_s2; //-------------------------------------- } const time_t start_time = time( 0 ); void strings_2_2( const string& in_s1, const while( counter < MAX_LOOPS_NUMBER ) { string& in_s2, const string&, const string& ) strings_4_1( s1, s2, s3, s4 ); { (1) string str = in_s1; ++counter; str += in_s2; } } const time_t end_time = time( 0 ); //****************************************************** //-------------------------------------- // suma 3 lancuchow const int diff = end_time - start_time; //****************************************************** results1.push_back( diff ); void strings_3_1( const string& in_s1, const string& cout << "(1. " << diff << ") " << endl; in_s2, const string& in_s3, const string& ) { } string str = in_s1 + in_s2 + in_s3; } { // ver.2 ================================ void strings_3_2( const string& in_s1, const string& int counter = 0; in_s2, const string& in_s3, const string& ) { //-------------------------------------- string str = in_s1; const time_t start_time = time( 0 ); str += in_s2; while( counter < MAX_LOOPS_NUMBER ) { str += in_s3; strings_4_2( s1, s2, s3, s4 ); } (2) //*************************************************** ++counter; // sums 4 lancuchow } //*************************************************** const time_t end_time = time( 0 ); void strings_4_1( const string& in_s1, const string& //-------------------------------------- in_s2, const string& in_s3, const string& in_s4 ) www.lpmagazine.org 39 Programowanie C++ rozpoczęciem testu, i robimy to ponow- ------------------------------------- Listing 1.2 listing programu testowego nie po zakończeniu testu. Pózniej obliczamy ------------------------------------- { średni czas wykonania funkcji i po jakimś cza- wer.2 |65|65|65|66|69|65|66|65|65|69| string str = in_s1 + in_s2 + sie (niezbędna jest cierpliwość) zostaną wy- 69|68|68|68|69|69|68|68|68|69 in_s3 + in_s4; świetlone otrzymane wyniki (dla konkret- } nego zestawu funkcji). Używane funkcje na- Sredni czas wer.1 (av1) = 76.45 void strings_4_2( const string& zywają się według tego samego schematu: Sredni czas wer.2 (av2) = 67.20 in_s1, const string& in_s2, const string_n_1 i string_n_2, gdzie n określa ile av1/av2 = 1.13 string& in_s3, const string& in_s4 ) łańcuchów będziemy dodawać, a 1 i 2 okre- { ślają sposób w jaki to robimy (1-tradycyj- REZULTAT: wersja 2 jest szybsza od wersji 1 string str = in_s1; nie, 2-w sposób pokazany przeze mnie). Aby o 13%. No to już jest znacząca różnica. str += in_s2; przetestować kolejne wersje należy zmienić WYNIKI (w sekundach) dodawanie czte- str += in_s3; nazwę funkcji w liniach (1) i (2). No i skompi- rech łańcuchów: str += in_s4; lować ( g++ test.cpp -o test). } wer.1 |112|112|112|112|112|113|113|11 Wyniki 3|113|115|115|115|115|115|115|115|115 Zdecydowanie szybszym sposobem na wy- Poniżej przedstawiam wyniki, jakie otrzy- |116|116|116 konanie powyższego zadania jest poniższy małem na swoim komputerze. Wyniki jakie -------------------------------------- kod. Deklaracja zmiennych tak jak poprzed- otrzyma czytelnik (jeśli uruchomi program) ------------------------------------------------- nio: będą oczywiście trochę inne. Zależą one w wer.2 | 72| 72| 72| 72| 74| 71| 71| istotny sposób od komputera, na którym test 71| 72| 74| 74| 74| 74| 74| 74| 74| string txt1 = pierwsza czesc jest wykonywany. 74| 73| 73| 73 lancucha ; WYNIKI (w sekundach) dodawanie string txt2 = druga czesc lancucha ; dwóch łańcuchów: Sredni czas wer.1 (av1) = 114.00 string txt3 = trzecia czesc Sredni czas wer.2 (av2) = 72.90 lancucha ; wer.1 |38|38|38|38|38|37|37|37|37|39| av1/av2 = 1.56 39|39|38|38|38|38|39|39|39|39 ale sumowanie wykonamy teraz inaczej: -------|--|--|--|--|--|--|--|--|--|-- REZULTAT: wersja 2 jest szybsza od wersji 1 |--|--|--|--|--|--|--|--|--|-- o 56%. No to już jest bardzo dużo. string txt_suma = txt1; wer.2 |36|36|37|36|39|38|37|38|36|39| Na Wykresie 1 przedstawiam zestawie- txt_suma += ; 39|40|40|40|40|40|39|39|39|39 nie wyników czasów wykonania w zależno- txt_suma += txt2; ści od liczby dodawanych łańcuchów. txt_suma += ; Sredni czas wer.1 (av1) = 38.15 txt_suma += txt3; Sredni czas wer.2 (av2) = 40.30 Podsumowanie av1/av2 = 0.94 Jak widać tworzenie (w tle) obiektów tym- Ta implementacja jest o wiele, wiele szybsza. czasowych jest kosztowne. Nierozważne W moich testach (o których za chwile) doda- REZULTAT: wersja 2 jest wolniejsza(!) od stosowanie mechanizmów klas C++ cza- wanie tylko czterech łańcuchów w wersji dru- wersji 1 o 0.6% ( no to niewiele). sami może być czasochłonne. Niestety, ale giej było o prawie 60% szybsze. Liczba tra- WYNIKI (w sekundach) dodawania trzech znajomość tego co się dzieje z naszym, wy- dycyjnie dodawanych łańcuchów drastycz- łańcuchów: dawałoby się prostym kodem, jest bardzo nie wpływa na szybkość wykonania ope- przydatna. Czasami warto się zastanowić racji. wer.1 |75|75|75|74|74|75|75|75|75|78| nie tylko nad tym jak to wygląda, ale i jak to 77|78|78|78|78|77|78|78|78|78 będzie działać. Testy jak to jest naprawdę Na Listingu 1. przedstawiam prosty program testujący obie wersje dodawania łańcuchów. Plik tego programu można znalezć na dysku dołączonym do gazety. Aby trochę uśrednić (i tym samym uwia- rygodnić) wyniki naszych badań, należy określić liczbę eksperymentów na większą niż 1 (patrz EXPERIMENTS_NUMBER). Tak- że liczba wykonywanych prób dodawa- nia powinna być w miarę duża (patrz MAX_ LOOPS_NUMBER). Niestety dlatego też tes- ty zabierają trochę czasu, ale im więcej wy- konamy pętli i prób, tym wyniki będą bar- dziej wiarygodne. Przed każdą z pętli te- Rysunek 1. Przyrost czasu wykonania tradycyjnego sumowania łańcuchów tekstowych w stosunku do stujących pobieramy czas systemowy przed sumowania szybkiego 40 marzec 2007