root/src/system/libroot/posix/malloc/hoard2/heap.cpp
///-*-C++-*-//////////////////////////////////////////////////////////////////
//
// Hoard: A Fast, Scalable, and Memory-Efficient Allocator
//        for Shared-Memory Multiprocessors
// Contact author: Emery Berger, http://www.cs.utexas.edu/users/emery
//
// Copyright (c) 1998-2000, The University of Texas at Austin.
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Library General Public License as
// published by the Free Software Foundation, http://www.fsf.org.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Library General Public License for more details.
//
//////////////////////////////////////////////////////////////////////////////

#include "config.h"

#include "heap.h"
#include "processheap.h"
#include "superblock.h"

using namespace BPrivate;

// NB: Use maketable.cpp to update this
//     if SIZE_CLASSES, ALIGNMENT, SIZE_CLASS_BASE, MAX_EMPTY_SUPERBLOCKS,
//     or SUPERBLOCK_SIZE changes.

#if (MAX_INTERNAL_FRAGMENTATION == 2)

size_t hoardHeap::_sizeTable[hoardHeap::SIZE_CLASSES] = {
        8UL, 16UL, 24UL, 32UL, 40UL, 48UL, 56UL, 72UL, 80UL, 96UL, 120UL, 144UL,
        168UL, 200UL, 240UL, 288UL, 344UL, 416UL, 496UL, 592UL, 712UL, 856UL,
        1024UL, 1232UL, 1472UL, 1768UL, 2120UL, 2544UL, 3048UL, 3664UL,
        4392UL, 5272UL, 6320UL, 7584UL, 9104UL, 10928UL, 13112UL, 15728UL,
        18872UL, 22648UL, 27176UL, 32616UL, 39136UL, 46960UL, 56352UL,
        67624UL, 81144UL, 97376UL, 116848UL, 140216UL, 168256UL, 201904UL,
        242288UL, 290744UL, 348896UL, 418672UL, 502408UL, 602888UL, 723464UL,
        868152UL, 1041784UL, 1250136UL, 1500160UL, 1800192UL, 2160232UL,
        2592280UL, 3110736UL, 3732880UL, 4479456UL, 5375344UL, 6450408UL,
        7740496UL, 9288592UL, 11146312UL, 13375568UL, 16050680UL, 19260816UL,
        23112984UL, 27735576UL, 33282688UL, 39939224UL, 47927072UL,
        57512488UL, 69014984UL, 82817976UL, 99381576UL, 119257888UL,
        143109472UL, 171731360UL, 206077632UL, 247293152UL, 296751776UL,
        356102144UL, 427322560UL, 512787072UL, 615344512UL, 738413376UL,
        886096064UL, 1063315264UL
};

size_t hoardHeap::_threshold[hoardHeap::SIZE_CLASSES] = {
        4096UL, 2048UL, 1364UL, 1024UL, 816UL, 680UL, 584UL, 452UL, 408UL,
        340UL, 272UL, 224UL, 192UL, 160UL, 136UL, 112UL, 92UL, 76UL, 64UL,
        52UL, 44UL, 36UL, 32UL, 24UL, 20UL, 16UL, 12UL, 12UL, 8UL, 8UL, 4UL,
        4UL, 4UL, 4UL, 4UL, 4UL, 4UL, 4UL, 4UL, 4UL, 4UL, 4UL, 4UL, 4UL, 4UL,
        4UL, 4UL, 4UL, 4UL, 4UL, 4UL, 4UL, 4UL, 4UL, 4UL, 4UL, 4UL, 4UL, 4UL,
        4UL, 4UL, 4UL, 4UL, 4UL, 4UL, 4UL, 4UL, 4UL, 4UL, 4UL, 4UL, 4UL, 4UL,
        4UL, 4UL, 4UL, 4UL, 4UL, 4UL, 4UL, 4UL, 4UL, 4UL, 4UL, 4UL, 4UL, 4UL,
        4UL, 4UL, 4UL, 4UL, 4UL, 4UL, 4UL, 4UL, 4UL, 4UL, 4UL, 4UL
};

#elif (MAX_INTERNAL_FRAGMENTATION == 6)

