Go Your Own Way. Part two. Heap



We continue our series of articles on the D garbage collector. This is the second part of the article on memory allocation outside the GC. The first part talked about allocating memory on the stack. Now we will look at memory allocation from the heap.



While this is only the fourth post in this series, this is the third in which I talk about ways to avoid using GC. Make no mistake: I'm not trying to scare away programmers from the D garbage collector. Quite the opposite. Understanding when and how to go without GC is essential to using it effectively.



Once again, I will say that for efficient garbage collection, you need to reduce the load on the GC. As mentioned in the first and subsequent articles of the series, this does not mean that it should be completely abandoned. This means that you need to be judicious about how much and how often to allocate memory through the GC. The fewer memory allocations, the fewer places left where garbage collection can start. The less memory is on the garbage collector's heap, the less memory it needs to scan.



It is impossible to accurately and comprehensively determine in which applications the impact of GC will be noticeable and in which it will not - it very much depends on the specific program. But we can safely say that in most applications there is no need to disable GC temporarily or completely, but when it is still needed, it is important to know how to do without it. The obvious solution is to allocate memory on the stack, but D also allows memory to be allocated on the regular heap, bypassing the GC.



Omnipresent Xi



, C . , , - API C. , C ABI, - , . D β€” . , D C.



core.stdc β€” D, C. D, C. , .



import core.stdc.stdio : puts;
void main() 
{
    puts("Hello C standard library.");
}


, D, , C extern(C), , D as a Better C [], -betterC. , . D C , extern(C) . puts core.stdc.stdio β€” , , .



malloc



D C, , malloc, calloc, realloc free. , core.stdc.stdlib. D GC .



import core.stdc.stdlib;
void main() 
{
    enum totalInts = 10;

    //    10   int.
    int* intPtr = cast(int*)malloc(int.sizeof * totalInts);

    // assert(0) ( assert(false))     ,  
    //   assert ,       
    //   malloc.
    if(!intPtr) assert(0, "Out of memory!");

    //      .     
    // ,     ,  
    //  .

    scope(exit) free(intPtr);

    //    ,     
    //  +.
    int[] intArray = intPtr[0 .. totalInts];
}


GC, D . T, GC, T.init β€” int 0. D, . malloc calloc, . , float.init β€” float.nan, 0.0f. .



, , malloc free . :



import core.stdc.stdlib;

//    ,      .
void[] allocate(size_t size)
{
    //  malloc(0)    (  null  - ),     ,    .
    assert(size != 0);

    void* ptr = malloc(size);
    if(!ptr) assert(0, "Out of memory!");

    //    ,      
    //  .
    return ptr[0 .. size];
}

T[] allocArray(T)(size_t count) 
{ 
    // ,      !
    return cast(T[])allocate(T.sizeof * count); 
}

//   deallocate  
void deallocate(void* ptr)
{   
    // free handles null pointers fine.
    free(ptr);
}

void deallocate(void[] mem) 
{ 
    deallocate(mem.ptr); 
}

void main() {
    import std.stdio : writeln;
    int[] ints = allocArray!int(10);
    scope(exit) deallocate(ints);

    foreach(i; 0 .. 10) {
        ints[i] = i;
    }

    foreach(i; ints[]) {
        writeln(i);
    }
}


allocate void[] void*, length. , , allocate , allocArray , , allocate , . , C , β€” , , . calloc realloc, , C.



, (, allocArray) -betterC, . D.



, -



, GC, , . , ~= ~, , GC. ( ). . , , GC.



import core.stdc.stdlib : malloc;
import std.stdio : writeln;

void main()
{
    int[] ints = (cast(int*)malloc(int.sizeof * 10))[0 .. 10];
    writeln("Capacity: ", ints.capacity);

    //      
    int* ptr = ints.ptr;
    ints ~= 22;
    writeln(ptr == ints.ptr);
}


:



Capacity: 0
false


0 , . , GC, , . , , . GC , , . , ints GC, , (. D slices ).



, , , - , malloc .



:



void leaker(ref int[] arr)
{
    ...
    arr ~= 10;
    ...
}

void cleaner(int[] arr)
{
    ...
    arr ~= 10;
    ...
}


