Z wydajnością biblioteki standardowej C++ bywa różnie

Biblioteka standardowa C++ (klasy strumieniowe, kontenery STL, klasa string itp) jest niesłychanie użyteczna - i powinna być wykorzystywana - ale korzystając z niej trzeba koniecznie zwrócić uwagę na efektywność implementacji (i ewentualnie ją zmienić!).

Prosta - a często używana - sprawa. Nieraz zdarza się nam budować inkrementalnie jakiś napis (czy to dodając kolejne kawałki do obiektu typu string, czy to posługując się strumieniem pamięciowym - ostrstream lub ostringstream). Widziałem już implementacje poważnych firm, które dla kodu typu

string s;
for(unsigned i = 0; i < 255; ++ i)
{
    s += 'a';
}

za każdym obrotem realokowały pamięć, przydzielając bufor o 1 większy! Nieraz zresztą przy okazji rezerwując jakiś mutex! Efekty dla wydajności przy operacjach na większych napisach są chyba oczywiste.

Jeśli tak jest, warto zainteresować się wymianą implementacji biblioteki standardowej. STLport (patrz www.stlport.org) jest porządną i dosyć wydajną implementacją dostępną za darmo na licencji OpenSource (korzystam na kilku platformach - także w programach komercyjnych - i sobie chwalę).

A tak na boku: jak wydajna nie byłaby nasza implementacja, jeśli z góry znamy - choćby z grubsza - rozmiar naszego napisu, warto prealokować pamięć wołając

   s.reserve(przyblizony_rozmiar);

Po takim wywołaniu mamy zarezerwowany odpowiednio duży bufor i realokacja nie będzie potrzebna (ale uwaga: jeśli zarezerwujemy za dużo, pamięć ta nie zostanie zwolniona aż usuniemy naszą zmienną - nawet po wyczyszczeniu zawartości string swoją pamięć 'trzyma').

Niestety nie ma możliwości wstępnego zarezerwowania pamięci w ostringstream (tak naprawdę robiłem pewne sztuczki umożliwiające to ale były tak hackerskie, że wolę się do nich nie przyznawać).

Podobne uwagi dotyczą wykorzystywania kontenerów STL, zwłaszcza klasy vector (choć tu problemy z wydajnością implementacji są jakoś wyraźnie rzadsze - chyba dlatego, że referencyjna implementacja HP, z której wyprowadzali swoje implementacje twórcy różnych kompilatorów, była przyzwoita).

Jedno końcowe ostrzeżenie: używanie STLport z Visual C++ przy zachowaniu domyślnych ustawień (o ile wartości domyślne w ostatnich wersjach się nie zmieniły) grozi wyciekami pamięci. Jest to spowodowane przez błąd kompilatora, który niekiedy ... tworzy kilka kopii tego samego obiektu statycznego (sprawa jest troszkę skomplikowana, są w użyciu template - z którymi Visual C++ stale miewa trudności). Efekt jest w każdym razie fajny: mamy kilka obiektów zarządzających pulą wolnej pamięci, przy pomocy jednego pamięć alokujemy a przy pomocy drugiego zwalniamy. Drugi obiekt dołącza zwalnianą pamięć do listy wolnych bloków ale nikt go o nie nie prosi, bo pamięć alokuje ten pierwszy. Problem zauważamy, gdy zdarzy się nam napisać kod, w którym pamięć alokowana jest w jednym pliku źródłowym a zwalniana w innym.

Pomaga rezygnacja z domyślnego alokatora dostarczanego przez STLport (niestety tracimy odrobinę na wydajności). Trzeba w czasie kompilacji (bądź w linii poleceń, bądź w nagłówkowym pliku konfiguracyjnym) definiować makro __STL_USE_NEWALLOC - co powoduje delegowanie poleceń alokacji pamięci do standardowego new.

komentarze obsługiwane przez Disqus