Question to GC experts: NO_SCAN for a part of the block

Stanislav Blinov via Digitalmars-d digitalmars-d at puremagic.com
Sat Jun 3 18:08:57 PDT 2017


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?


More information about the Digitalmars-d mailing list