Skip to content

Commit 0913f49

Browse files
ishitatsuyukiivyl
authored andcommitted
ntdll: Use log-linear bucketing for free lists.
Currently, the free list consists of a "small list" for sizes below 256, which are linearly spaced, and a "large list" which is manually split into a few chunks. This patch replaces it with a single log-linear policy, while expanding the range the large list covers. The old implementation had issues when a lot of large allocations happened. In this case, all the allocations went in the last catch-all bucket in the "large list", and what happens is: 1. The linked list grew in size over time, causing searching cost to skyrocket. 2. With the first-fit allocation policy, fragmentation was also making the problem worse. The new bucketing covers the entire range up until we start allocating large blocks, which will not enter the free list. It also makes the allocation policy closer to best-fit (although not exactly), reducing fragmentation. The increase in number of free lists does incur some cost when it needs to be skipped over, but the improvement in allocation performance outweighs it. For future work, these ideas (mostly from glibc) might or might not benefit performance: - Use an exact best-fit allocation policy. - Add a bitmap for freelist, allowing empty lists to be skipped with a single bit scan. Signed-off-by: Tatsuyuki Ishi <[email protected]> (cherry picked from commit a612ab6)
1 parent b1cec9d commit 0913f49

File tree

2 files changed

+64
-38
lines changed

2 files changed

+64
-38
lines changed

dlls/kernel32/tests/heap.c

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -452,9 +452,7 @@ static void test_HeapCreate(void)
452452

453453
SetLastError( 0xdeadbeef );
454454
ptr1 = HeapAlloc( heap, 0, alloc_size - (0x200 + 0x80 * sizeof(void *)) );
455-
todo_wine
456455
ok( !ptr1, "HeapAlloc succeeded\n" );
457-
todo_wine
458456
ok( GetLastError() == ERROR_NOT_ENOUGH_MEMORY, "got error %lu\n", GetLastError() );
459457
ret = HeapFree( heap, 0, ptr1 );
460458
ok( ret, "HeapFree failed, error %lu\n", GetLastError() );

dlls/ntdll/heap.c

Lines changed: 64 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,8 @@ C_ASSERT( sizeof(ARENA_LARGE) == 4 * BLOCK_ALIGN );
146146

147147
#define ROUND_ADDR(addr, mask) ((void *)((UINT_PTR)(addr) & ~(UINT_PTR)(mask)))
148148
#define ROUND_SIZE(size, mask) ((((SIZE_T)(size) + (mask)) & ~(SIZE_T)(mask)))
149-
#define FIELD_MAX(type, field) (((SIZE_T)1 << (sizeof(((type *)0)->field) * 8)) - 1)
149+
#define FIELD_BITS(type, field) (sizeof(((type *)0)->field) * 8)
150+
#define FIELD_MAX(type, field) (((SIZE_T)1 << FIELD_BITS(type, field)) - 1)
150151

151152
#define HEAP_MIN_BLOCK_SIZE ROUND_SIZE(sizeof(struct entry) + BLOCK_ALIGN, BLOCK_ALIGN - 1)
152153

@@ -168,17 +169,11 @@ C_ASSERT( HEAP_MAX_FREE_BLOCK_SIZE >= HEAP_MAX_BLOCK_REGION_SIZE );
168169
/* minimum size to start allocating large blocks */
169170
#define HEAP_MIN_LARGE_BLOCK_SIZE (HEAP_MAX_USED_BLOCK_SIZE - 0x1000)
170171

