D-Bus  1.5.8
dbus-mempool.c
00001 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
00002 /* dbus-mempool.h Memory pools
00003  * 
00004  * Copyright (C) 2002, 2003  Red Hat, Inc.
00005  *
00006  * Licensed under the Academic Free License version 2.1
00007  * 
00008  * This program is free software; you can redistribute it and/or modify
00009  * it under the terms of the GNU General Public License as published by
00010  * the Free Software Foundation; either version 2 of the License, or
00011  * (at your option) any later version.
00012  *
00013  * This program is distributed in the hope that it will be useful,
00014  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00015  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00016  * GNU General Public License for more details.
00017  * 
00018  * You should have received a copy of the GNU General Public License
00019  * along with this program; if not, write to the Free Software
00020  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
00021  *
00022  */
00023 
00024 #include <config.h>
00025 #include "dbus-mempool.h"
00026 #include "dbus-internals.h"
00027 
00053 typedef struct DBusFreedElement DBusFreedElement;
00054 
00060 struct DBusFreedElement
00061 {
00062   DBusFreedElement *next; 
00063 };
00064 
00069 #define ELEMENT_PADDING 4
00070 
00075 typedef struct DBusMemBlock DBusMemBlock;
00076 
00081 struct DBusMemBlock
00082 {
00083   DBusMemBlock *next;  
00088   /* this is a long so that "elements" is aligned */
00089   long used_so_far;     
00091   unsigned char elements[ELEMENT_PADDING]; 
00092 };
00093 
00097 struct DBusMemPool
00098 {
00099   int element_size;                
00100   int block_size;                  
00101   unsigned int zero_elements : 1;  
00103   DBusFreedElement *free_elements; 
00104   DBusMemBlock *blocks;            
00105   int allocated_elements;          
00106 };
00107 
00136 DBusMemPool*
00137 _dbus_mem_pool_new (int element_size,
00138                     dbus_bool_t zero_elements)
00139 {
00140   DBusMemPool *pool;
00141 
00142   pool = dbus_new0 (DBusMemPool, 1);
00143   if (pool == NULL)
00144     return NULL;
00145 
00146   /* Make the element size at least 8 bytes. */
00147   if (element_size < 8)
00148     element_size = 8;
00149   
00150   /* these assertions are equivalent but the first is more clear
00151    * to programmers that see it fail.
00152    */
00153   _dbus_assert (element_size >= (int) sizeof (void*));
00154   _dbus_assert (element_size >= (int) sizeof (DBusFreedElement));
00155 
00156   /* align the element size to a pointer boundary so we won't get bus
00157    * errors under other architectures.  
00158    */
00159   pool->element_size = _DBUS_ALIGN_VALUE (element_size, sizeof (void *));
00160 
00161   pool->zero_elements = zero_elements != FALSE;
00162 
00163   pool->allocated_elements = 0;
00164   
00165   /* pick a size for the first block; it increases
00166    * for each block we need to allocate. This is
00167    * actually half the initial block size
00168    * since _dbus_mem_pool_alloc() unconditionally
00169    * doubles it prior to creating a new block.  */
00170   pool->block_size = pool->element_size * 8;
00171 
00172   _dbus_assert ((pool->block_size %
00173                  pool->element_size) == 0);
00174   
00175   return pool;
00176 }
00177 
00183 void
00184 _dbus_mem_pool_free (DBusMemPool *pool)
00185 {
00186   DBusMemBlock *block;
00187 
00188   block = pool->blocks;
00189   while (block != NULL)
00190     {
00191       DBusMemBlock *next = block->next;
00192 
00193       dbus_free (block);
00194 
00195       block = next;
00196     }
00197 
00198   dbus_free (pool);
00199 }
00200 
00208 void*
00209 _dbus_mem_pool_alloc (DBusMemPool *pool)
00210 {
00211 #ifdef DBUS_BUILD_TESTS
00212   if (_dbus_disable_mem_pools ())
00213     {
00214       DBusMemBlock *block;
00215       int alloc_size;
00216       
00217       /* This is obviously really silly, but it's
00218        * debug-mode-only code that is compiled out
00219        * when tests are disabled (_dbus_disable_mem_pools()
00220        * is a constant expression FALSE so this block
00221        * should vanish)
00222        */
00223       
00224       alloc_size = sizeof (DBusMemBlock) - ELEMENT_PADDING +
00225         pool->element_size;
00226       
00227       if (pool->zero_elements)
00228         block = dbus_malloc0 (alloc_size);
00229       else
00230         block = dbus_malloc (alloc_size);
00231 
00232       if (block != NULL)
00233         {
00234           block->next = pool->blocks;
00235           pool->blocks = block;
00236           pool->allocated_elements += 1;
00237 
00238           return (void*) &block->elements[0];
00239         }
00240       else
00241         return NULL;
00242     }
00243   else
00244 #endif
00245     {
00246       if (_dbus_decrement_fail_alloc_counter ())
00247         {
00248           _dbus_verbose (" FAILING mempool alloc\n");
00249           return NULL;
00250         }
00251       else if (pool->free_elements)
00252         {
00253           DBusFreedElement *element = pool->free_elements;
00254 
00255           pool->free_elements = pool->free_elements->next;
00256 
00257           if (pool->zero_elements)
00258             memset (element, '\0', pool->element_size);
00259 
00260           pool->allocated_elements += 1;
00261           
00262           return element;
00263         }
00264       else
00265         {
00266           void *element;
00267       
00268           if (pool->blocks == NULL ||
00269               pool->blocks->used_so_far == pool->block_size)
00270             {
00271               /* Need a new block */
00272               DBusMemBlock *block;
00273               int alloc_size;
00274 #ifdef DBUS_BUILD_TESTS
00275               int saved_counter;
00276 #endif
00277           
00278               if (pool->block_size <= _DBUS_INT_MAX / 4) /* avoid overflow */
00279                 {
00280                   /* use a larger block size for our next block */
00281                   pool->block_size *= 2;
00282                   _dbus_assert ((pool->block_size %
00283                                  pool->element_size) == 0);
00284                 }
00285 
00286               alloc_size = sizeof (DBusMemBlock) - ELEMENT_PADDING + pool->block_size;
00287 
00288 #ifdef DBUS_BUILD_TESTS
00289               /* We save/restore the counter, so that memory pools won't
00290                * cause a given function to have different number of
00291                * allocations on different invocations. i.e.  when testing
00292                * we want consistent alloc patterns. So we skip our
00293                * malloc here for purposes of failed alloc simulation.
00294                */
00295               saved_counter = _dbus_get_fail_alloc_counter ();
00296               _dbus_set_fail_alloc_counter (_DBUS_INT_MAX);
00297 #endif
00298           
00299               if (pool->zero_elements)
00300                 block = dbus_malloc0 (alloc_size);
00301               else
00302                 block = dbus_malloc (alloc_size);
00303 
00304 #ifdef DBUS_BUILD_TESTS
00305               _dbus_set_fail_alloc_counter (saved_counter);
00306               _dbus_assert (saved_counter == _dbus_get_fail_alloc_counter ());
00307 #endif
00308           
00309               if (block == NULL)
00310                 return NULL;
00311 
00312               block->used_so_far = 0;
00313               block->next = pool->blocks;
00314               pool->blocks = block;          
00315             }
00316       
00317           element = &pool->blocks->elements[pool->blocks->used_so_far];
00318           
00319           pool->blocks->used_so_far += pool->element_size;
00320 
00321           pool->allocated_elements += 1;
00322           
00323           return element;
00324         }
00325     }
00326 }
00327 
00336 dbus_bool_t
00337 _dbus_mem_pool_dealloc (DBusMemPool *pool,
00338                         void        *element)
00339 {
00340 #ifdef DBUS_BUILD_TESTS
00341   if (_dbus_disable_mem_pools ())
00342     {
00343       DBusMemBlock *block;
00344       DBusMemBlock *prev;
00345 
00346       /* mmm, fast. ;-) debug-only code, so doesn't matter. */
00347       
00348       prev = NULL;
00349       block = pool->blocks;
00350 
00351       while (block != NULL)
00352         {
00353           if (block->elements == (unsigned char*) element)
00354             {
00355               if (prev)
00356                 prev->next = block->next;
00357               else
00358                 pool->blocks = block->next;
00359               
00360               dbus_free (block);
00361 
00362               _dbus_assert (pool->allocated_elements > 0);
00363               pool->allocated_elements -= 1;
00364               
00365               if (pool->allocated_elements == 0)
00366                 _dbus_assert (pool->blocks == NULL);
00367               
00368               return pool->blocks == NULL;
00369             }
00370           prev = block;
00371           block = block->next;
00372         }
00373       
00374       _dbus_assert_not_reached ("freed nonexistent block");
00375       return FALSE;
00376     }
00377   else
00378 #endif
00379     {
00380       DBusFreedElement *freed;
00381       
00382       freed = element;
00383       freed->next = pool->free_elements;
00384       pool->free_elements = freed;
00385       
00386       _dbus_assert (pool->allocated_elements > 0);
00387       pool->allocated_elements -= 1;
00388       
00389       return pool->allocated_elements == 0;
00390     }
00391 }
00392 
00393 #ifdef DBUS_ENABLE_STATS
00394 void
00395 _dbus_mem_pool_get_stats (DBusMemPool   *pool,
00396                           dbus_uint32_t *in_use_p,
00397                           dbus_uint32_t *in_free_list_p,
00398                           dbus_uint32_t *allocated_p)
00399 {
00400   DBusMemBlock *block;
00401   DBusFreedElement *freed;
00402   dbus_uint32_t in_use = 0;
00403   dbus_uint32_t in_free_list = 0;
00404   dbus_uint32_t allocated = 0;
00405 
00406   if (pool != NULL)
00407     {
00408       in_use = pool->element_size * pool->allocated_elements;
00409 
00410       for (freed = pool->free_elements; freed != NULL; freed = freed->next)
00411         {
00412           in_free_list += pool->element_size;
00413         }
00414 
00415       for (block = pool->blocks; block != NULL; block = block->next)
00416         {
00417           if (block == pool->blocks)
00418             allocated += pool->block_size;
00419           else
00420             allocated += block->used_so_far;
00421         }
00422     }
00423 
00424   if (in_use_p != NULL)
00425     *in_use_p = in_use;
00426 
00427   if (in_free_list_p != NULL)
00428     *in_free_list_p = in_free_list;
00429 
00430   if (allocated_p != NULL)
00431     *allocated_p = allocated;
00432 }
00433 #endif /* DBUS_ENABLE_STATS */
00434 
00437 #ifdef DBUS_BUILD_TESTS
00438 #include "dbus-test.h"
00439 #include <stdio.h>
00440 #include <time.h>
00441 
00442 static void
00443 time_for_size (int size)
00444 {
00445   int i;
00446   int j;
00447   clock_t start;
00448   clock_t end;
00449 #define FREE_ARRAY_SIZE 512
00450 #define N_ITERATIONS FREE_ARRAY_SIZE * 512
00451   void *to_free[FREE_ARRAY_SIZE];
00452   DBusMemPool *pool;
00453 
00454   _dbus_verbose ("Timings for size %d\n", size);
00455   
00456   _dbus_verbose (" malloc\n");
00457   
00458   start = clock ();
00459   
00460   i = 0;
00461   j = 0;
00462   while (i < N_ITERATIONS)
00463     {
00464       to_free[j] = dbus_malloc (size);
00465       _dbus_assert (to_free[j] != NULL); /* in a real app of course this is wrong */
00466 
00467       ++j;
00468 
00469       if (j == FREE_ARRAY_SIZE)
00470         {
00471           j = 0;
00472           while (j < FREE_ARRAY_SIZE)
00473             {
00474               dbus_free (to_free[j]);
00475               ++j;
00476             }
00477 
00478           j = 0;
00479         }
00480       
00481       ++i;
00482     }
00483 
00484   end = clock ();
00485 
00486   _dbus_verbose ("  created/destroyed %d elements in %g seconds\n",
00487                  N_ITERATIONS, (end - start) / (double) CLOCKS_PER_SEC);
00488 
00489 
00490 
00491   _dbus_verbose (" mempools\n");
00492   
00493   start = clock ();
00494 
00495   pool = _dbus_mem_pool_new (size, FALSE);
00496   
00497   i = 0;
00498   j = 0;
00499   while (i < N_ITERATIONS)
00500     {
00501       to_free[j] = _dbus_mem_pool_alloc (pool); 
00502       _dbus_assert (to_free[j] != NULL);  /* in a real app of course this is wrong */
00503 
00504       ++j;
00505 
00506       if (j == FREE_ARRAY_SIZE)
00507         {
00508           j = 0;
00509           while (j < FREE_ARRAY_SIZE)
00510             {
00511               _dbus_mem_pool_dealloc (pool, to_free[j]);
00512               ++j;
00513             }
00514 
00515           j = 0;
00516         }
00517       
00518       ++i;
00519     }
00520 
00521   _dbus_mem_pool_free (pool);
00522   
00523   end = clock ();
00524 
00525   _dbus_verbose ("  created/destroyed %d elements in %g seconds\n",
00526                  N_ITERATIONS, (end - start) / (double) CLOCKS_PER_SEC);
00527 
00528   _dbus_verbose (" zeroed malloc\n");
00529     
00530   start = clock ();
00531   
00532   i = 0;
00533   j = 0;
00534   while (i < N_ITERATIONS)
00535     {
00536       to_free[j] = dbus_malloc0 (size);
00537       _dbus_assert (to_free[j] != NULL); /* in a real app of course this is wrong */
00538 
00539       ++j;
00540 
00541       if (j == FREE_ARRAY_SIZE)
00542         {
00543           j = 0;
00544           while (j < FREE_ARRAY_SIZE)
00545             {
00546               dbus_free (to_free[j]);
00547               ++j;
00548             }
00549 
00550           j = 0;
00551         }
00552       
00553       ++i;
00554     }
00555 
00556   end = clock ();
00557 
00558   _dbus_verbose ("  created/destroyed %d elements in %g seconds\n",
00559                  N_ITERATIONS, (end - start) / (double) CLOCKS_PER_SEC);
00560   
00561   _dbus_verbose (" zeroed mempools\n");
00562   
00563   start = clock ();
00564 
00565   pool = _dbus_mem_pool_new (size, TRUE);
00566   
00567   i = 0;
00568   j = 0;
00569   while (i < N_ITERATIONS)
00570     {
00571       to_free[j] = _dbus_mem_pool_alloc (pool); 
00572       _dbus_assert (to_free[j] != NULL);  /* in a real app of course this is wrong */
00573 
00574       ++j;
00575 
00576       if (j == FREE_ARRAY_SIZE)
00577         {
00578           j = 0;
00579           while (j < FREE_ARRAY_SIZE)
00580             {
00581               _dbus_mem_pool_dealloc (pool, to_free[j]);
00582               ++j;
00583             }
00584 
00585           j = 0;
00586         }
00587       
00588       ++i;
00589     }
00590 
00591   _dbus_mem_pool_free (pool);
00592   
00593   end = clock ();
00594 
00595   _dbus_verbose ("  created/destroyed %d elements in %g seconds\n",
00596                  N_ITERATIONS, (end - start) / (double) CLOCKS_PER_SEC);
00597 }
00598 
00604 dbus_bool_t
00605 _dbus_mem_pool_test (void)
00606 {
00607   int i;
00608   int element_sizes[] = { 4, 8, 16, 50, 124 };
00609   
00610   i = 0;
00611   while (i < _DBUS_N_ELEMENTS (element_sizes))
00612     {
00613       time_for_size (element_sizes[i]);
00614       ++i;
00615     }
00616   
00617   return TRUE;
00618 }
00619 
00620 #endif /* DBUS_BUILD_TESTS */