size_t hoardHeap::_sizeTable[hoardHeap::SIZE_CLASSES] = {
        8UL, 16UL, 24UL, 32UL, 48UL, 72UL, 112UL, 176UL, 288UL, 456UL, 728UL,
        1160UL, 1848UL, 2952UL, 4728UL, 7560UL, 12096UL, 19344UL, 30952UL,
        49520UL, 79232UL, 126768UL, 202832UL, 324520UL, 519232UL, 830768UL,
        1329232UL, 2126768UL, 3402824UL, 5444520UL, 8711232UL, 13937968UL,
        22300752UL, 35681200UL, 57089912UL, 91343856UL, 146150176UL,
        233840256UL, 374144416UL, 598631040UL, 957809728UL, 1532495488UL
};

size_t hoardHeap::_threshold[hoardHeap::SIZE_CLASSES] = {
        4096UL, 2048UL, 1364UL, 1024UL, 680UL, 452UL, 292UL, 184UL, 112UL, 68UL,
        44UL, 28UL, 16UL, 8UL, 4UL, 4UL, 4UL, 4UL, 4UL, 4UL, 4UL, 4UL, 4UL,
        4UL, 4UL, 4UL, 4UL, 4UL, 4UL, 4UL, 4UL, 4UL, 4UL, 4UL, 4UL, 4UL, 4UL,
        4UL, 4UL, 4UL, 4UL, 4UL
};

#elif (MAX_INTERNAL_FRAGMENTATION == 10)

size_t hoardHeap::_sizeTable[hoardHeap::SIZE_CLASSES] = {
        8UL, 16UL, 32UL, 64UL, 128UL, 256UL, 512UL, 1024UL, 2048UL, 4096UL,
        8192UL, 16384UL, 32768UL, 65536UL, 131072UL, 262144UL, 524288UL,
        1048576UL, 2097152UL, 4194304UL, 8388608UL, 16777216UL, 33554432UL,
        67108864UL, 134217728UL, 268435456UL, 536870912UL, 1073741824UL,
        2147483648UL
};

size_t hoardHeap::_threshold[hoardHeap::SIZE_CLASSES] = {
        4096UL, 2048UL, 1024UL, 512UL, 256UL, 128UL, 64UL, 32UL, 16UL, 8UL, 4UL,
        4UL, 4UL, 4UL, 4UL, 4UL, 4UL, 4UL, 4UL, 4UL, 4UL, 4UL, 4UL, 4UL, 4UL,
        4UL, 4UL, 4UL, 4UL
};

#else
#       error "Undefined size class base."
#endif


int hoardHeap::fMaxThreadHeaps = 1;
int hoardHeap::_numProcessors;
int hoardHeap::_numProcessorsMask;


// Return ceil(log_2(num)).
// num must be positive.

static int
lg(int num)
{
        assert(num > 0);
        int power = 0;
        int n = 1;
        // Invariant: 2^power == n.
        while (n < num) {
                n <<= 1;
                power++;
        }
        return power;
}


//      #pragma mark -


hoardHeap::hoardHeap(void)
        :
        _index(0), _reusableSuperblocks(NULL), _reusableSuperblocksCount(0)
#if HEAP_DEBUG
        , _magic(HEAP_MAGIC)
#endif
{
        initLock();

        for (int i = 0; i < SUPERBLOCK_FULLNESS_GROUP; i++) {
                for (int j = 0; j < SIZE_CLASSES; j++) {
                        // Initialize all superblocks lists to empty.
                        _superblocks[i][j] = NULL;
                }
        }

        for (int k = 0; k < SIZE_CLASSES; k++) {
                _leastEmptyBin[k] = 0;
        }
}