171-
/* There will be a free list bucket for every arena size up to and including this value */
172-
#define HEAP_MAX_SMALL_FREE_LIST 0x100
173-
C_ASSERT( HEAP_MAX_SMALL_FREE_LIST % BLOCK_ALIGN == 0 );
174-
#define HEAP_NB_SMALL_FREE_LISTS (((HEAP_MAX_SMALL_FREE_LIST - HEAP_MIN_BLOCK_SIZE) / BLOCK_ALIGN) + 1)
175-
176-
/* Max size of the blocks on the free lists above HEAP_MAX_SMALL_FREE_LIST */
177-
static const SIZE_T free_list_sizes[] =
178-
{
179-
0x200, 0x400, 0x1000, ~(SIZE_T)0
180-
};
181-
#define HEAP_NB_FREE_LISTS (ARRAY_SIZE(free_list_sizes) + HEAP_NB_SMALL_FREE_LISTS)
172+
#define FREE_LIST_LINEAR_BITS 2
173+
#define FREE_LIST_LINEAR_MASK ((1 << FREE_LIST_LINEAR_BITS) - 1)
174+
#define FREE_LIST_COUNT ((FIELD_BITS( struct block, block_size ) - FREE_LIST_LINEAR_BITS + 1) * (1 << FREE_LIST_LINEAR_BITS) + 1)
175+
/* for reference, update this when changing parameters */
176+
C_ASSERT( FREE_LIST_COUNT == 0x3d );
182177

183178
typedef struct DECLSPEC_ALIGN(BLOCK_ALIGN) tagSUBHEAP
184179
{
@@ -304,7 +299,7 @@ struct heap
304299
DWORD pending_pos; /* Position in pending free requests ring */
305300
struct block **pending_free; /* Ring buffer for pending free requests */
306301
RTL_CRITICAL_SECTION cs;
307-
struct entry free_lists[HEAP_NB_FREE_LISTS];
302+
struct entry free_lists[FREE_LIST_COUNT];
308303
struct bin *bins;
309304
SUBHEAP subheap;
310305
};
@@ -569,23 +564,6 @@ static void valgrind_notify_free_all( SUBHEAP *subheap, const struct heap *heap
569564
#endif
570565
}
571566

572-
/* locate a free list entry of the appropriate size */
573-
/* size is the size of the whole block including the arena header */
574-
static inline struct entry *find_free_list( struct heap *heap, SIZE_T block_size, BOOL last )
575-
{
576-
struct entry *list, *end = heap->free_lists + ARRAY_SIZE(heap->free_lists);
577-
unsigned int i;
578-
579-
if (block_size <= HEAP_MAX_SMALL_FREE_LIST)
580-
i = (block_size - HEAP_MIN_BLOCK_SIZE) / BLOCK_ALIGN;
581-
else for (i = HEAP_NB_SMALL_FREE_LISTS; i < HEAP_NB_FREE_LISTS - 1; i++)
582-
if (block_size <= free_list_sizes[i - HEAP_NB_SMALL_FREE_LISTS]) break;
583-
584-
list = heap->free_lists + i;
585-
if (last && ++list == end) list = heap->free_lists;
586-
return list;
587-
}
588-
589567
/* get the memory protection type to use for a given heap */
590568
static inline ULONG get_protection_type( DWORD flags )
591569
{
@@ -624,10 +602,60 @@ static void heap_set_status( const struct heap *heap, ULONG flags, NTSTATUS stat
624602
if (status) RtlSetLastWin32ErrorAndNtStatusFromNtStatus( status );
625603
}
626604

627-
static size_t get_free_list_block_size( unsigned int index )
605+
static SIZE_T get_free_list_block_size( unsigned int index )
606+
{
607+
DWORD log = index >> FREE_LIST_LINEAR_BITS;
608+
DWORD linear = index & FREE_LIST_LINEAR_MASK;
609+
610+
if (log == 0) return index * BLOCK_ALIGN;
611+
612+
return (((1 << FREE_LIST_LINEAR_BITS) + linear) << (log - 1)) * BLOCK_ALIGN;
613+
}
614+
615+
/*
616+
* Given a size, return its index in the block size list for freelists.
617+
*
618+
* With FREE_LIST_LINEAR_BITS=2, the list looks like this
619+
* (with respect to size / BLOCK_ALIGN):
620+
* 0,
621+
* 1, 2, 3, 4, 5, 6, 7, 8,
622+
* 10, 12, 14, 16, 20, 24, 28, 32,
623+
* 40, 48, 56, 64, 80, 96, 112, 128,
624+
* 160, 192, 224, 256, 320, 384, 448, 512,
625+
* ...
626+
*/
627+
static unsigned int get_free_list_index( SIZE_T block_size )
628+
{
629+
DWORD bit, log, linear;
630+
631+
if (block_size > get_free_list_block_size( FREE_LIST_COUNT - 1 ))
632+
return FREE_LIST_COUNT - 1;
633+
634+
block_size /= BLOCK_ALIGN;
635+
/* find the highest bit */
636+
if (!BitScanReverse( &bit, block_size ) || bit < FREE_LIST_LINEAR_BITS)
637+
{
638+
/* for small values, the index is same as block_size. */
639+
log = 0;
640+
linear = block_size;
641+
}
642+
else
643+
{
644+
/* the highest bit is always set, ignore it and encode the next FREE_LIST_LINEAR_BITS bits
645+
* as a linear scale, combined with the shift as a log scale, in the free list index. */
646+
log = bit - FREE_LIST_LINEAR_BITS + 1;
647+
linear = (block_size >> (bit - FREE_LIST_LINEAR_BITS)) & FREE_LIST_LINEAR_MASK;
648+
}
649+
650+
return (log << FREE_LIST_LINEAR_BITS) + linear;
651+
}
652+
653+
/* locate a free list entry of the appropriate size */
654+
static inline struct entry *find_free_list( struct heap *heap, SIZE_T block_size, BOOL last )
628655
{
629-
if (index < HEAP_NB_SMALL_FREE_LISTS) return HEAP_MIN_BLOCK_SIZE + index * BLOCK_ALIGN;
630-
return free_list_sizes[index - HEAP_NB_SMALL_FREE_LISTS];
656+
unsigned int index = get_free_list_index( block_size );
657+
if (last && ++index == FREE_LIST_COUNT) index = 0;
658+
return &heap->free_lists[index];
631659
}
632660

633661
static void heap_dump( const struct heap *heap )
@@ -651,7 +679,7 @@ static void heap_dump( const struct heap *heap )
651679
}
652680

