Suis ton propre chemin. Partie un. Empiler



Il s'agit du troisième d'une série sur GC. Dans le premier article, j'ai présenté le ramasse-miettes D et les fonctionnalités de langage qui le nécessitent, et abordé des techniques simples pour l'utiliser efficacement. Dans le deuxième article, j'ai montré quels outils dans le langage et les bibliothèques existent qui limitent GC à certains endroits du code, et comment le compilateur peut aider à identifier les endroits où cela vaut la peine d'être fait, et également recommandé lors de l'écriture de programmes en D, tout d'abord, utilisez hardiment GC, tout en minimiser son impact sur les performances avec des stratégies simples, puis peaufiner le code pour éviter GC ou encore optimiser son utilisation uniquement là où le profileur le garantit.



Lorsque le garbage collector est désactivé GC.disableou désactivé par un attribut de fonction @nogc, la mémoire doit encore être allouée quelque part. Et même si vous utilisez GC au maximum, il est toujours conseillé de minimiser la quantité et le nombre d'allocations de mémoire via le GC. Cela signifie allouer de la mémoire sur la pile ou sur le tas normal. Cet article se concentrera sur le premier. L'allocation de mémoire sur le tas fera l'objet du prochain article.



Allocation de mémoire sur la pile



La stratégie d'allocation de mémoire la plus simple en D est la même qu'en C: évitez d'utiliser le tas et utilisez la pile autant que possible. Si un tableau est nécessaire et que sa taille est connue au moment de la compilation, utilisez un tableau statique au lieu d'un tableau dynamique. Les structures ont une sémantique de valeur et sont créées sur la pile par défaut, tandis que les classes ont une sémantique de référence et sont généralement créées sur le tas; les structures devraient être préférées chaque fois que possible. Les capacités D au moment de la compilation vous aident à réaliser de nombreuses choses qui ne seraient pas possibles autrement.



Tableaux statiques



Les tableaux statiques en D nécessitent que la taille soit connue au moment de la compilation.



// OK
int[10] nums;

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


Contrairement aux tableaux dynamiques, l'initialisation de tableaux statiques via un littéral n'alloue pas de mémoire via le GC. Les longueurs du tableau et du littéral doivent correspondre, sinon le compilateur générera une erreur.



@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 .



Dans le prochain article de la série, nous examinerons les moyens d'allouer de la mémoire sur un tas normal sans passer par le GC.




All Articles