void
hoardHeap::insertSuperblock(int sizeclass,
        superblock *sb, processHeap *pHeap)
{
        assert(sb->isValid());
        assert(sb->getBlockSizeClass() == sizeclass);
        assert(sb->getPrev() == NULL);
        assert(sb->getNext() == NULL);
        assert(_magic == HEAP_MAGIC);

        // Now it's ours.
        sb->setOwner(this);

        // How full is this superblock?  We'll use this information to put
        // it into the right 'bin'.
        sb->computeFullness();
        int fullness = sb->getFullness();

        // Update the stats.
        incStats(sizeclass, sb->getNumBlocks() - sb->getNumAvailable(),
                sb->getNumBlocks());

        if (fullness == 0
                && sb->getNumBlocks() > 1
                && sb->getNumBlocks() == sb->getNumAvailable()) {
                // Recycle this superblock.
#if 0
                removeSuperblock(sb, sizeclass);
                // Update the stats.
                decStats(sizeclass,
                                 sb->getNumBlocks() - sb->getNumAvailable(), sb->getNumBlocks());
                // Free it immediately.
                const size_t s = sizeFromClass(sizeclass);
                const int blksize = align(sizeof(block) + s);
#if HEAP_LOG
                // Record the memory deallocation.
                MemoryRequest m;
                m.deallocate((int)sb->getNumBlocks() *
                                         (int)sizeFromClass(sb->getBlockSizeClass()));
                pHeap->getLog(getIndex()).append(m);
#endif
#if HEAP_FRAG_STATS

                pHeap->setDeallocated(0, sb->getNumBlocks() * sizeFromClass(sb->getBlockSizeClass()));
#endif

                hoardUnsbrk(sb, align(sizeof(superblock) + blksize));
#else
                recycle(sb);
#endif
        } else {
                // Insert it into the appropriate list.
                superblock *&head = _superblocks[fullness][sizeclass];
                sb->insertBefore(head);
                head = sb;
                assert(head->isValid());

                // Reset the least-empty bin counter.
                _leastEmptyBin[sizeclass] = RESET_LEAST_EMPTY_BIN;
        }
}


superblock *
hoardHeap::removeMaxSuperblock(int sizeclass)
{
        assert(_magic == HEAP_MAGIC);

        superblock *head = NULL;

        // First check the reusable superblocks list.

        head = reuse(sizeclass);
        if (head) {
                // We found one. Since we're removing this superblock, update the
                // stats accordingly.
                decStats(sizeclass,
                        head->getNumBlocks() - head->getNumAvailable(),
                        head->getNumBlocks());

                return head;
        }

        // Instead of finding the superblock with the most available space
        // (something that would either involve a linear scan through the
        // superblocks or maintaining the superblocks in sorted order), we
        // just pick one that is no more than
        // 1/(SUPERBLOCK_FULLNESS_GROUP-1) more full than the superblock
        // with the most available space.  We start with the emptiest group.

        int i = 0;

        // Note: the last group (SUPERBLOCK_FULLNESS_GROUP - 1) is full, so
        // we never need to check it. But for robustness, we leave it in.
        while (i < SUPERBLOCK_FULLNESS_GROUP) {
                head = _superblocks[i][sizeclass];
                if (head)
                        break;

                i++;
        }

        if (!head)
                return NULL;

        // Make sure that this superblock is at least 1/EMPTY_FRACTION
        // empty.
        assert(head->getNumAvailable() * EMPTY_FRACTION >= head->getNumBlocks());

        removeSuperblock(head, sizeclass);

        assert(head->isValid());
        assert(head->getPrev() == NULL);
        assert(head->getNext() == NULL);
        return head;
}


void
hoardHeap::removeSuperblock(superblock *sb, int sizeclass)
{
        assert(_magic == HEAP_MAGIC);

        assert(sb->isValid());
        assert(sb->getOwner() == this);
        assert(sb->getBlockSizeClass() == sizeclass);

        for (int i = 0; i < SUPERBLOCK_FULLNESS_GROUP; i++) {
                if (sb == _superblocks[i][sizeclass]) {
                        _superblocks[i][sizeclass] = sb->getNext();
                        if (_superblocks[i][sizeclass] != NULL) {
                                assert(_superblocks[i][sizeclass]->isValid());
                        }
                        break;
                }
        }

        sb->remove();
        decStats(sizeclass, sb->getNumBlocks() - sb->getNumAvailable(),
                sb->getNumBlocks());
}


void
hoardHeap::moveSuperblock(superblock *sb,
        int sizeclass, int fromBin, int toBin)
{
        assert(_magic == HEAP_MAGIC);
        assert(sb->isValid());
        assert(sb->getOwner() == this);
        assert(sb->getBlockSizeClass() == sizeclass);
        assert(sb->getFullness() == toBin);

        // Remove the superblock from the old bin.

        superblock *&oldHead = _superblocks[fromBin][sizeclass];
        if (sb == oldHead) {
                oldHead = sb->getNext();
                if (oldHead != NULL) {
                        assert(oldHead->isValid());
                }
        }

        sb->remove();

        // Insert the superblock into the new bin.

        superblock *&newHead = _superblocks[toBin][sizeclass];
        sb->insertBefore(newHead);
        newHead = sb;
        assert(newHead->isValid());

        // Reset the least-empty bin counter.
        _leastEmptyBin[sizeclass] = RESET_LEAST_EMPTY_BIN;
}


