C: Speicherallokation: malloc() vs. talloc()
Als ich vor Kurzem auf gentoo Linux ein `emerge -quDN world‘ machte, habe ich ausnahmsweise mal zugeschaut, was da gerade alles aktualisiert wird.
Da fiel mir ein Paket namens `sys-libs/talloc‘ auf – und fragte mich, ob das tatsächlich etwas mit Speicherallocation zu tun haben könnte.
Kurz gegoogelt bin ich auf einen Artikel von Stephen Gallagher aufmerksam geworden: Why you should use talloc for your next project.
talloc ist eine Speicherimplementierung vom Samba-Projekt. talloc bietet hier die Möglichkeit, reservierten Speicher in einem zusammenhängenden Bereich zu erhalten. Welche Vorteile daraus gezogen werden können, ist an Hand dieses kleinen Beispiels ersichtlich:
#include <unistd.h> #include <sys/types.h> #include <stdio.h> #include <stdlib.h> #include <sys/time.h> #include <talloc.h> struct user_ { uid_t uid; char *username; size_t num_groups; char **groups; }; double get_duration_in_ms(struct timeval *started) { long seconds, useconds; double duration_msecs; struct timeval ended; gettimeofday(&ended, NULL); seconds = ended.tv_sec - started->tv_sec; useconds = ended.tv_usec - started->tv_usec; duration_msecs = ((seconds) * 1000 + useconds/1000.0) + 0.5; return duration_msecs; } /* ************************************************* */ int main() { struct user_ *user = talloc(NULL, struct user_); int i; double duration; struct timeval started; gettimeofday(&started, NULL); user->uid = 1000; user->num_groups = 320000; user->username = talloc_strdup(user, "Test user"); user->groups = talloc_array(user, char*, user->num_groups); for (i = 0; i < user->num_groups; i++) { user->groups[i] = talloc_asprintf(user->groups, "Test group %d", i); } /* now free allocated memory */ talloc_free(user); duration = get_duration_in_ms(&started); printf("duration talloc: `%f' ms\n", duration); return EXIT_SUCCESS; }
Hier stellt man fest, dass nur ein einziges free() in Form von talloc_free() angegeben wurde.
Normalerweise würde der oben gelistete Code so aussehen, um den verwendeten Speicher korrekt und vollständig wieder freizugeben:
#include <unistd.h> #include <sys/types.h> #include <stdio.h> #include <stdlib.h> #include <sys/time.h> #include <string.h> /* struct user_ and get_duration_in_ms() see above */ /* ************************************************* */ int main() { struct user_ *user = malloc(sizeof(struct user_)); int i; double duration; struct timeval started; gettimeofday(&started, NULL); user->uid = 1000; user->num_groups = 320000; user->username = strdup("Test user"); user->groups = (char**) malloc(user->num_groups * sizeof(char *)); for (i = 0; i < user->num_groups; i++) { asprintf(&user->groups[i], "Test group %d", i); } /* now free allocated memory */ for (i = 0; i < user->num_groups; i++) { free(user->groups[i]); } free(user->groups); free(user->username); free(user); duration = get_duration_in_ms(&started); printf("duration malloc: `%f' ms\n", duration); return EXIT_SUCCESS; }
Würde man im unteren Beispiel wie im obigen Beispiel (Zeile 55) nur `user‘ befreien, würden hier konkret 320002 Bereiche nicht mehr freigeben sein (wenn das Programm nicht enden würde).
Valgrind gibt darüber Aufschluss, wieviel Speicher zurückbliebe:
…
==3204== definitely lost: 2,560,010 bytes in 2 blocks
==3204== indirectly lost: 5,648,874 bytes in 319,999 blocks
==3204== possibly lost: 16 bytes in 1 blocks
…
talloc_free() allerdings übernimmt durch den zusammenhängenden Aufbau eben schon die Freigabe des verwendeten Speichers von `user‘, inklusive aller Elemente von user->groups.
Das hat nun den Vorteil, dass man weniger free()-Aufrufe selbst tätigen muss.
Der Luxus, der einem mit talloc() geboten wird, hat aber natürlich auch seine Nachteile – die der schlechteren Performance.
Spielt die Performance in einem Projekt also nicht die Hauptrolle, ist talloc() sicherlich eine schöne Alternative zur normalen Speicherverwaltung der glibc (ptmalloc2).
I’m a little surprised by that measurement. Usually we’ve found talloc to be about 4% slower than malloc. The more-than-double time you see there is surprising, to say the least.
talloc can also be made much higher performance with the use of talloc_pool. This allows you to allocate a chunk of memory once and then continue to reuse it until it’s full (at which time, talloc calls will automatically start allocating outside the pool)
There’s a fantastic guide to talloc written by a colleague of mine here:
http://talloc.samba.org/talloc/doc/html/libtalloc__tutorial.html
He covers the talloc pools in Chapter 5:
http://talloc.samba.org/talloc/doc/html/libtalloc__tutorial.html
Also, Chapter 7 (Best Practises) is a must-read if you are interested in talloc:
http://talloc.samba.org/talloc/doc/html/libtalloc__bestpractices.html