source: trunk/third/evolution/e-util/e-memory.c @ 18142

Revision 18142, 28.3 KB checked in by ghudson, 22 years ago (diff)
This commit was generated by cvs2svn to compensate for changes in r18141, which included commits to RCS files with non-trunk default branches.
Line 
1/*
2 * Copyright (c) 2000, 2001 Ximian Inc.
3 *
4 * Authors: Michael Zucchi <notzed@ximian.com>
5 *          Jacob Berkman <jacob@ximian.com>
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of version 2 of the GNU General Public
9 * License as published by the Free Software Foundation.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
19 * USA
20
21*/
22
23#include "e-memory.h"
24
25#include <string.h> /* memset() */
26#include <stdlib.h> /* alloca() */
27#include <glib.h>
28
29#define s(x)                    /* strv debug */
30#define p(x)   /* poolv debug */
31#define p2(x)   /* poolv assertion checking */
32
33/*#define MALLOC_CHECK*/
34
35/*#define PROFILE_POOLV*/
36
37#ifdef PROFILE_POOLV
38#include <time.h>
39#define pp(x) x
40#else
41#define pp(x)
42#endif
43
44/*#define TIMEIT*/
45
46#ifdef TIMEIT
47#include <sys/time.h>
48#include <unistd.h>
49
50struct timeval timeit_start;
51
52static time_start(const char *desc)
53{
54        gettimeofday(&timeit_start, NULL);
55        printf("starting: %s\n", desc);
56}
57
58static time_end(const char *desc)
59{
60        unsigned long diff;
61        struct timeval end;
62
63        gettimeofday(&end, NULL);
64        diff = end.tv_sec * 1000 + end.tv_usec/1000;
65        diff -= timeit_start.tv_sec * 1000 + timeit_start.tv_usec/1000;
66        printf("%s took %ld.%03ld seconds\n",
67               desc, diff / 1000, diff % 1000);
68}
69#else
70#define time_start(x)
71#define time_end(x)
72#endif
73
74#ifdef MALLOC_CHECK
75#include <mcheck.h>
76#include <stdio.h>
77static void
78checkmem(void *p)
79{
80        if (p) {
81                int status = mprobe(p);
82
83                switch (status) {
84                case MCHECK_HEAD:
85                        printf("Memory underrun at %p\n", p);
86                        abort();
87                case MCHECK_TAIL:
88                        printf("Memory overrun at %p\n", p);
89                        abort();
90                case MCHECK_FREE:
91                        printf("Double free %p\n", p);
92                        abort();
93                }
94        }
95}
96#define MPROBE(x) checkmem((void *)(x))
97#else
98#define MPROBE(x)
99#endif
100
101/* mempool class */
102
103#define STRUCT_ALIGN (4)
104
105typedef struct _MemChunkFreeNode {
106        struct _MemChunkFreeNode *next;
107        unsigned int atoms;
108} MemChunkFreeNode;
109
110typedef struct _EMemChunk {
111        unsigned int blocksize; /* number of atoms in a block */
112        unsigned int atomsize;  /* size of each atom */
113        GPtrArray *blocks;      /* blocks of raw memory */
114        struct _MemChunkFreeNode *free;
115} MemChunk;
116
117/**
118 * e_memchunk_new:
119 * @atomcount: The number of atoms stored in a single malloc'd block of memory.
120 * @atomsize: The size of each allocation.
121 *
122 * Create a new memchunk header.  Memchunks are an efficient way to allocate
123 * and deallocate identical sized blocks of memory quickly, and space efficiently.
124 *
125 * e_memchunks are effectively the same as gmemchunks, only faster (much), and
126 * they use less memory overhead for housekeeping.
127 *
128 * Return value: The new header.
129 **/
130MemChunk *e_memchunk_new(int atomcount, int atomsize)
131{
132        MemChunk *m = g_malloc(sizeof(*m));
133
134        m->blocksize = atomcount;
135        m->atomsize = MAX(atomsize, sizeof(MemChunkFreeNode));
136        m->blocks = g_ptr_array_new();
137        m->free = NULL;
138
139        return m;
140}
141
142/**
143 * memchunk_alloc:
144 * @m:
145 *
146 * Allocate a new atom size block of memory from a memchunk.
147 **/
148void *e_memchunk_alloc(MemChunk *m)
149{
150        char *b;
151        MemChunkFreeNode *f;
152        void *mem;
153
154        f = m->free;
155        if (f) {
156                f->atoms--;
157                if (f->atoms > 0) {
158                        mem = ((char *)f) + (f->atoms*m->atomsize);
159                } else {
160                        mem = f;
161                        m->free = m->free->next;
162                }
163                return mem;
164        } else {
165                b = g_malloc(m->blocksize * m->atomsize);
166                g_ptr_array_add(m->blocks, b);
167                f = (MemChunkFreeNode *)&b[m->atomsize];
168                f->atoms = m->blocksize-1;
169                f->next = NULL;
170                m->free = f;
171                return b;
172        }
173}
174
175void *e_memchunk_alloc0(EMemChunk *m)
176{
177        void *mem;
178
179        mem = e_memchunk_alloc(m);
180        memset(mem, 0, m->atomsize);
181
182        return mem;
183}
184
185/**
186 * e_memchunk_free:
187 * @m:
188 * @mem: Address of atom to free.
189 *
190 * Free a single atom back to the free pool of atoms in the given
191 * memchunk.
192 **/
193void
194e_memchunk_free(MemChunk *m, void *mem)
195{
196        MemChunkFreeNode *f;
197
198        /* put the location back in the free list.  If we knew if the preceeding or following
199           cells were free, we could merge the free nodes, but it doesn't really add much */
200        f = mem;
201        f->next = m->free;
202        m->free = f;
203        f->atoms = 1;
204
205        /* we could store the free list sorted - we could then do the above, and also
206           probably improve the locality of reference properties for the allocator */
207        /* and it would simplify some other algorithms at that, but slow this one down
208           significantly */
209}
210
211/**
212 * e_memchunk_empty:
213 * @m:
214 *
215 * Clean out the memchunk buffers.  Marks all allocated memory as free blocks,
216 * but does not give it back to the system.  Can be used if the memchunk
217 * is to be used repeatedly.
218 **/
219void
220e_memchunk_empty(MemChunk *m)
221{
222        int i;
223        MemChunkFreeNode *f, *h = NULL;
224
225        for (i=0;i<m->blocks->len;i++) {
226                f = (MemChunkFreeNode *)m->blocks->pdata[i];
227                f->atoms = m->blocksize;
228                f->next = h;
229                h = f;
230        }
231        m->free = h;
232}
233
234struct _cleaninfo {
235        struct _cleaninfo *next;
236        char *base;
237        int count;
238        int size;               /* just so tree_search has it, sigh */
239};
240
241static int tree_compare(struct _cleaninfo *a, struct _cleaninfo *b)
242{
243        if (a->base < b->base)
244                return -1;
245        else if (a->base > b->base)
246                return 1;
247        return 0;
248}
249
250static int tree_search(struct _cleaninfo *a, char *mem)
251{
252        if (a->base <= mem) {
253                if (mem < &a->base[a->size])
254                        return 0;
255                return 1;
256        }
257        return -1;
258}
259
260/**
261 * e_memchunk_clean:
262 * @m:
263 *
264 * Scan all empty blocks and check for blocks which can be free'd
265 * back to the system.
266 *
267 * This routine may take a while to run if there are many allocated
268 * memory blocks (if the total number of allocations is many times
269 * greater than atomcount).
270 **/
271void
272e_memchunk_clean(MemChunk *m)
273{
274        GTree *tree;
275        int i;
276        MemChunkFreeNode *f;
277        struct _cleaninfo *ci, *hi = NULL;
278
279        f = m->free;
280        if (m->blocks->len == 0 || f == NULL)
281                return;
282
283        /* first, setup the tree/list so we can map free block addresses to block addresses */
284        tree = g_tree_new((GCompareFunc)tree_compare);
285        for (i=0;i<m->blocks->len;i++) {
286                ci = alloca(sizeof(*ci));
287                ci->count = 0;
288                ci->base = m->blocks->pdata[i];
289                ci->size = m->blocksize * m->atomsize;
290                g_tree_insert(tree, ci, ci);
291                ci->next = hi;
292                hi = ci;
293        }
294
295        /* now, scan all free nodes, and count them in their tree node */
296        while (f) {
297                ci = g_tree_search(tree, (GSearchFunc)tree_search, f);
298                if (ci) {
299                        ci->count += f->atoms;
300                } else {
301                        g_warning("error, can't find free node in memory block\n");
302                }
303                f = f->next;
304        }
305
306        /* if any nodes are all free, free & unlink them */
307        ci = hi;
308        while (ci) {
309                if (ci->count == m->blocksize) {
310                        MemChunkFreeNode *prev = NULL;
311                       
312                        f = m->free;
313                        while (f) {
314                                if (tree_search (ci, (void *) f) == 0) {
315                                        /* prune this node from our free-node list */
316                                        if (prev)
317                                                prev->next = f->next;
318                                        else
319                                                m->free = f->next;
320                                } else {
321                                        prev = f;
322                                }
323                               
324                                f = f->next;
325                        }
326                       
327                        g_ptr_array_remove_fast(m->blocks, ci->base);
328                        g_free(ci->base);
329                }
330                ci = ci->next;
331        }
332
333        g_tree_destroy(tree);
334}
335
336/**
337 * e_memchunk_destroy:
338 * @m:
339 *
340 * Free the memchunk header, and all associated memory.
341 **/
342void
343e_memchunk_destroy(MemChunk *m)
344{
345        int i;
346
347        if (m == NULL)
348                return;
349
350        for (i=0;i<m->blocks->len;i++)
351                g_free(m->blocks->pdata[i]);
352        g_ptr_array_free(m->blocks, TRUE);
353        g_free(m);
354}
355
356typedef struct _MemPoolNode {
357        struct _MemPoolNode *next;
358
359        int free;
360        char data[1];
361} MemPoolNode;
362
363typedef struct _MemPoolThresholdNode {
364        struct _MemPoolThresholdNode *next;
365        char data[1];
366} MemPoolThresholdNode;
367
368typedef struct _EMemPool {
369        int blocksize;
370        int threshold;
371        unsigned int align;
372        struct _MemPoolNode *blocks;
373        struct _MemPoolThresholdNode *threshold_blocks;
374} MemPool;
375
376/* a pool of mempool header blocks */
377static MemChunk *mempool_memchunk;
378#ifdef G_THREADS_ENABLED
379static GStaticMutex mempool_mutex = G_STATIC_MUTEX_INIT;
380#endif
381
382/**
383 * e_mempool_new:
384 * @blocksize: The base blocksize to use for all system alocations.
385 * @threshold: If the allocation exceeds the threshold, then it is
386 * allocated separately and stored in a separate list.
387 * @flags: Alignment options: E_MEMPOOL_ALIGN_STRUCT uses native
388 * struct alignment, E_MEMPOOL_ALIGN_WORD aligns to 16 bits (2 bytes),
389 * and E_MEMPOOL_ALIGN_BYTE aligns to the nearest byte.  The default
390 * is to align to native structures.
391 *
392 * Create a new mempool header.  Mempools can be used to efficiently
393 * allocate data which can then be freed as a whole.
394 *
395 * Mempools can also be used to efficiently allocate arbitrarily
396 * aligned data (such as strings) without incurring the space overhead
397 * of aligning each allocation (which is not required for strings).
398 *
399 * However, each allocation cannot be freed individually, only all
400 * or nothing.
401 *
402 * Return value:
403 **/
404MemPool *e_mempool_new(int blocksize, int threshold, EMemPoolFlags flags)
405{
406        MemPool *pool;
407
408#ifdef G_THREADS_ENABLED
409        g_static_mutex_lock(&mempool_mutex);
410#endif
411        if (mempool_memchunk == NULL) {
412                mempool_memchunk = e_memchunk_new(8, sizeof(MemPool));
413        }
414        pool = e_memchunk_alloc(mempool_memchunk);
415#ifdef G_THREADS_ENABLED
416        g_static_mutex_unlock(&mempool_mutex);
417#endif
418        if (threshold >= blocksize)
419                threshold = blocksize * 2 / 3;
420        pool->blocksize = blocksize;
421        pool->threshold = threshold;
422        pool->blocks = NULL;
423        pool->threshold_blocks = NULL;
424
425        switch (flags & E_MEMPOOL_ALIGN_MASK) {
426        case E_MEMPOOL_ALIGN_STRUCT:
427        default:
428                pool->align = STRUCT_ALIGN-1;
429                break;
430        case E_MEMPOOL_ALIGN_WORD:
431                pool->align = 2-1;
432                break;
433        case E_MEMPOOL_ALIGN_BYTE:
434                pool->align = 1-1;
435        }
436        return pool;
437}
438
439/**
440 * e_mempool_alloc:
441 * @pool:
442 * @size:
443 *
444 * Allocate a new data block in the mempool.  Size will
445 * be rounded up to the mempool's alignment restrictions
446 * before being used.
447 **/
448void *e_mempool_alloc(MemPool *pool, register int size)
449{
450        size = (size + pool->align) & (~(pool->align));
451        if (size>=pool->threshold) {
452                MemPoolThresholdNode *n;
453
454                n = g_malloc(sizeof(*n) - sizeof(char) + size);
455                n->next = pool->threshold_blocks;
456                pool->threshold_blocks = n;
457                return &n->data[0];
458        } else {
459                register MemPoolNode *n;
460
461                n = pool->blocks;
462                if (n && n->free >= size) {
463                        n->free -= size;
464                        return &n->data[n->free];
465                }
466
467                /* maybe we could do some sort of the free blocks based on size, but
468                   it doubt its worth it at all */
469
470                n = g_malloc(sizeof(*n) - sizeof(char) + pool->blocksize);
471                n->next = pool->blocks;
472                pool->blocks = n;
473                n->free = pool->blocksize - size;
474                return &n->data[n->free];
475        }
476}
477
478char *e_mempool_strdup(EMemPool *pool, const char *str)
479{
480        char *out;
481
482        out = e_mempool_alloc(pool, strlen(str)+1);
483        strcpy(out, str);
484
485        return out;
486}
487
488/**
489 * e_mempool_flush:
490 * @pool:
491 * @freeall: Free all system allocated blocks as well.
492 *
493 * Flush used memory and mark allocated blocks as free.
494 *
495 * If @freeall is #TRUE, then all allocated blocks are free'd
496 * as well.  Otherwise only blocks above the threshold are
497 * actually freed, and the others are simply marked as empty.
498 **/
499void e_mempool_flush(MemPool *pool, int freeall)
500{
501        MemPoolThresholdNode *tn, *tw;
502        MemPoolNode *pw, *pn;
503
504        tw = pool->threshold_blocks;
505        while (tw) {
506                tn = tw->next;
507                g_free(tw);
508                tw = tn;
509        }
510        pool->threshold_blocks = NULL;
511
512        if (freeall) {
513                pw = pool->blocks;
514                while (pw) {
515                        pn = pw->next;
516                        g_free(pw);
517                        pw = pn;
518                }
519                pool->blocks = NULL;
520        } else {
521                pw = pool->blocks;
522                while (pw) {
523                        pw->free = pool->blocksize;
524                        pw = pw->next;
525                }
526        }
527}
528
529/**
530 * e_mempool_destroy:
531 * @pool:
532 *
533 * Free all memory associated with a mempool.
534 **/
535void e_mempool_destroy(MemPool *pool)
536{
537        if (pool) {
538                e_mempool_flush(pool, 1);
539                e_memchunk_free(mempool_memchunk, pool);
540        }
541}
542
543
544/*
545  string array classes
546*/
547
548#define STRV_UNPACKED ((unsigned char)(~0))
549
550struct _EStrv {
551        unsigned char length;   /* how many entries we have (or the token STRV_UNPACKED) */
552        char data[1];           /* data follows */
553};
554
555struct _s_strv_string {
556        char *string;           /* the string to output */
557        char *free;             /* a string to free, if we referenced it */
558};
559
560struct _e_strvunpacked {
561        unsigned char type;     /* we overload last to indicate this is unpacked */
562        MemPool *pool;          /* pool of memory for strings */
563        struct _EStrv *source;  /* if we were converted from a packed one, keep the source around for a while */
564        unsigned int length;
565        struct _s_strv_string strings[1]; /* the string array data follows */
566};
567
568/**
569 * e_strv_new:
570 * @size: The number of elements in the strv.  Currently this is limited
571 * to 254 elements.
572 *
573 * Create a new strv (string array) header.  strv's can be used to
574 * create and work with arrays of strings that can then be compressed
575 * into a space-efficient static structure.  This is useful
576 * where a number of strings are to be stored for lookup, and not
577 * generally edited afterwards.
578 *
579 * The size limit is currently 254 elements.  This will probably not
580 * change as arrays of this size suffer significant performance
581 * penalties when looking up strings with high indices.
582 *
583 * Return value:
584 **/
585struct _EStrv *
586e_strv_new(int size)
587{
588        struct _e_strvunpacked *s;
589
590        g_assert(size<255);
591
592        s = g_malloc(sizeof(*s) + (size-1)*sizeof(s->strings[0]));
593        s(printf("new strv=%p, size = %d bytes\n", s, sizeof(*s) + (size-1)*sizeof(char *)));
594        s->type = STRV_UNPACKED;
595        s->pool = NULL;
596        s->length = size;
597        s->source = NULL;
598        memset(s->strings, 0, size*sizeof(s->strings[0]));
599
600        return (struct _EStrv *)s;
601}
602
603static struct _e_strvunpacked *
604strv_unpack(struct _EStrv *strv)
605{
606        struct _e_strvunpacked *s;
607        register char *p;
608        int i;
609
610        s(printf("unpacking\n"));
611
612        s = (struct _e_strvunpacked *)e_strv_new(strv->length);
613        p = strv->data;
614        for (i=0;i<s->length;i++) {
615                if (i>0)
616                        while (*p++)
617                                ;
618                s->strings[i].string = p;
619        }
620        s->source = strv;
621        s->type = STRV_UNPACKED;
622
623        return s;
624}
625
626/**
627 * e_strv_set_ref:
628 * @strv:
629 * @index:
630 * @str:
631 *
632 * Set a string array element by reference.  The string
633 * is not copied until the array is packed.
634 *
635 * If @strv has been packed, then it is unpacked ready
636 * for more inserts, and should be packed again once finished with.
637 * The memory used by the original @strv is not freed until
638 * the new strv is packed, or freed itself.
639 *
640 * Return value: A new EStrv if the strv has already
641 * been packed, otherwise @strv.
642 **/
643struct _EStrv *
644e_strv_set_ref(struct _EStrv *strv, int index, char *str)
645{
646        struct _e_strvunpacked *s;
647
648        s(printf("set ref %d '%s'\nawkmeharder: %s\n ", index, str, str));
649
650        if (strv->length != STRV_UNPACKED)
651                s = strv_unpack(strv);
652        else
653                s = (struct _e_strvunpacked *)strv;
654
655        g_assert(index>=0 && index < s->length);
656
657        s->strings[index].string = str;
658
659        return (struct _EStrv *)s;
660}
661
662/**
663 * e_strv_set_ref_free:
664 * @strv:
665 * @index:
666 * @str:
667 *
668 * Set a string by reference, similar to set_ref, but also
669 * free the string when finished with it.  The string
670 * is not copied until the strv is packed, and not at
671 * all if the index is overwritten.
672 *
673 * Return value: @strv if already unpacked, otherwise an packed
674 * EStrv.
675 **/
676struct _EStrv *
677e_strv_set_ref_free(struct _EStrv *strv, int index, char *str)
678{
679        struct _e_strvunpacked *s;
680
681        s(printf("set ref %d '%s'\nawkmeevenharder: %s\n ", index, str, str));
682
683        if (strv->length != STRV_UNPACKED)
684                s = strv_unpack(strv);
685        else
686                s = (struct _e_strvunpacked *)strv;
687
688        g_assert(index>=0 && index < s->length);
689
690        s->strings[index].string = str;
691        if (s->strings[index].free)
692                g_free(s->strings[index].free);
693        s->strings[index].free = str;
694
695        return (struct _EStrv *)s;
696}
697
698/**
699 * e_strv_set:
700 * @strv:
701 * @index:
702 * @str:
703 *
704 * Set a string array reference.  The string @str is copied
705 * into the string array at location @index.
706 *
707 * If @strv has been packed, then it is unpacked ready
708 * for more inserts, and should be packed again once finished with.
709 *
710 * Return value: A new EStrv if the strv has already
711 * been packed, otherwise @strv.
712 **/
713struct _EStrv *
714e_strv_set(struct _EStrv *strv, int index, const char *str)
715{
716        struct _e_strvunpacked *s;
717
718        s(printf("set %d '%s'\n", index, str));
719
720        if (strv->length != STRV_UNPACKED)
721                s = strv_unpack(strv);
722        else
723                s = (struct _e_strvunpacked *)strv;
724
725        g_assert(index>=0 && index < s->length);
726
727        if (s->pool == NULL)
728                s->pool = e_mempool_new(1024, 512, E_MEMPOOL_ALIGN_BYTE);
729
730        s->strings[index].string = e_mempool_alloc(s->pool, strlen(str)+1);
731        strcpy(s->strings[index].string, str);
732
733        return (struct _EStrv *)s;
734}
735
736/**
737 * e_strv_pack:
738 * @strv:
739 *
740 * Pack the @strv into a space efficient structure for later lookup.
741 *
742 * All strings are packed into a single allocated block, separated
743 * by single \0 characters, together with a count byte.
744 *
745 * Return value:
746 **/
747struct _EStrv *
748e_strv_pack(struct _EStrv *strv)
749{
750        struct _e_strvunpacked *s;
751        int len, i;
752        register char *src, *dst;
753
754        if (strv->length == STRV_UNPACKED) {
755                s = (struct _e_strvunpacked *)strv;
756
757                s(printf("packing string\n"));
758
759                len = 0;
760                for (i=0;i<s->length;i++)
761                        len += s->strings[i].string?strlen(s->strings[i].string)+1:1;
762
763                strv = g_malloc(sizeof(*strv) + len);
764                s(printf("allocating strv=%p, size = %d\n", strv, sizeof(*strv)+len));
765                strv->length = s->length;
766                dst = strv->data;
767                for (i=0;i<s->length;i++) {
768                        if ((src = s->strings[i].string)) {
769                                while ((*dst++ = *src++))
770                                        ;
771                        } else {
772                                *dst++ = 0;
773                        }
774                }
775                e_strv_destroy((struct _EStrv *)s);
776        }
777        return strv;
778}
779
780/**
781 * e_strv_get:
782 * @strv:
783 * @index:
784 *
785 * Retrieve a string by index.  This function works
786 * identically on both packed and unpacked strv's, although
787 * may be much slower on a packed strv.
788 *
789 * Return value:
790 **/
791char *
792e_strv_get(struct _EStrv *strv, int index)
793{
794        struct _e_strvunpacked *s;
795        char *p;
796
797        if (strv->length != STRV_UNPACKED) {
798                g_assert(index>=0 && index < strv->length);
799                p = strv->data;
800                while (index > 0) {
801                        while (*p++ != 0)
802                                ;
803                        index--;
804                }
805                return p;
806        } else {
807                s = (struct _e_strvunpacked *)strv;
808                g_assert(index>=0 && index < s->length);
809                return s->strings[index].string?s->strings[index].string:"";
810        }
811}
812
813/**
814 * e_strv_destroy:
815 * @strv:
816 *
817 * Free a strv and all associated memory.  Works on packed
818 * or unpacked strv's.
819 **/
820void
821e_strv_destroy(struct _EStrv *strv)
822{
823        struct _e_strvunpacked *s;
824        int i;
825
826        s(printf("freeing strv\n"));
827
828        if (strv->length == STRV_UNPACKED) {
829                s = (struct _e_strvunpacked *)strv;
830                if (s->pool)
831                        e_mempool_destroy(s->pool);
832                if (s->source)
833                        e_strv_destroy(s->source);
834                for (i=0;i<s->length;i++) {
835                        if (s->strings[i].free)
836                                g_free(s->strings[i].free);
837                }
838        }
839
840        s(printf("freeing strv=%p\n", strv));
841
842        g_free(strv);
843}
844
845
846
847/* string pool stuff */
848
849/* TODO:
850    garbage collection, using the following technique:
851      Create a memchunk for each possible size of poolv, and allocate every poolv from those
852      To garbage collect, scan all memchunk internally, ignoring any free areas (or mark each
853        poolv when freeing it - set length 0?), and find out which strings are not anywhere,
854        then free them.
855
856    OR:
857       Just keep a refcount in the hashtable, instead of duplicating the key pointer.
858
859   either would also require a free for the mempool, so ignore it for now */
860
861/*#define POOLV_REFCNT*/ /* Define to enable refcounting code that does
862                        automatic garbage collection of unused strings */
863
864static GHashTable *poolv_pool = NULL;
865static EMemPool *poolv_mempool = NULL;
866
867#ifdef MALLOC_CHECK
868static GPtrArray *poolv_table = NULL;
869#endif
870
871#ifdef PROFILE_POOLV
872static gulong poolv_hits = 0;
873static gulong poolv_misses = 0;
874static unsigned long poolv_mem, poolv_count;
875#endif
876
877#ifdef G_THREADS_ENABLED
878static GStaticMutex poolv_mutex = G_STATIC_MUTEX_INIT;
879#endif
880
881struct _EPoolv {
882        unsigned char length;
883        char *s[1];
884};
885
886/**
887 * e_poolv_new: @size: The number of elements in the poolv, maximum of 254 elements.
888 *
889 * create a new poolv (string vector which shares a global string
890 * pool).  poolv's can be used to work with arrays of strings which
891 * save memory by eliminating duplicated allocations of the same
892 * string.
893 *
894 * this is useful when you have a log of read-only strings that do not
895 * go away and are duplicated a lot (such as email headers).
896 *
897 * we should probably in the future ref count the strings contained in
898 * the hash table, but for now let's not.
899 *
900 * Return value: new pooled string vector
901 **/
902EPoolv *
903e_poolv_new(unsigned int size)
904{
905        EPoolv *poolv;
906
907        g_assert(size < 255);
908
909        poolv = g_malloc0(sizeof (*poolv) + (size - 1) * sizeof (char *));
910        poolv->length = size;
911
912#ifdef G_THREADS_ENABLED
913        g_static_mutex_lock(&poolv_mutex);
914#endif
915        if (!poolv_pool)
916                poolv_pool = g_hash_table_new(g_str_hash, g_str_equal);
917
918        if (!poolv_mempool)
919                poolv_mempool = e_mempool_new(32 * 1024, 512, E_MEMPOOL_ALIGN_BYTE);
920
921#ifdef MALLOC_CHECK
922        {
923                int i;
924
925                if (poolv_table == NULL)
926                        poolv_table = g_ptr_array_new();
927
928                for (i=0;i<poolv_table->len;i++)
929                        MPROBE(poolv_table->pdata[i]);
930
931                g_ptr_array_add(poolv_table, poolv);
932        }
933#endif
934
935#ifdef G_THREADS_ENABLED
936        g_static_mutex_unlock(&poolv_mutex);
937#endif
938
939        p(printf("new poolv=%p\tsize=%d\n", poolv, sizeof(*poolv) + (size-1)*sizeof(char *)));
940
941#ifdef PROFILE_POOLV
942        poolv_count++;
943#endif
944        return poolv;
945}
946
947/**
948 * e_poolv_cpy:
949 * @dest: destination pooled string vector
950 * @src: source pooled string vector
951 *
952 * Copy the contents of a pooled string vector
953 *
954 * Return value: @dest, which may be re-allocated if the strings
955 * are different lengths.
956 **/
957EPoolv *
958e_poolv_cpy(EPoolv *dest, const EPoolv *src)
959{
960#ifdef POOLV_REFCNT
961        int i;
962        unsigned int ref;
963        char *key;
964#endif
965
966        p2(g_return_val_if_fail (dest != NULL, NULL));
967        p2(g_return_val_if_fail (src != NULL, NULL));
968
969        MPROBE(dest);
970        MPROBE(src);
971
972        if (dest->length != src->length) {
973                e_poolv_destroy(dest);
974                dest = e_poolv_new(src->length);
975        }
976
977#ifdef POOLV_REFCNT
978#ifdef G_THREADS_ENABLED
979        g_static_mutex_lock(&poolv_mutex);
980#endif
981        /* ref new copies */
982        for (i=0;i<src->length;i++) {
983                if (src->s[i]) {
984                        if (g_hash_table_lookup_extended(poolv_pool, src->s[i], (void **)&key, (void **)&ref)) {
985                                g_hash_table_insert(poolv_pool, key, (void *)(ref+1));
986                        } else {
987                                g_assert_not_reached();
988                        }
989                }
990        }
991
992        /* unref the old ones */
993        for (i=0;i<dest->length;i++) {
994                if (dest->s[i]) {
995                        if (g_hash_table_lookup_extended(poolv_pool, dest->s[i], (void **)&key, (void **)&ref)) {
996                                /* if ref == 1 free it */
997                                g_assert(ref > 0);
998                                g_hash_table_insert(poolv_pool, key, (void *)(ref-1));
999                        } else {
1000                                g_assert_not_reached();
1001                        }
1002                }
1003        }
1004#ifdef G_THREADS_ENABLED
1005        g_static_mutex_unlock(&poolv_mutex);
1006#endif
1007#endif
1008
1009        memcpy(dest->s, src->s, src->length * sizeof (char *));
1010
1011        return dest;
1012}
1013
1014#ifdef PROFILE_POOLV
1015static void
1016poolv_profile_update (void)
1017{
1018        static time_t last_time = 0;
1019        time_t new_time;
1020
1021        new_time = time (NULL);
1022        if (new_time - last_time < 5)
1023                return;
1024
1025        printf("poolv profile: %lu hits, %lu misses: %d%% hit rate, memory: %lu, instances: %lu\n",
1026               poolv_hits, poolv_misses,
1027               (int)(100.0 * ((double) poolv_hits / (double) (poolv_hits + poolv_misses))),
1028               poolv_mem, poolv_count);
1029
1030        last_time = new_time;
1031}
1032#endif
1033
1034/**
1035 * e_poolv_set:
1036 * @poolv: pooled string vector
1037 * @index: index in vector of string
1038 * @str: string to set
1039 * @freeit: whether the caller is releasing its reference to the
1040 * string
1041 *
1042 * Set a string vector reference.  If the caller will no longer be
1043 * referencing the string, freeit should be TRUE.  Otherwise, this
1044 * will duplicate the string if it is not found in the pool.
1045 *
1046 * Return value: @poolv
1047 **/
1048EPoolv *
1049e_poolv_set (EPoolv *poolv, int index, char *str, int freeit)
1050{
1051#ifdef POOLV_REFCNT
1052        unsigned int ref;
1053        char *key;
1054#endif
1055
1056        p2(g_return_val_if_fail (poolv != NULL, NULL));
1057
1058        g_assert(index >=0 && index < poolv->length);
1059
1060        MPROBE(poolv);
1061
1062        p(printf("setting %d `%s'\n", index, str));
1063
1064        if (!str) {
1065#ifdef POOLV_REFCNT
1066                if (poolv->s[index]) {
1067                        if (g_hash_table_lookup_extended(poolv_pool, poolv->s[index], (void **)&key, (void **)&ref)) {
1068                                g_assert(ref > 0);
1069                                g_hash_table_insert(poolv_pool, key, (void *)(ref-1));
1070                        } else {
1071                                g_assert_not_reached();
1072                        }
1073                }
1074#endif
1075                poolv->s[index] = NULL;
1076                return poolv;
1077        }
1078
1079#ifdef G_THREADS_ENABLED
1080        g_static_mutex_lock(&poolv_mutex);
1081#endif
1082
1083#ifdef POOLV_REFCNT
1084        if (g_hash_table_lookup_extended(poolv_pool, str, (void **)&key, (void **)&ref)) {
1085                g_hash_table_insert(poolv_pool, key, (void *)(ref+1));
1086                poolv->s[index] = key;
1087# ifdef PROFILE_POOLV
1088                poolv_hits++;
1089                poolv_profile_update ();
1090# endif
1091        } else {
1092# ifdef PROFILE_POOLV
1093                poolv_misses++;
1094                poolv_mem += strlen(str);
1095                poolv_profile_update ();
1096# endif
1097                poolv->s[index] = e_mempool_strdup(poolv_mempool, str);
1098                g_hash_table_insert(poolv_pool, poolv->s[index], (void *)1);
1099        }
1100
1101#else  /* !POOLV_REFCNT */
1102        if ((poolv->s[index] = g_hash_table_lookup(poolv_pool, str)) != NULL) {
1103# ifdef PROFILE_POOLV
1104                poolv_hits++;
1105                poolv_profile_update ();
1106# endif
1107        } else {
1108# ifdef PROFILE_POOLV
1109                poolv_misses++;
1110                poolv_mem += strlen(str);
1111                poolv_profile_update ();
1112# endif
1113                poolv->s[index] = e_mempool_strdup(poolv_mempool, str);
1114                g_hash_table_insert(poolv_pool, poolv->s[index], poolv->s[index]);
1115        }
1116#endif /* !POOLV_REFCNT */
1117
1118#ifdef G_THREADS_ENABLED
1119        g_static_mutex_unlock(&poolv_mutex);
1120#endif
1121
1122        if (freeit)
1123                g_free(str);
1124
1125        return poolv;
1126}
1127
1128/**
1129 * e_poolv_get:
1130 * @poolv: pooled string vector
1131 * @index: index in vector of string
1132 *
1133 * Retrieve a string by index.  This could possibly just be a macro.
1134 *
1135 * Since the pool is never freed, this string does not need to be
1136 * duplicated, but should not be modified.
1137 *
1138 * Return value: string at that index.
1139 **/
1140const char *
1141e_poolv_get(EPoolv *poolv, int index)
1142{
1143        g_assert(poolv != NULL);
1144        g_assert(index>= 0 && index < poolv->length);
1145
1146        MPROBE(poolv);
1147
1148        p(printf("get %d = `%s'\n", index, poolv->s[index]));
1149
1150        return poolv->s[index]?poolv->s[index]:"";
1151}
1152
1153/**
1154 * e_poolv_destroy:
1155 * @poolv: pooled string vector to free
1156 *
1157 * Free a pooled string vector.  This doesn't free the strings from
1158 * the vector, however.
1159 **/
1160void
1161e_poolv_destroy(EPoolv *poolv)
1162{
1163#ifdef POOLV_REFCNT
1164        int i;
1165        unsigned int ref;
1166        char *key;
1167
1168        MPROBE(poolv);
1169
1170#ifdef G_THREADS_ENABLED
1171        g_static_mutex_lock(&poolv_mutex);
1172#endif
1173
1174#ifdef MALLOC_CHECK
1175        for (i=0;i<poolv_table->len;i++)
1176                MPROBE(poolv_table->pdata[i]);
1177
1178        g_ptr_array_remove_fast(poolv_table, poolv);
1179#endif
1180
1181        for (i=0;i<poolv->length;i++) {
1182                if (poolv->s[i]) {
1183                        if (g_hash_table_lookup_extended(poolv_pool, poolv->s[i], (void **)&key, (void **)&ref)) {
1184                                /* if ref == 1 free it */
1185                                g_assert(ref > 0);
1186                                g_hash_table_insert(poolv_pool, key, (void *)(ref-1));
1187                        } else {
1188                                g_assert_not_reached();
1189                        }
1190                }
1191        }
1192#ifdef G_THREADS_ENABLED
1193        g_static_mutex_unlock(&poolv_mutex);
1194#endif
1195#endif
1196
1197#ifdef PROFILE_POOLV
1198        poolv_count++;
1199#endif
1200        g_free(poolv);
1201}
1202
1203#if 0
1204
1205#define CHUNK_SIZE (20)
1206#define CHUNK_COUNT (32)
1207
1208#define s(x)
1209
1210main()
1211{
1212        int i;
1213        MemChunk *mc;
1214        void *mem, *last;
1215        GMemChunk *gmc;
1216        struct _EStrv *s;
1217
1218        s = strv_new(8);
1219        s = strv_set(s, 1, "Testing 1");
1220        s = strv_set(s, 2, "Testing 2");
1221        s = strv_set(s, 3, "Testing 3");
1222        s = strv_set(s, 4, "Testing 4");
1223        s = strv_set(s, 5, "Testing 5");
1224        s = strv_set(s, 6, "Testing 7");
1225
1226        for (i=0;i<8;i++) {
1227                printf("s[%d] = %s\n", i, strv_get(s, i));
1228        }
1229
1230        s(sleep(5));
1231
1232        printf("packing ...\n");
1233        s = strv_pack(s);
1234
1235        for (i=0;i<8;i++) {
1236                printf("s[%d] = %s\n", i, strv_get(s, i));
1237        }
1238
1239        printf("setting ...\n");
1240
1241        s = strv_set_ref(s, 1, "Testing 1 x");
1242
1243        for (i=0;i<8;i++) {
1244                printf("s[%d] = %s\n", i, strv_get(s, i));
1245        }
1246
1247        printf("packing ...\n");
1248        s = strv_pack(s);
1249
1250        for (i=0;i<8;i++) {
1251                printf("s[%d] = %s\n", i, strv_get(s, i));
1252        }
1253
1254        strv_free(s);
1255
1256#if 0
1257        time_start("Using memchunks");
1258        mc = memchunk_new(CHUNK_COUNT, CHUNK_SIZE);
1259        for (i=0;i<1000000;i++) {
1260                mem = memchunk_alloc(mc);
1261                if ((i & 1) == 0)
1262                        memchunk_free(mc, mem);
1263        }
1264        s(sleep(10));
1265        memchunk_destroy(mc);
1266        time_end("allocating 1000000 memchunks, freeing 500k");
1267
1268        time_start("Using gmemchunks");
1269        gmc = g_mem_chunk_new("memchunk", CHUNK_SIZE, CHUNK_SIZE*CHUNK_COUNT, G_ALLOC_AND_FREE);
1270        for (i=0;i<1000000;i++) {
1271                mem = g_mem_chunk_alloc(gmc);
1272                if ((i & 1) == 0)
1273                        g_mem_chunk_free(gmc, mem);
1274        }
1275        s(sleep(10));
1276        g_mem_chunk_destroy(gmc);
1277        time_end("allocating 1000000 gmemchunks, freeing 500k");
1278
1279        time_start("Using memchunks");
1280        mc = memchunk_new(CHUNK_COUNT, CHUNK_SIZE);
1281        for (i=0;i<1000000;i++) {
1282                mem = memchunk_alloc(mc);
1283        }
1284        s(sleep(10));
1285        memchunk_destroy(mc);
1286        time_end("allocating 1000000 memchunks");
1287
1288        time_start("Using gmemchunks");
1289        gmc = g_mem_chunk_new("memchunk", CHUNK_SIZE, CHUNK_COUNT*CHUNK_SIZE, G_ALLOC_ONLY);
1290        for (i=0;i<1000000;i++) {
1291                mem = g_mem_chunk_alloc(gmc);
1292        }
1293        s(sleep(10));
1294        g_mem_chunk_destroy(gmc);
1295        time_end("allocating 1000000 gmemchunks");
1296
1297        time_start("Using malloc");
1298        for (i=0;i<1000000;i++) {
1299                malloc(CHUNK_SIZE);
1300        }
1301        time_end("allocating 1000000 malloc");
1302#endif
1303       
1304}
1305
1306#endif
Note: See TracBrowser for help on using the repository browser.