Go Your Own Way. Part one. Stack



This is the third in a series on GC. In the first article, I introduced the D garbage collector and the language features that require it, and also touched on simple techniques for using it effectively. In the second article, I showed what tools in the language and libraries exist that limit GC in certain places in the code, and how the compiler can help in identifying places where it is worth doing, and also recommended when writing programs in D to first use GC safely, while minimizing its performance impact with simple strategies, and then tweaking the code to avoid GC or even further optimizing its use only where the profiler guarantees it.



When the garbage collector is disabled GC.disableor disabled by a function attribute @nogc, memory still needs to be allocated somewhere. And even if you use GC to its fullest, it is still advisable to minimize the amount and number of memory allocations through the GC. This means allocating memory either on the stack or on the regular heap. This article will focus on the former. Memory allocation on the heap will be the subject of the next article.



Allocating memory on the stack



The simplest memory allocation strategy in D is the same as in C: avoid using the heap and use the stack as much as possible. If an array is needed and its size is known at compile time, use a static array instead of a dynamic one. Structures have value semantics and are created on the stack by default, while classes have reference semantics and are usually created on the heap; structures should be preferred whenever possible. The compile-time D capabilities help you achieve many things that would not be possible otherwise.



Static arrays



Static arrays in D require the size to be known at compile time.



// OK
int[10] nums;

// :  x     
int x = 10;
int[x] err;


Unlike dynamic arrays, initializing static arrays via a literal does not allocate memory via the GC. The lengths of the array and the literal must match, otherwise the compiler will generate an error.



@nogc void main() {
    int[3] nums = [1, 2, 3];
}


, , , .



void printNums(int[] nums) {
    import std.stdio : writeln;
    writeln(nums);
}

void main() {
    int[]  dnums = [0, 1, 2];
    int[3] snums = [0, 1, 2];
    printNums(dnums);
    printNums(snums);
}


-vgc , , โ€” . :



int[] foo() {
    auto nums = [0, 1, 2];

    //  -  nums...

    return nums;
}


nums . , , . , .



, - , , . , . .dup:



int[] foo() {
    int[3] nums = [0, 1, 2];

    //  x โ€”  -   nums
    bool condition = x;

    if(condition) return nums.dup;
    else return [];
}


GC .dup, . , [] null โ€” , ( length) 0, ptr null.





D , . , , : .



struct Foo {
    int x;
    ~this() {
        import std.stdio;
        writefln("#%s says bye!", x);
    }
}
void main() {
    Foo f1 = Foo(1);
    Foo f2 = Foo(2);
    Foo f3 = Foo(3);
}


, :



#3 says bye!
#2 says bye!
#1 says bye!


, , . GC new, GC . , . [std.typecons.scoped](https://dlang.org/phobos/std_typecons.html#.scoped) .



class Foo {
    int x;

    this(int x) { 
        this.x = x; 
    }

    ~this() {
        import std.stdio;
        writefln("#%s says bye!", x);
    }
}
void main() {
    import std.typecons : scoped;
    auto f1 = scoped!Foo(1);
    auto f2 = scoped!Foo(2);
    auto f3 = scoped!Foo(3);
}


, , . core.object.destroy, .



, scoped, destroy @nogc-. , , GC, , @nogc-. , nogc, .



, . (Plain Old Data, POD) , - GUI, , . , , . , , , .



alloca



C D ยซ ยป, alloca. , GC, . , :



import core.stdc.stdlib : alloca;

void main() {
    size_t size = 10;
    void* mem = alloca(size);

    // Slice the memory block
    int[] arr = cast(int[])mem[0 .. size];
}


C, alloca : . , arr . arr.dup.





, Queue, ยซยป. D , . Java , , . D , (Design by Introspection). , , , , UFCS, ( ).



DbI . (. .)

Queue . , , . , : - . , , , .



//  `Size`   0     
//    ;  
//    
struct Queue(T, size_t Size = 0) 
{
    //       .
    //   `public`  DbI-   
    // ,   Queue   .
    enum isFixedSize = Size > 0;

    void enqueue(T item) 
    {
        static if(isFixedSize) {
            assert(_itemCount < _items.length);
        }
        else {
            ensureCapacity();
        }
        push(item);
    }

    T dequeue() {
        assert(_itemCount != 0);
        static if(isFixedSize) {
            return pop();
        }
        else {
            auto ret = pop();
            ensurePacked();
            return ret;
        }
    }

    //       
    static if(!isFixedSize) {
        void reserve(size_t capacity) { 
            /*      */ 
        }
    }

private:   
    static if(isFixedSize) {
        T[Size] _items;     
    }
    else T[] _items;
    size_t _head, _tail;
    size_t _itemCount;

    void push(T item) { 
        /*  item,  _head  _tail */
        static if(isFixedSize) { ... }
        else { ... }
    }

    T pop() { 
        /*  item,  _head  _tail */ 
        static if(isFixedSize) { ... }
        else { ... }
    }

    //       
    static if(!isFixedSize) {
        void ensureCapacity() { /*  ,   */ }
        void ensurePacked() { /*  ,   */}
    }
}


:



Queue!Foo qUnbounded;
Queue!(Foo, 128) qBounded;


qBounded . qUnbounded, . , . isFixedSize:



void doSomethingWithQueueInterface(T)(T queue)
{
    static if(T.isFixedSize) { ... }
    else { ... }
}


: __traits(hasMember, T, "reserve"), โ€” : hasMember!T("reserve"). __traits std.traits โ€” DbI; .





GC. , , โ€” GC .



In the next article in the series, we'll look at ways to allocate memory on a regular heap without going through the GC.




All Articles