, β€” , , . , (, length ptr) . β€” .



leaker , C, GC. : , free ptr ( , GC, C), . cleaner . , , . GC, ptr .



, . cleaner , . , , , @nogc. , , malloc, free, , .



Array std.container.array: GC, , .



API



C β€” . malloc, . , . API: , Win32 HeapAlloc ( core.sys.windows.windows). , D , GC.





, . . . .



, int.



struct Point { int x, y; }
Point* onePoint = cast(Point*)malloc(Point.sizeof);
Point* tenPoints = cast(Point*)malloc(Point.sizeof * 10);


, . malloc D. , Phobos , .



std.conv.emplace , void[], , . , emplace malloc, allocate :



struct Vertex4f 
{ 
    float x, y, z, w; 
    this(float x, float y, float z, float w = 1.0f)
    {
        this.x = x;
        this.y = y;
        this.z = z;
        this.w = w;
    }
}

void main()
{
    import core.stdc.stdlib : malloc;
    import std.conv : emplace;
    import std.stdio : writeln;

    Vertex4f* temp1 = cast(Vertex4f*)malloc(Vertex4f.sizeof);
    Vertex4f* vert1 = emplace(temp1, 4.0f, 3.0f, 2.0f); 
    writeln(*vert1);

    void[] temp2 = allocate(Vertex4f.sizeof);
    Vertex4f* vert2 = emplace!Vertex4f(temp2, 10.0f, 9.0f, 8.0f);
    writeln(*vert2);
}


emplace . , D . , Vertex4f:



struct Vertex4f 
{
    // x, y  z   float.nan
    float x, y, z;

    // w   1.0f
    float w = 1.0f;
}

void main()
{
    import core.stdc.stdlib : malloc;
    import std.conv : emplace;
    import std.stdio : writeln;

    Vertex4f vert1, vert2 = Vertex4f(4.0f, 3.0f, 2.0f);
    writeln(vert1);
    writeln(vert2);    

    auto vert3 = emplace!Vertex4f(allocate(Vertex4f.sizeof));
    auto vert4 = emplace!Vertex4f(allocate(Vertex4f.sizeof), 4.0f, 3.0f, 2.0f);
    writeln(*vert3);
    writeln(*vert4);
}


:



Vertex4f(nan, nan, nan, 1)
Vertex4f(4, 3, 2, 1)
Vertex4f(nan, nan, nan, 1)
Vertex4f(4, 3, 2, 1)


, emplace , β€” . int float. , , . , emplace , .



std.experimental.allocator



. , - , std.experimental.allocator D. API, , , (Design by Introspection), , , . Mallocator GCAllocator , , - . β€” emsi-containers.



GC



GC , D, GC, , GC. , GC. , malloc , new.



GC GC.addRange.



import core.memory;
enum size = int.sizeof * 10;
void* p1 = malloc(size);
GC.addRange(p1, size);

void[] p2 = allocate!int(10);
GC.addRange(p2.ptr, p2.length);


, GC.removeRange, . . free , . , .



GC , , , . . , , . GC , . addRange . , GC, addRange .





addRange. C , .



struct Item { SomeClass foo; }
auto items = (cast(Item*)malloc(Item.sizeof * 10))[0 .. 10];
GC.addRange(items.ptr, items.length);


GC 10 . length . , β€” void[] ( , byte ubyte). :



GC.addRange(items.ptr, items.length * Item.sizeof);


API , , void[].



void addRange(void[] mem) 
{
    import core.memory;
    GC.addRange(mem.ptr, mem.length);
}


addRange(items) . void[] , mem.length , items.length * Item.sizeof.



GC



This article has covered the very basics of how to use the heap without resorting to GC. Besides classes, there is one more gaping gap left in our story: what to do with destructors. I'll save this topic for the next article, where it will be very appropriate. Here's what's planned for the next GC in this series. Stay in touch!



Thanks to Walter Bright, Guillaume Piolat, Adam D. Ruppe and Steven Schveighoffer for their invaluable help in preparing this article.



Instead of continuing
, . , , . API core.memory.GC inFinalizer , , .



All Articles