Ve por tu propio camino. Parte uno. Apilar



Este es el tercero de una serie sobre GC. En el primer artículo, presenté el recolector de basura D y las características del lenguaje que lo requieren, y también mencioné técnicas simples para usarlo de manera efectiva. En el segundo artículo, mostré qué herramientas en el lenguaje y las bibliotecas existen que limitan la GC en ciertos lugares del código, y cómo el compilador puede ayudar a identificar los lugares donde vale la pena hacerlo, y también recomendé al escribir programas en D para usar GC de manera segura, mientras minimizando su impacto en el rendimiento con estrategias simples, y luego ajustando el código para evitar GC o incluso optimizando aún más su uso solo donde el perfilador lo garantiza.



Cuando el recolector de basura está deshabilitado GC.disableo deshabilitado por un atributo de función @nogc, la memoria aún debe asignarse en algún lugar. E incluso si utiliza el GC al máximo, es recomendable minimizar la cantidad y el número de asignaciones de memoria a través del GC. Esto significa asignar memoria en la pila o en el montón normal. Este artículo se centrará en el primero. La asignación de memoria en el montón será el tema del próximo artículo.



Asignación de memoria en la pila



La estrategia de asignación de memoria más simple en D es la misma que en C: evite usar el montón y use la pila tanto como sea posible. Si se necesita una matriz y se conoce su tamaño en el momento de la compilación, use una matriz estática en lugar de una dinámica. Las estructuras tienen semántica de valor y se crean en la pila de forma predeterminada, mientras que las clases tienen semántica de referencia y generalmente se crean en el montón; Se deben preferir las estructuras siempre que sea posible. Las capacidades D en tiempo de compilación lo ayudan a lograr muchas cosas que de otra forma no serían posibles.



Matrices estáticas



Las matrices estáticas en D requieren que se conozca el tamaño en tiempo de compilación.



// OK
int[10] nums;

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


A diferencia de las matrices dinámicas, la inicialización de matrices estáticas a través de un literal no asigna memoria a través del GC. Las longitudes de la matriz y el literal deben coincidir; de lo contrario, el compilador generará un 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 .



En el próximo artículo de la serie, veremos formas de asignar memoria en un montón normal sin pasar por el GC.




All Articles