Wolfgang Thallergcc 15.1.0ada185d1 year agoHistory
334 lines · 11.5 KB · D
RawDownload
// Written in the D programming language./**Source: $(PHOBOSSRC std/experimental/allocator/building_blocks/quantizer.d)*/module std.experimental.allocator.building_blocks.quantizer; import std.experimental.allocator.common; /**This allocator sits on top of `ParentAllocator` and quantizes allocation sizes,usually from arbitrary positive numbers to a small set of round numbers (e.g.powers of two, page sizes etc). This technique is commonly used to: $(UL$(LI Preallocate more memory than requested such that later on, whenreallocation is needed (e.g. to grow an array), expansion can be done quicklyin place. Reallocation to smaller sizes is also fast (in-place) when the newsize requested is within the same quantum as the existing size. Code that'sreallocation-heavy can therefore benefit from fronting a generic allocator witha `Quantizer`. These advantages are present even if `ParentAllocator` does notsupport reallocation at all.)$(LI Improve behavior of allocators sensitive to allocation sizes, such as`FreeList` and `FreeTree`. Rounding allocation requests up makes for smallerfree lists/trees at the cost of slack memory (internal fragmentation).)) The following methods are forwarded to the parent allocator if present:`allocateAll`, `owns`, `deallocateAll`, `empty`. Preconditions: `roundingFunction` must satisfy three constraints. These arenot enforced (save for the use of `assert`) for the sake of efficiency.$(OL$(LI $(D roundingFunction(n) >= n) for all `n` of type `size_t`;)$(LI `roundingFunction` must be monotonically increasing, i.e. $(DroundingFunction(n1) <= roundingFunction(n2)) for all $(D n1 < n2);)$(LI `roundingFunction` must be `nothrow`, `@safe`, `@nogc` and `pure`, i.e.always return the same value for a given `n`.))*/struct Quantizer(ParentAllocator, alias roundingFunction){    import std.traits : hasMember;     /**    The parent allocator. Depending on whether `ParentAllocator` holds state    or not, this is a member variable or an alias for    `ParentAllocator.instance`.    */    static if (stateSize!ParentAllocator)    {        ParentAllocator parent;    }    else    {        alias parent = ParentAllocator.instance;        __gshared Quantizer instance;    }     /**    Returns `roundingFunction(n)`.    */    size_t goodAllocSize(size_t n)    {        auto result = roundingFunction(n);        assert(result >= n);        return result;    }     /**    Alignment is identical to that of the parent.    */    enum alignment = ParentAllocator.alignment;     /**    Gets a larger buffer `buf` by calling    `parent.allocate(goodAllocSize(n))`. If `buf` is `null`, returns    `null`. Otherwise, returns $(D buf[0 .. n]).    */    void[] allocate(size_t n)    {        auto result = parent.allocate(goodAllocSize(n));        return result.ptr ? result.ptr[0 .. n] : null;    }     static if (hasMember!(ParentAllocator, "allocateZeroed"))    package(std) void[] allocateZeroed()(size_t n)    {        auto result = parent.allocateZeroed(goodAllocSize(n));        return result.ptr ? result.ptr[0 .. n] : null;    }     /**    Defined only if `parent.alignedAllocate` exists and works similarly to    `allocate` by forwarding to    $(D parent.alignedAllocate(goodAllocSize(n), a)).    */    static if (hasMember!(ParentAllocator, "alignedAllocate"))    void[] alignedAllocate(size_t n, uint a)    {        auto result = parent.alignedAllocate(goodAllocSize(n), a);        return result.ptr ? result.ptr[0 .. n] : null;    }     /**    First checks whether there's enough slack memory preallocated for `b`    by evaluating $(D b.length + delta <= goodAllocSize(b.length)). If that's    the case, expands `b` in place. Otherwise, attempts to use    `parent.expand` appropriately if present.    */    bool expand(ref void[] b, size_t delta)    {        if (!b || delta == 0) return delta == 0;        immutable allocated = goodAllocSize(b.length),            needed = b.length + delta,            neededAllocation = goodAllocSize(needed);        assert(b.length <= allocated);        assert(needed <= neededAllocation);        assert(allocated <= neededAllocation);        // Second test needed because expand must work for null pointers, too.        if (allocated == neededAllocation)        {            // Nice!            b = (() @trusted => b.ptr[0 .. needed])();            return true;        }        // Hail Mary        static if (hasMember!(ParentAllocator, "expand"))        {            // Expand to the appropriate quantum            auto original = (() @trusted => b.ptr[0 .. allocated])();            assert(goodAllocSize(needed) >= allocated);            if (!parent.expand(original, neededAllocation - allocated))                return false;            // Dial back the size            b = (() @trusted => original.ptr[0 .. needed])();            return true;        }        else        {            return false;        }    }     /**    Expands or shrinks allocated block to an allocated size of $(D    goodAllocSize(s)). Expansion occurs in place under the conditions required    by `expand`. Shrinking occurs in place if $(D goodAllocSize(b.length)    == goodAllocSize(s)).    */    bool reallocate(ref void[] b, size_t s)    {        if (!b.ptr)        {            b = allocate(s);            return b.length == s;        }        if (s >= b.length && expand(b, s - b.length)) return true;        immutable toAllocate = goodAllocSize(s),            allocated = goodAllocSize(b.length);        // Are the lengths within the same quantum?        if (allocated == toAllocate)        {            // Reallocation (whether up or down) will be done in place            b = b.ptr[0 .. s];            return true;        }        // Defer to parent (or global) with quantized size        auto original = b.ptr[0 .. allocated];        if (!parent.reallocate(original, toAllocate)) return false;        b = original.ptr[0 .. s];        return true;    }     /**    Defined only if `ParentAllocator.alignedAllocate` exists. Expansion    occurs in place under the conditions required by `expand`. Shrinking    occurs in place if $(D goodAllocSize(b.length) == goodAllocSize(s)).    */    static if (hasMember!(ParentAllocator, "alignedAllocate"))    bool alignedReallocate(ref void[] b, size_t s, uint a)    {        if (!b.ptr)        {            b = alignedAllocate(s, a);            return b.length == s;        }        if (s >= b.length && b.ptr.alignedAt(a) && expand(b, s - b.length)) return true;        immutable toAllocate = goodAllocSize(s),            allocated = goodAllocSize(b.length);        // Are the lengths within the same quantum?        if (allocated == toAllocate && b.ptr.alignedAt(a))        {            assert(b.ptr); // code above must have caught this            // Reallocation (whether up or down) will be done in place            b = b.ptr[0 .. s];            return true;        }        // Defer to parent (or global) with quantized size        auto original = b.ptr[0 .. allocated];        if (!parent.alignedReallocate(original, toAllocate, a)) return false;        b = original.ptr[0 .. s];        return true;    }     /**    Defined if `ParentAllocator.deallocate` exists and forwards to    $(D parent.deallocate(b.ptr[0 .. goodAllocSize(b.length)])).    */    static if (hasMember!(ParentAllocator, "deallocate"))    bool deallocate(void[] b)    {        if (!b.ptr) return true;        return parent.deallocate(b.ptr[0 .. goodAllocSize(b.length)]);    }     // Forwarding methods    mixin(forwardToMember("parent",        "allocateAll", "owns", "deallocateAll", "empty"));} ///@system unittest{    import std.experimental.allocator.building_blocks.free_tree : FreeTree;    import std.experimental.allocator.gc_allocator : GCAllocator;     size_t roundUpToMultipleOf(size_t s, uint base)    {        auto rem = s % base;        return rem ? s + base - rem : s;    }     // Quantize small allocations to a multiple of cache line, large ones to a    // multiple of page size    alias MyAlloc = Quantizer!(        FreeTree!GCAllocator,        n => roundUpToMultipleOf(n, n <= 16_384 ? 64 : 4096));    MyAlloc alloc;    const buf = alloc.allocate(256);    assert(buf.ptr);} version (StdUnittest)@system unittest{    import std.experimental.allocator.gc_allocator : GCAllocator;    alias MyAlloc = Quantizer!(GCAllocator,        (size_t n) => n.roundUpToMultipleOf(64));    testAllocator!(() => MyAlloc());     assert((() pure nothrow @safe @nogc => MyAlloc().goodAllocSize(1))() == 64);     auto a = MyAlloc();    auto b = a.allocate(42);    assert(b.length == 42);    // Inplace expand, since goodAllocSize is 64    assert((() @safe => a.expand(b, 22))());    //assert((() nothrow @safe => a.expand(b, 22))());    assert(b.length == 64);    // Trigger parent.expand, which may or may not succed    //() nothrow @safe { a.expand(b, 1); }();    () @safe { a.expand(b, 1); }();    assert(a.reallocate(b, 100));    assert(b.length == 100);    // Ensure deallocate inherits from parent    () nothrow @nogc { a.deallocate(b); }();} @system unittest{    import std.experimental.allocator.building_blocks.region : Region;    import std.experimental.allocator.mallocator : Mallocator;    import std.typecons : Ternary;     alias Alloc = Quantizer!(Region!(Mallocator),            (size_t n) => n.roundUpToMultipleOf(64));    auto a = Alloc(Region!Mallocator(1024 * 64));    const b = a.allocate(42);    assert(b.length == 42);    // Check that owns inherits from parent, i.e. Region    assert((() pure nothrow @safe @nogc => a.owns(b))() == Ternary.yes);    assert((() pure nothrow @safe @nogc => a.owns(null))() == Ternary.no);     auto c = a.allocate(42);    assert(c.length == 42);    assert((() pure nothrow @safe @nogc => a.owns(c))() == Ternary.yes);    // Inplace expand, since goodAllocSize is 64    assert((() nothrow @safe => a.expand(c, 22))());    assert(c.length == 64);    // Trigger parent.expand    assert((() nothrow @safe => a.expand(c, 1))());    assert(c.length == 65);    // Check that reallocate inherits from parent    assert((() nothrow @nogc => a.reallocate(c, 100))());    assert(c.length == 100);} version (StdUnittest)@system unittest{    import std.experimental.allocator.building_blocks.region : Region;    import std.experimental.allocator.mallocator : Mallocator;     alias MyAlloc = Quantizer!(Region!(Mallocator),            (size_t n) => n.roundUpToMultipleOf(64));    testAllocator!(() => MyAlloc(Region!Mallocator(1024 * 64)));     auto a = MyAlloc(Region!Mallocator(1024 * 64));    void[] b;    assert((() nothrow @nogc => a.alignedReallocate(b, 42, 16))());    assert(b.length == 42);    assert(alignedAt(&b[0], 16));} version (StdUnittest)@system unittest{    import std.experimental.allocator.building_blocks.region : BorrowedRegion;    import std.typecons : Ternary;     alias MyAlloc = Quantizer!(BorrowedRegion!(),        (size_t n) => n.roundUpToMultipleOf(64));    testAllocator!(() => MyAlloc(BorrowedRegion!()(new ubyte[1024 * 64])));     auto a = MyAlloc(BorrowedRegion!()(new ubyte[1024 * 64]));    // Check that empty inherits from parent    assert((() pure nothrow @safe @nogc => a.empty)() == Ternary.yes);    auto b = a.allocate(42);    assert(b.length == 42);    assert((() pure nothrow @safe @nogc => a.empty)() == Ternary.no);    // Check that deallocateAll inherits from parent    assert((() nothrow @nogc => a.deallocateAll())());    assert((() pure nothrow @safe @nogc => a.empty)() == Ternary.yes);}

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t