Suppose I need to allocate several dynamic arrays of different types and/or sizes. An obvious choice is to do just that: perform N allocations. But given that I know all the sizes, and also am pretty sure that each of the arrays will have the same lifespan, why would I do N allocations when I can do just one? After all, I can ask the GC for a sufficiently-sized memory block and put my arrays in it.

But I don't necessarily want the GC to scan *all* my arrays for pointers. The problem is, with current GC, it seems that it's impossible to mark only a part of the block as NO_SCAN. Note that I'm not really talking about precise GC here with full type information, but about addRange() pointing within a GC-allocated block. A short example, two arrays, one holding class references, the other - bytes:

---

import core.memory : GC;
import std.stdio;
import std.typecons;

class C {
    int i;
    this(int i) {
        this.i = i;
    }

    ~this() {
        writeln("C(", i, ") dtor");
    }
}

auto obvious(int numCs, int numBytes) {
    // obvious solution: two arrays, two allocations
return tuple!("Cs", "bytes")(new C[numCs], new byte[numBytes]);
}

auto scanned(int numCs, int numBytes) {

    // one allocation for both arrays,
// for simplicity not dealing with alignment/out of memory here.
    // allocate with default attributes, scanned block
auto memory = GC.malloc(numCs*C.sizeof + numBytes*byte.sizeof);
    auto cs = (cast(C*)memory)[0..numCs];
    cs[] = C.init;
auto bytes = (cast(byte*)(memory + numCs*C.sizeof))[0..numBytes];
    bytes[] = byte.init;
    return tuple!("Cs", "bytes")(cs, bytes);
}

auto selective(int numCs, int numBytes) {

    // one allocation for both arrays,
// for simplicity not dealing with alignment/out of memory here.
    // explicitly ask for NO_SCAN block,
    // to not scan bytes for pointers
    auto memory = GC.malloc(numCs*C.sizeof + numBytes*byte.sizeof,
            GC.BlkAttr.NO_SCAN);
    auto cs = (cast(C*)memory)[0..numCs];
    cs[] = C.init;
    // add scanning range for references
    GC.addRange(cs.ptr, cs.length*C.sizeof, typeid(C));
auto bytes = (cast(byte*)(memory + numCs*C.sizeof))[0..numBytes];
    bytes[] = byte.init;
    return tuple!("Cs", "bytes")(cs, bytes);
}

void main() {

    int numCs = 4; // comes at runtime from elsewhere
    int numBytes = 32; // comes at runtime from elsewhere

    auto arrays1 = obvious(numCs, numBytes);

    int counter;
    foreach (ref e; arrays1.Cs)
        e = new C(counter++); // dtors will be called

    auto arrays2 = scanned(numCs, numBytes);
    foreach (ref e; arrays2.Cs)
        e = new C(counter++); // dtors will be called

    auto arrays3 = selective(numCs, numBytes);
    foreach (ref e; arrays3.Cs)
        e = new C(counter++); // dtors will not be called
}

---

Should this work, and if not, why?

Reply via email to