Siga seu próprio caminho. Parte um. Pilha



Este é o terceiro de uma série no GC. No primeiro artigo, apresentei o coletor de lixo D e os recursos de linguagem que o exigem, além de abordar técnicas simples para usá-lo com eficiência. No segundo artigo, mostrei quais ferramentas e bibliotecas existem na linguagem e nas bibliotecas que limitam a GC em determinados lugares do código, e como o compilador pode ajudar a identificar lugares onde vale a pena fazer, e também recomendei que, ao escrever programas em D, primeiro use a GC com ousadia, enquanto minimizando seu impacto no desempenho com estratégias simples e, em seguida, aprimorando o código para evitar o GC ou otimizar ainda mais seu uso apenas quando o criador de perfil o garantir.



Quando o coletor de lixo é desabilitado GC.disableou desabilitado por um atributo de função @nogc, a memória ainda precisa ser alocada em algum lugar. E mesmo se você usar o GC ao máximo, ainda é recomendável minimizar a quantidade e o número de alocações de memória por meio do GC. Isso significa alocar memória na pilha ou na pilha regular. Este artigo se concentrará no primeiro. A alocação de memória no heap será o assunto do próximo artigo.



Alocando memória na pilha



A estratégia mais simples de alocação de memória em D é a mesma de C: evite usar o heap e use a pilha o máximo possível. Se uma matriz for necessária e seu tamanho for conhecido no momento da compilação, use uma matriz estática em vez de dinâmica. As estruturas têm semântica de valor e são criadas na pilha por padrão, enquanto as classes têm semântica de referência e geralmente são criadas no heap; estruturas devem ser preferidas sempre que possível. Os recursos D em tempo de compilação o ajudam a conseguir muitas coisas que de outra forma não seriam possíveis.



Matrizes estáticas



Matrizes estáticas em D exigem que o tamanho seja conhecido em tempo de compilação.



// OK
int[10] nums;

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


Diferentemente das matrizes dinâmicas, a inicialização de matrizes estáticas por meio de um literal não aloca memória pelo GC. Os comprimentos da matriz e do literal devem corresponder, caso contrário, o compilador gerará um erro.



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



No próximo artigo da série, veremos maneiras de alocar memória em uma pilha regular sem passar pelo GC.




All Articles