Skip to content

Commit

Permalink
Try to reduce SMA fragmentation
Browse files Browse the repository at this point in the history
Instead of picking the first fitting block, check if there is
a smaller block that still fits. We only look at a limited, small
number of additional blocks. This is an approximation of best-fit
allocation.

This is intended to reduce fragmentation, in particular for the
case where a large block was recently freed (and is on top of the
free list) and a small allocation is performed.
  • Loading branch information
nikic committed Dec 6, 2018
1 parent cd69041 commit fa41659
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 28 deletions.
73 changes: 45 additions & 28 deletions apc_sma.c
Original file line number Diff line number Diff line change
Expand Up @@ -108,17 +108,55 @@ struct block_t {
#define RESET_CANARY(v)
#endif

/* {{{ MINBLOCKSIZE */
#define MINBLOCKSIZE (ALIGNWORD(1) + ALIGNWORD(sizeof(block_t)))
/* }}} */

/* How many extra blocks to check for a better fit */
#define BEST_FIT_LIMIT 3

static inline block_t *find_block(sma_header_t *header, size_t realsize) {
void *shmaddr = header;
block_t *cur, *prv = BLOCKAT(ALIGNWORD(sizeof(sma_header_t)));
block_t *found = NULL;
uint32_t i;
CHECK_CANARY(prv);

while (prv->fnext) {
cur = BLOCKAT(prv->fnext);
CHECK_CANARY(cur);

/* Found a suitable block */
if (cur->size >= realsize) {
found = cur;
break;
}

prv = cur;
}

if (found) {
/* Try to find a smaller block that also fits */
prv = cur;
for (i = 0; i < BEST_FIT_LIMIT && prv->fnext; i++) {
cur = BLOCKAT(prv->fnext);
CHECK_CANARY(cur);

if (cur->size >= realsize && cur->size < found->size) {
found = cur;
}

prv = cur;
}
}

return found;
}

/* {{{ sma_allocate: tries to allocate at least size bytes in a segment */
static APC_HOTSPOT size_t sma_allocate(sma_header_t *header, size_t size, size_t fragment, size_t *allocated)
{
void* shmaddr; /* header of shared memory segment */
block_t* prv; /* block prior to working block */
block_t* cur; /* working block in list */
block_t* prvnextfit; /* block before next fit */
size_t realsize; /* actual size of block needed, including header */
size_t block_size = ALIGNWORD(sizeof(struct block_t));

Expand All @@ -134,37 +172,16 @@ static APC_HOTSPOT size_t sma_allocate(sma_header_t *header, size_t size, size_t
return -1;
}

prvnextfit = 0; /* initially null (no fit) */
prv = BLOCKAT(ALIGNWORD(sizeof(sma_header_t)));

CHECK_CANARY(prv);

while (prv->fnext != 0) {
cur = BLOCKAT(prv->fnext);

CHECK_CANARY(cur);

/* If it can fit realsize bytes in cur block, stop searching */
if (cur->size >= realsize) {
prvnextfit = prv;
break;
}
prv = cur;
}

if (prvnextfit == 0) {
cur = find_block(header, realsize);
if (!cur) {
/* No suitable block found */
return -1;
}

prv = prvnextfit;
cur = BLOCKAT(prv->fnext);

CHECK_CANARY(prv);
CHECK_CANARY(cur);

if (cur->size == realsize || (cur->size > realsize && cur->size < (realsize + (MINBLOCKSIZE + fragment)))) {
/* cur is big enough for realsize, but too small to split - unlink it */
*(allocated) = cur->size - block_size;
prv = BLOCKAT(cur->fprev);
prv->fnext = cur->fnext;
BLOCKAT(cur->fnext)->fprev = OFFSET(prv);
NEXT_SBLOCK(cur)->prev_size = 0; /* block is alloc'd */
Expand Down
31 changes: 31 additions & 0 deletions tests/sma001.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
--TEST--
Test SMA behavior #1
--INI--
apc.enabled=1
apc.enable_cli=1
apc.shm_size=16M
--FILE--
<?php

// Make sure that a sequence of alternating small and large
// allocations does not result in catastrophic fragmentation

$len = 1024 * 1024;
for ($i = 0; $i < 100; $i++) {
apcu_delete("key");
$result = apcu_store("key", str_repeat("x", $len));
if ($result === false) {
echo "Failed $i.\n";
}

// Force a small allocation
apcu_store("dummy" . $i, null);

// Increase $len slightly
$len += 100;
}

?>
===DONE===
--EXPECT--
===DONE===

0 comments on commit fa41659

Please sign in to comment.