GSlice i C++
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