// The heap lock must be held when this procedure is called.

int
hoardHeap::freeBlock(block * &b, superblock * &sb,
        int sizeclass, processHeap *pHeap)
{
        assert(sb->isValid());
        assert(b->isValid());
        assert(this == sb->getOwner());

        const int oldFullness = sb->getFullness();
        sb->putBlock(b);
        decUStats(sizeclass);
        const int newFullness = sb->getFullness();

        // Free big superblocks.
        if (sb->getNumBlocks() == 1) {
                removeSuperblock(sb, sizeclass);
                const size_t s = sizeFromClass(sizeclass);
                const int blksize = align(sizeof(block) + s);
#if HEAP_LOG
                // Record the memory deallocation.
                MemoryRequest m;
                m.deallocate((int)sb->getNumBlocks()
                        * (int)sizeFromClass(sb->getBlockSizeClass()));
                pHeap->getLog(getIndex()).append(m);
#endif
#if HEAP_FRAG_STATS
                pHeap->setDeallocated(0,
                        sb->getNumBlocks() * sizeFromClass(sb->getBlockSizeClass()));
#endif
                hoardUnsbrk(sb, align(sizeof(superblock) + blksize));
                return 1;
        }

        // If the fullness value has changed, move the superblock.
        if (newFullness != oldFullness) {
                moveSuperblock(sb, sizeclass, oldFullness, newFullness);
        } else {
                // Move the superblock to the front of its list (to reduce
                // paging).
                superblock *&head = _superblocks[newFullness][sizeclass];
                if (sb != head) {
                        sb->remove();
                        sb->insertBefore(head);
                        head = sb;
                }
        }

        // If the superblock is now empty, recycle it.

        if ((newFullness == 0) && (sb->getNumBlocks() == sb->getNumAvailable())) {
                removeSuperblock(sb, sizeclass);
#if 0
                // Free it immediately.
                const size_t s = sizeFromClass(sizeclass);
                const int blksize = align(sizeof(block) + s);
#if HEAP_LOG
                // Record the memory deallocation.
                MemoryRequest m;
                m.deallocate((int)sb->getNumBlocks()
                        * (int)sizeFromClass(sb->getBlockSizeClass()));
                pHeap->getLog(getIndex()).append(m);
#endif
#if HEAP_FRAG_STATS
                pHeap->setDeallocated(0,
                        sb->getNumBlocks() * sizeFromClass(sb->getBlockSizeClass()));
#endif

                hoardUnsbrk(sb, align(sizeof(superblock) + blksize));
                return 1;
#else
                recycle(sb);
                // Update the stats.  This restores the stats to their state
                // before the call to removeSuperblock, above.
                incStats(sizeclass,
                        sb->getNumBlocks() - sb->getNumAvailable(), sb->getNumBlocks());
#endif
        }

        // If this is the process heap, then we're done.
        if (this == (hoardHeap *)pHeap)
                return 0;

        //
        // Release a superblock, if necessary.
        //

        //
        // Check to see if the amount free exceeds the release threshold
        // (two superblocks worth of blocks for a given sizeclass) and if
        // the heap is sufficiently empty.
        //

        // We never move anything to the process heap if we're on a
        // uniprocessor.
        if (_numProcessors > 1) {
                int inUse, allocated;
                getStats(sizeclass, inUse, allocated);
                if ((inUse < allocated - getReleaseThreshold(sizeclass))
                        && (EMPTY_FRACTION * inUse <
                                EMPTY_FRACTION * allocated - allocated)) {

                        // We've crossed the magical threshold. Find the superblock with
                        // the most free blocks and give it to the process heap.
                        superblock *const maxSb = removeMaxSuperblock(sizeclass);
                        assert(maxSb != NULL);

                        // Update the statistics.

                        assert(maxSb->getNumBlocks() >= maxSb->getNumAvailable());

                        // Give the superblock back to the process heap.
                        pHeap->release(maxSb);
                }
        }

        return 0;
}


void
hoardHeap::initNumProcs(void)
{
        system_info info;
        if (get_system_info(&info) != B_OK)
                hoardHeap::_numProcessors = 1;
        else
                hoardHeap::_numProcessors = info.cpu_count;

        fMaxThreadHeaps = 1 << (lg(_numProcessors) + 1);
        _numProcessorsMask = fMaxThreadHeaps - 1;
}