2007 03 Pomysły na szybkie C [Programowanie]


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


Wyszukiwarka