|  | @@ -8,10 +8,12 @@
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  #include <stdlib.h>
 | 
	
		
			
				|  |  |  #include <string.h>
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | +#include "torint.h"
 | 
	
		
			
				|  |  |  #define MEMPOOL_PRIVATE
 | 
	
		
			
				|  |  |  #include "mempool.h"
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +//#define LAZY_CHUNK_SORT
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  /* OVERVIEW:
 | 
	
		
			
				|  |  |   *
 | 
	
		
			
				|  |  |   *     This is an implementation of memory pools for Tor cells.  It may be
 | 
	
	
		
			
				|  | @@ -59,7 +61,6 @@
 | 
	
		
			
				|  |  |   *     if you need doubles.
 | 
	
		
			
				|  |  |   *   - Could probably be optimized a bit; the representation contains
 | 
	
		
			
				|  |  |   *     a bit more info than it really needs to have.
 | 
	
		
			
				|  |  | - *   - probably, chunks should always be a power of 2.
 | 
	
		
			
				|  |  |   */
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  #if 1
 | 
	
	
		
			
				|  | @@ -173,6 +174,9 @@ mp_chunk_new(mp_pool_t *pool)
 | 
	
		
			
				|  |  |    mp_chunk_t *chunk = ALLOC_ROUNDUP(&alloc_size);
 | 
	
		
			
				|  |  |  #else
 | 
	
		
			
				|  |  |    mp_chunk_t *chunk = ALLOC(CHUNK_OVERHEAD + sz);
 | 
	
		
			
				|  |  | +#endif
 | 
	
		
			
				|  |  | +#ifdef MEMPOOL_STATS
 | 
	
		
			
				|  |  | +  ++pool->total_chunks_allocated;
 | 
	
		
			
				|  |  |  #endif
 | 
	
		
			
				|  |  |    CHECK_ALLOC(chunk);
 | 
	
		
			
				|  |  |    memset(chunk, 0, sizeof(mp_chunk_t)); /* Doesn't clear the whole thing. */
 | 
	
	
		
			
				|  | @@ -189,6 +193,17 @@ mp_chunk_new(mp_pool_t *pool)
 | 
	
		
			
				|  |  |    return chunk;
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +/** DOCDOC */
 | 
	
		
			
				|  |  | +static INLINE void
 | 
	
		
			
				|  |  | +add_newly_used_chunk_to_used_list(mp_pool_t *pool, mp_chunk_t *chunk)
 | 
	
		
			
				|  |  | +{
 | 
	
		
			
				|  |  | +  chunk->next = pool->used_chunks;
 | 
	
		
			
				|  |  | +  if (chunk->next)
 | 
	
		
			
				|  |  | +    chunk->next->prev = chunk;
 | 
	
		
			
				|  |  | +  pool->used_chunks = chunk;
 | 
	
		
			
				|  |  | +  ASSERT(!chunk->prev);
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  /** Return an newly allocated item from <b>pool</b>. */
 | 
	
		
			
				|  |  |  void *
 | 
	
		
			
				|  |  |  mp_pool_get(mp_pool_t *pool)
 | 
	
	
		
			
				|  | @@ -214,10 +229,7 @@ mp_pool_get(mp_pool_t *pool)
 | 
	
		
			
				|  |  |        chunk->next->prev = NULL;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      /* Put the chunk on the 'used' list*/
 | 
	
		
			
				|  |  | -    chunk->next = pool->used_chunks;
 | 
	
		
			
				|  |  | -    if (chunk->next)
 | 
	
		
			
				|  |  | -      chunk->next->prev = chunk;
 | 
	
		
			
				|  |  | -    pool->used_chunks = chunk;
 | 
	
		
			
				|  |  | +    add_newly_used_chunk_to_used_list(pool, chunk);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      ASSERT(!chunk->prev);
 | 
	
		
			
				|  |  |      --pool->n_empty_chunks;
 | 
	
	
		
			
				|  | @@ -229,11 +241,7 @@ mp_pool_get(mp_pool_t *pool)
 | 
	
		
			
				|  |  |      CHECK_ALLOC(chunk);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      /* Add the new chunk to the used list. */
 | 
	
		
			
				|  |  | -    chunk->next = pool->used_chunks;
 | 
	
		
			
				|  |  | -    if (chunk->next)
 | 
	
		
			
				|  |  | -      chunk->next->prev = chunk;
 | 
	
		
			
				|  |  | -    pool->used_chunks = chunk;
 | 
	
		
			
				|  |  | -    ASSERT(!chunk->prev);
 | 
	
		
			
				|  |  | +    add_newly_used_chunk_to_used_list(pool, chunk);
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |    ASSERT(chunk->n_allocated < chunk->capacity);
 | 
	
	
		
			
				|  | @@ -258,6 +266,9 @@ mp_pool_get(mp_pool_t *pool)
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |    ++chunk->n_allocated;
 | 
	
		
			
				|  |  | +#ifdef MEMPOOL_STATS
 | 
	
		
			
				|  |  | +  ++pool->total_items_allocated;
 | 
	
		
			
				|  |  | +#endif
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |    if (PREDICT_UNLIKELY(chunk->n_allocated == chunk->capacity)) {
 | 
	
		
			
				|  |  |      /* This chunk just became full. */
 | 
	
	
		
			
				|  | @@ -275,7 +286,6 @@ mp_pool_get(mp_pool_t *pool)
 | 
	
		
			
				|  |  |        chunk->next->prev = chunk;
 | 
	
		
			
				|  |  |      pool->full_chunks = chunk;
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |    /* And return the memory portion of the mp_allocated_t. */
 | 
	
		
			
				|  |  |    return A2M(allocated);
 | 
	
		
			
				|  |  |  }
 | 
	
	
		
			
				|  | @@ -393,43 +403,111 @@ mp_pool_new(size_t item_size, size_t chunk_capacity)
 | 
	
		
			
				|  |  |    return pool;
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +#ifdef LAZY_CHUNK_SORT
 | 
	
		
			
				|  |  | +/** DOCDOC */
 | 
	
		
			
				|  |  | +static int
 | 
	
		
			
				|  |  | +mp_pool_sort_used_chunks_helper(const void *_a, const void *_b)
 | 
	
		
			
				|  |  | +{
 | 
	
		
			
				|  |  | +  mp_chunk_t *a = *(mp_chunk_t**)_a;
 | 
	
		
			
				|  |  | +  mp_chunk_t *b = *(mp_chunk_t**)_b;
 | 
	
		
			
				|  |  | +  return b->n_allocated - a->n_allocated;
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +/** DOCDOC */
 | 
	
		
			
				|  |  | +static void
 | 
	
		
			
				|  |  | +mp_pool_sort_used_chunks(mp_pool_t *pool)
 | 
	
		
			
				|  |  | +{
 | 
	
		
			
				|  |  | +  int i, n=0, inverted=0;
 | 
	
		
			
				|  |  | +  mp_chunk_t **chunks, *chunk;
 | 
	
		
			
				|  |  | +  for (chunk = pool->used_chunks; chunk; chunk = chunk->next) {
 | 
	
		
			
				|  |  | +    ++n;
 | 
	
		
			
				|  |  | +    if (chunk->next && chunk->next->n_allocated > chunk->n_allocated)
 | 
	
		
			
				|  |  | +      ++inverted;
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +  if (!inverted)
 | 
	
		
			
				|  |  | +    return;
 | 
	
		
			
				|  |  | +  ASSERT(n);
 | 
	
		
			
				|  |  | +  //printf("Sort %d/%d\n",inverted,n);
 | 
	
		
			
				|  |  | +  chunks = ALLOC(sizeof(mp_chunk_t *)*n);
 | 
	
		
			
				|  |  | +#ifdef ALLOC_CAN_RETURN_NULL
 | 
	
		
			
				|  |  | +  if (PREDICT_UNLIKELY(!chunks)) return;
 | 
	
		
			
				|  |  | +#endif
 | 
	
		
			
				|  |  | +  for (i=0,chunk = pool->used_chunks; chunk; chunk = chunk->next)
 | 
	
		
			
				|  |  | +    chunks[i++] = chunk;
 | 
	
		
			
				|  |  | +  qsort(chunks, n, sizeof(mp_chunk_t *), mp_pool_sort_used_chunks_helper);
 | 
	
		
			
				|  |  | +  pool->used_chunks = chunks[0];
 | 
	
		
			
				|  |  | +  chunks[0]->prev = NULL;
 | 
	
		
			
				|  |  | +  for (i=1;i<n;++i) {
 | 
	
		
			
				|  |  | +    chunks[i-1]->next = chunks[i];
 | 
	
		
			
				|  |  | +    chunks[i]->prev = chunks[i-1];
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +  chunks[n-1]->next = NULL;
 | 
	
		
			
				|  |  | +  FREE(chunks);
 | 
	
		
			
				|  |  | +#if 0
 | 
	
		
			
				|  |  | +  inverted = 0;
 | 
	
		
			
				|  |  | +  for (chunk = pool->used_chunks; chunk; chunk = chunk->next) {
 | 
	
		
			
				|  |  | +    if (chunk->next) {
 | 
	
		
			
				|  |  | +      ASSERT(chunk->next->n_allocated <= chunk->n_allocated);
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +#endif
 | 
	
		
			
				|  |  | +  mp_pool_assert_ok(pool);
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +#endif
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  /** If there are more than <b>n</b> empty chunks in <b>pool</b>, free the
 | 
	
		
			
				|  |  |   * excess ones that have been empty for the longest.  (If <b>n</b> is less
 | 
	
		
			
				|  |  |   * than zero, free only empty chunks that were not used since the last
 | 
	
		
			
				|  |  | - * call to mp_pool_clean(), leaving only -<b>n</b>.) */
 | 
	
		
			
				|  |  | + * call to mp_pool_clean(), leaving only -<b>n</b>.)
 | 
	
		
			
				|  |  | + * DOCDOC Keep_recently_used, n_to_keep
 | 
	
		
			
				|  |  | + * XXXX020 maybe dump negative n_to_keep behavior, if k_r_u turns out to be
 | 
	
		
			
				|  |  | + *   smarter.
 | 
	
		
			
				|  |  | + **/
 | 
	
		
			
				|  |  |  void
 | 
	
		
			
				|  |  | -mp_pool_clean(mp_pool_t *pool, int n)
 | 
	
		
			
				|  |  | +mp_pool_clean(mp_pool_t *pool, int n_to_keep, int keep_recently_used)
 | 
	
		
			
				|  |  |  {
 | 
	
		
			
				|  |  | -  /* XXXX020 this is stupid.  We shouldn't care about empty chunks if there
 | 
	
		
			
				|  |  | -   * is lots of space in used chunks. */
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |    mp_chunk_t *chunk, **first_to_free;
 | 
	
		
			
				|  |  | -  if (n < 0) {
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +#ifdef LAZY_CHUNK_SORT
 | 
	
		
			
				|  |  | +  mp_pool_sort_used_chunks(pool);
 | 
	
		
			
				|  |  | +#endif
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  if (n_to_keep < 0) {
 | 
	
		
			
				|  |  |      /* As said in the documentation, "negative n" means "leave an additional
 | 
	
		
			
				|  |  |       * -n chunks". So replace n with a positive number. */
 | 
	
		
			
				|  |  | -    n = pool->min_empty_chunks + (-n);
 | 
	
		
			
				|  |  | -    if (n < pool->n_empty_chunks)
 | 
	
		
			
				|  |  | -      pool->min_empty_chunks = n;
 | 
	
		
			
				|  |  | +    n_to_keep = pool->min_empty_chunks + (-n_to_keep);
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +  if (keep_recently_used) {
 | 
	
		
			
				|  |  | +    int n_recently_used = pool->n_empty_chunks - pool->min_empty_chunks;
 | 
	
		
			
				|  |  | +    if (n_to_keep < n_recently_used)
 | 
	
		
			
				|  |  | +      n_to_keep = n_recently_used;
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  | -  ASSERT(n>=0);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  ASSERT(n_to_keep >= 0);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |    first_to_free = &pool->empty_chunks;
 | 
	
		
			
				|  |  | -  while (*first_to_free && n > 0) {
 | 
	
		
			
				|  |  | +  while (*first_to_free && n_to_keep > 0) {
 | 
	
		
			
				|  |  |      first_to_free = &(*first_to_free)->next;
 | 
	
		
			
				|  |  | -    --n;
 | 
	
		
			
				|  |  | +    --n_to_keep;
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  | -  if (!*first_to_free)
 | 
	
		
			
				|  |  | +  if (!*first_to_free) {
 | 
	
		
			
				|  |  | +    pool->min_empty_chunks = pool->n_empty_chunks;
 | 
	
		
			
				|  |  |      return;
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |    chunk = *first_to_free;
 | 
	
		
			
				|  |  |    while (chunk) {
 | 
	
		
			
				|  |  |      mp_chunk_t *next = chunk->next;
 | 
	
		
			
				|  |  |      chunk->magic = 0xdeadbeef;
 | 
	
		
			
				|  |  |      FREE(chunk);
 | 
	
		
			
				|  |  | +#ifdef MEMPOOL_STATS
 | 
	
		
			
				|  |  | +    ++pool->total_chunks_freed;
 | 
	
		
			
				|  |  | +#endif
 | 
	
		
			
				|  |  |      --pool->n_empty_chunks;
 | 
	
		
			
				|  |  |      chunk = next;
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +  pool->min_empty_chunks = pool->n_empty_chunks;
 | 
	
		
			
				|  |  |    *first_to_free = NULL;
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -535,6 +613,8 @@ mp_pool_log_status(mp_pool_t *pool, int severity)
 | 
	
		
			
				|  |  |      ++n_used;
 | 
	
		
			
				|  |  |      bu += chunk->n_allocated * pool->item_alloc_size;
 | 
	
		
			
				|  |  |      ba += chunk->mem_size;
 | 
	
		
			
				|  |  | +    log_fn(severity, LD_MM, "   used chunk: %d items allocated",
 | 
	
		
			
				|  |  | +           chunk->n_allocated);
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |    log_fn(severity, LD_MM, U64_FORMAT"/"U64_FORMAT
 | 
	
		
			
				|  |  |           " bytes in %d partially full chunks",
 | 
	
	
		
			
				|  | @@ -556,6 +636,15 @@ mp_pool_log_status(mp_pool_t *pool, int severity)
 | 
	
		
			
				|  |  |    log_fn(severity, LD_MM, "Total: "U64_FORMAT"/"U64_FORMAT" bytes allocated "
 | 
	
		
			
				|  |  |           "for cell pools are full.",
 | 
	
		
			
				|  |  |           U64_PRINTF_ARG(bytes_used), U64_PRINTF_ARG(bytes_allocated));
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +#ifdef MEMPOOL_STATS
 | 
	
		
			
				|  |  | +  log_fn(severity, LD_MM, U64_FORMAT" cell allocations ever; "
 | 
	
		
			
				|  |  | +         U64_FORMAT" chunk allocations ever; "
 | 
	
		
			
				|  |  | +         U64_FORMAT" chunk frees ever.",
 | 
	
		
			
				|  |  | +         U64_PRINTF_ARG(pool->total_items_allocated),
 | 
	
		
			
				|  |  | +         U64_PRINTF_ARG(pool->total_chunks_allocated),
 | 
	
		
			
				|  |  | +         U64_PRINTF_ARG(pool->total_chunks_freed));
 | 
	
		
			
				|  |  | +#endif
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  #endif
 | 
	
		
			
				|  |  |  
 |