Programowanie (PHP, Java...), System (GNU, BSD, Windows...)

GSlice i C++

22 lipca, 2007 o 17:42:31 Dodaj komentarz Poziom: 0 Permalink

Ostatnio zastanawiałem się, czy da się połączyć GSlice i operator new w C++. Kod wydawał by się bardzo prosty:

Oraz test wydajności:

#include #include using namespace std; #ifdef GSLICE #include static GHashTable *mem_map = g_hash_table_new(g_direct_hash, g_direct_equal); void *operator new(unsigned int size) { gpointer p = g_slice_alloc(size); g_hash_table_insert(mem_map, p, GINT_TO_POINTER(size)); #ifndef NDEBUG fprintf(stderr, "Allocated %u bytes in %p.\n", size, p); #endif return p; } void operator delete(void *p) { unsigned int size = GPOINTER_TO_INT(g_hash_table_lookup(mem_map, p)); #ifndef NDEBUG fprintf(stderr, "Freed %u bytes from %p.\n", size, p); #endif g_slice_free1(size, p); g_hash_table_remove(mem_map, p); } #endif #ifndef N #define N 1024 #endif typedef int * pint; int main(int argc, char **argv) { #ifdef TEST1 pint *table = new pint[N]; bool *table_alloced = new bool[N]; for(unsigned int i = 0; i < N; i++) table_alloced[i] = false; for(unsigned int i = 0; i < N * 8; i++) { unsigned int pos = (rand() % N); if(table_alloced[pos]) delete table[pos]; else table[pos] = new int(rand()); table_alloced[pos] = !table_alloced[pos]; } for(unsigned int i = 0; i < N; i++) if(table_alloced[i]) delete table[i]; delete[] table; delete[] table_alloced; #endif #ifdef TEST2 for(int i = 0; i < N * 16; i++) { int *j = new int(rand()); delete j; } #endif }]]>

Okazało się, że kod z GSlice ma niewielki nażut - który w dodatku rośnie geometrycznie(tzn. na początku to jest praktycznie ta sama ilość czasu aż stopniowo dąży do dwukrotności). Postanowiłem przetestować, jak wygląda ten sam kod w czystym C + GSlice

#ifndef NDEBUG #include #endif #ifndef N #define N 1024 #endif int main(int argc, char **argv) { #ifdef TEST1 int **table = g_slice_alloc(sizeof(int *) * N); char *table_alloced = g_slice_alloc0(sizeof(char) * N); unsigned int i = 0; while(i++ < N * 8) { unsigned int pos = (rand() % N); if(table_alloced[pos]) { g_slice_free(int, table[pos]); } else { table[pos] = g_slice_new(int); *table[pos] = rand(); } table_alloced[pos] = !table_alloced[pos]; } i = -1; while(++i < N) if(table_alloced[i]) g_slice_free(int, table[i]); g_slice_free1(sizeof(int *) * N, table); g_slice_free1(sizeof(char) * N, table_alloced); #endif #ifdef TEST2 unsigned int i = 0; while(i++ < N * 16) { int *j = g_slice_new(int); *j = rand(); g_slice_free(int, j); } #endif }]]>

Okazało się, że trwa to ok. ¾ czasu dla C++. Postawiłem hipoteze, że cały narzut jest spowodowany koniecznością zaglądania do mem_map. Dlatego zmieniłem kod dla C na coś takiego:

#else #include #endif #ifndef NDEBUG #include #endif #ifndef N #define N 1024 #endif #ifdef GSLICE #define MALLOC(size) g_slice_alloc(size) #define NEW(type) g_slice_new(type) #define FREE(type, mem) g_slice_free(type, mem) #define FREE1(size, mem) g_slice_free1(size, mem) #else #define MALLOC(size) malloc(size) #define NEW(type) malloc(sizeof(type)) #define FREE(type, mem) free(mem) #define FREE1(size, mem) free(mem) #endif int main(int argc, char **argv) { #ifdef TEST1 int **table = MALLOC(sizeof(int *) * N); char *table_alloced = MALLOC(sizeof(char) * N); unsigned int i = 0; while(i++ < N) table_alloced[i] = 0; i = 0; while(i++ < N * 8) { unsigned int pos = (rand() % N); if(table_alloced[pos]) { FREE(int, table[pos]); } else { table[pos] = NEW(int); *table[pos] = rand(); } table_alloced[pos] = !table_alloced[pos]; } i = -1; while(++i < N) if(table_alloced[i]) FREE(int, table[i]); FREE1(sizeof(int *) * N, table); FREE1(sizeof(char) * N, table_alloced); #endif #ifdef TEST2 unsigned int i = 0; while(i++ < N * 16) { int *j = NEW(int); *j = rand(); FREE(int, j); } #endif }]]>

Kod C był trochę bardziej wydajny niż C++ (na oko 10% dla najwyższych N). Zresztą jak pisze GLib Manual For newly written code it is recommended to use the new g_slice API instead of g_malloc() and friends, as long as (...) the object size used at allocation time is still available when freeing.. Można oczywiście pisać programy tego typu:

#include using namespace std; #ifndef N #define N 1024 #endif typedef int * pint; int main(int argc, char **argv) { #ifdef TEST1 int **table = (int **)g_slice_alloc(sizeof(int) * N); bool *table_alloced = (bool *)g_slice_alloc0(sizeof(bool) * N); for(unsigned int i = 0; i < N * 8; i++) { unsigned int pos = (rand() % N); if(table_alloced[pos]) { g_slice_free(int, table[pos]); } else { table[pos] = g_slice_new(int); *table[pos] = int(rand()); } table_alloced[pos] = !table_alloced[pos]; } for(unsigned int i = 0; i < N; i++) if(table_alloced[i]) g_slice_free(int, table[i]); g_slice_free1(sizeof(int) * N, table); g_slice_free1(sizeof(bool) * N, table_alloced); #endif #ifdef TEST2 for(int i = 0; i < N * 16; i++) { int *j = new int(rand()); delete j; } #endif }]]>

Po pierwsze - czy ktoś wie, jak wywołać destruktor? Po drugie zajmuje to trochę więcej linii kodu. Pomijając pierwszy problem wysiłek może się opłacić - w pierwszym teście doganiał od C z GSlice a w drugim był koło C++.

W zasadzie zastanawia mnie tylko jedno - dlaczego jest aż tak duży nażut podczas przeszukiwania hash'a (w końcu podobną operacje trzeba zapewne wykonać przy free) - ale to już osobny problem.

Zapomniałbym - skrypt testujący który używałem:

>>> TESTS=$TESTS" while [ $n -le 20 ] do i=$((2**n)) echo ">>> i=$i" echo ">> STANDARD C++" g++ operators.cc -DNDEBUG -DN=$i $TESTS -O2; time ./a.out >> /dev/null rm a.out echo ">> GSLICE C++" g++ operators.cc -DNDEBUG -DGSLICE $TESTS -DN=$i -O2 $glib; time ./a.out >> /dev/null echo ">> GSLICE C++ without new" g++ nnew.cc -DNDEBUG -DGSLICE $TESTS -DN=$i -O2 $glib; time ./a.out >> /dev/null echo ">> STANDATD C" gcc operators.c -DNDEBUG -DN=$i $TESTS -O2; time ./a.out >> /dev/null echo ">> GSLICE C" gcc operators.c -DNDEBUG -DGSLICE $TESTS -DN=$i -O2 $glib; time ./a.out >> /dev/null rm a.out n=$((n+1)) done]]>

Testy były przeprowadzone na Arch'u z kernelem Linux 2.6.21 oraz biblioteką GNU libc 2.6 gcc 4.2.1.

Komentarze do wpisu

Możesz śledzić odpowiedzi poprzez kanał RSS. Możesz dodać komentarz lub zostawić ślad (trackback) ze swojego bloga.

Jeszcze nie ma żadnych komentarzy. Twój może być pierwszy.

Dodaj komentarz

Textile Lite włączony ( szczegółowy opis znaczników ):