1 /* 2 A tiny hack to redirect all GC allocations to a fixed size arena. 3 4 Say, you've got an operation that actively allocates in GC'ed heap but 5 after it's complete you don't need anything it allocated. And you need 6 to do it in a loop many times, potentially allocating lots of memory. 7 Just add one line in the beginning of loop's scope and each iteration 8 will reuse the same fixed size buffer. 9 Like this: 10 11 foreach(fname; files) { 12 auto ar = useCleanArena(); // (1) 13 auto img = readPng(fname).getAsTrueColorImage(); 14 process(img); 15 // (2) 16 } 17 18 Between points (1) and (2) all GC allocations will happen inside 19 an arena which will be reused on each iteration. No GC will happen, 20 no garbage accumulated. 21 22 If you need some data created inbetween, you can temporarily pause 23 using the arena and allocate something on main GC heap: 24 25 void testArena() { 26 auto ar = useCleanArena(); 27 auto s = new ubyte[100]; // allocated in arena 28 { 29 auto pause = pauseArena(); // in this scope it's not used 30 auto t = new ubyte[200]; // allocated in main GC heap 31 } 32 auto v = new ubyte[300]; // allocated in arena again 33 writeln("hi"); 34 // end of scope, stop using arena 35 } 36 37 You can set size for arena by calling setArenaSize() before its first use. 38 Default size is 64 MB. 39 40 */ 41 module gcarena; 42 import std.stdio, core.memory, core.exception; 43 44 void setArenaSize(size_t totalSize) { 45 gcaData.arena_size = totalSize; 46 } 47 48 struct ArenaHandler { 49 this(this) @disable; 50 ~this() @nogc { stop(); } 51 52 void stop() @nogc { 53 if (stopped) return; 54 gcaData.clearProxy(); 55 stopped = true; 56 } 57 58 size_t allocated() { return gcaData.arena_pos; } 59 60 private bool stopped = false; 61 } 62 63 auto useCleanArena() { 64 gcaData.arena_pos = 0; 65 gcaData.installProxy(); 66 return ArenaHandler(); 67 } 68 69 auto pauseArena() { 70 gcaData.clearProxy(); 71 struct ArenaPause { 72 ~this() @nogc { gcaData.installProxy(); } 73 this(this) @disable; 74 } 75 return ArenaPause(); 76 } 77 78 private: ////////////////////////////////////////////////////////////// 79 alias BlkInfo = GC.BlkInfo; 80 81 struct Proxy // copied from proxy.d (d runtime) 82 { 83 extern (C) 84 { 85 void function() gc_enable; 86 void function() gc_disable; 87 void function() gc_collect; 88 void function() gc_minimize; 89 90 uint function(void*) gc_getAttr; 91 uint function(void*, uint) gc_setAttr; 92 uint function(void*, uint) gc_clrAttr; 93 94 void* function(size_t, uint, const TypeInfo) gc_malloc; 95 BlkInfo function(size_t, uint, const TypeInfo) gc_qalloc; 96 void* function(size_t, uint, const TypeInfo) gc_calloc; 97 void* function(void*, size_t, uint ba, const TypeInfo) gc_realloc; 98 size_t function(void*, size_t, size_t, const TypeInfo) gc_extend; 99 size_t function(size_t) gc_reserve; 100 void function(void*) gc_free; 101 102 void* function(void*) gc_addrOf; 103 size_t function(void*) gc_sizeOf; 104 105 BlkInfo function(void*) gc_query; 106 107 void function(void*) gc_addRoot; 108 void function(void*, size_t, const TypeInfo) gc_addRange; 109 110 void function(void*) gc_removeRoot; 111 void function(void*) gc_removeRange; 112 void function(in void[]) gc_runFinalizers; 113 } 114 } 115 116 struct GCAData { 117 Proxy myProxy; 118 Proxy *pOrg; // pointer to original Proxy of runtime 119 Proxy** pproxy; 120 121 ubyte[] arena_bytes; 122 size_t arena_pos = 0; 123 size_t arena_size = 64*1024*1024; 124 125 void initProxy() { 126 pOrg = gc_getProxy(); 127 pproxy = cast(Proxy**) (cast(byte*)pOrg + Proxy.sizeof); 128 foreach(funname; __traits(allMembers, Proxy)) 129 __traits(getMember, myProxy, funname) = &genCall!funname; 130 myProxy.gc_malloc = &gca_malloc; 131 myProxy.gc_qalloc = &gca_qalloc; 132 myProxy.gc_calloc = &gca_calloc; 133 } 134 135 void* alloc(size_t size) { 136 if (arena_bytes.length==0) { 137 auto oldproxy = *pproxy; 138 *pproxy = null; 139 arena_bytes = new ubyte[arena_size]; 140 *pproxy = oldproxy; 141 } 142 143 if (arena_pos + size > arena_bytes.length) { 144 writeln("Arena too small! arena=", arena_bytes.length, " asked for ", size, " need ", arena_pos + size); 145 onOutOfMemoryError(); 146 } 147 auto pos = arena_pos; 148 arena_pos += size; 149 arena_pos = (arena_pos + 15) & ~15; 150 return &arena_bytes[pos]; 151 } 152 153 void clearArena() { 154 writeln("clearArena: allocated ", arena_pos); 155 arena_pos = 0; 156 } 157 158 void installProxy() @nogc { 159 // writeln("using arena now"); 160 *pproxy = &myProxy; 161 } 162 163 void clearProxy() @nogc { 164 // writeln("using GC now"); 165 *pproxy = null; 166 } 167 } 168 169 extern(C) { 170 Proxy* gc_getProxy(); 171 172 auto genCall(string funname)(FunArgsTypes!funname args) { 173 *gcaData.pproxy = null; 174 scope(exit) *gcaData.pproxy = &gcaData.myProxy; 175 return __traits(getMember, *gcaData.pOrg, funname)(args); 176 } 177 178 void* gca_malloc(size_t sz, uint ba, const TypeInfo ti) { 179 //writeln("gca_malloc ", sz); 180 return gcaData.alloc(sz); 181 } 182 183 BlkInfo gca_qalloc(size_t sz, uint ba, const TypeInfo ti) { 184 //writeln("gca_qalloc ", sz); 185 auto pos0 = gcaData.arena_pos; 186 BlkInfo b; 187 b.base = gcaData.alloc(sz); 188 b.size = gcaData.arena_pos - pos0; 189 b.attr = ba; 190 return b; 191 } 192 193 void* gca_calloc(size_t sz, uint ba, const TypeInfo ti) { 194 import core.stdc.string : memset; 195 //writeln("gca_calloc ", sz); 196 void* p = gcaData.alloc(sz); 197 memset(p, 0, sz); 198 return p; 199 } 200 } 201 202 template FunArgsTypes(string funname) { 203 alias FunType = typeof(*__traits(getMember, gcaData.myProxy, funname)); 204 alias FunArgsTypes = ParameterTypeTuple!FunType; 205 } 206 207 GCAData gcaData; // thread local 208 209 static this() { 210 gcaData.initProxy(); 211 }