653681
TRACE( " free_lists: %p\n", heap->free_lists );
654-
for (i = 0; i < HEAP_NB_FREE_LISTS; i++)
682+
for (i = 0; i < FREE_LIST_COUNT; i++)
655683
TRACE( " %p: size %#8Ix, prev %p, next %p\n", heap->free_lists + i, get_free_list_block_size( i ),
656684
LIST_ENTRY( heap->free_lists[i].entry.prev, struct entry, entry ),
657685
LIST_ENTRY( heap->free_lists[i].entry.next, struct entry, entry ) );
@@ -1126,7 +1154,7 @@ static BOOL is_valid_free_block( const struct heap *heap, const struct block *bl
11261154
unsigned int i;
11271155

11281156
if ((subheap = find_subheap( heap, block, FALSE ))) return TRUE;
1129-
for (i = 0; i < HEAP_NB_FREE_LISTS; i++) if (block == &heap->free_lists[i].block) return TRUE;
1157+
for (i = 0; i < FREE_LIST_COUNT; i++) if (block == &heap->free_lists[i].block) return TRUE;
11301158
return FALSE;
11311159
}
11321160

@@ -1510,7 +1538,7 @@ HANDLE WINAPI RtlCreateHeap( ULONG flags, void *addr, SIZE_T total_size, SIZE_T
15101538
list_init( &heap->large_list );
15111539

15121540
list_init( &heap->free_lists[0].entry );
1513-
for (i = 0, entry = heap->free_lists; i < HEAP_NB_FREE_LISTS; i++, entry++)
1541+
for (i = 0, entry = heap->free_lists; i < FREE_LIST_COUNT; i++, entry++)
15141542
{
15151543
block_set_flags( &entry->block, ~0, BLOCK_FLAG_FREE_LINK );
15161544
block_set_size( &entry->block, 0 );

0 commit comments

Comments
 (0)