Skip to content
This repository was archived by the owner on Oct 12, 2022. It is now read-only.

Commit 435ae8a

Browse files
authored
Merge pull request #3120 from denizzzka/gc_options_bytes
Store memory-related GC options in bytes instead of megabytes. merged-on-behalf-of: Nicholas Wilson <[email protected]>
2 parents d774ab9 + 88daad5 commit 435ae8a

File tree

5 files changed

+225
-24
lines changed

5 files changed

+225
-24
lines changed

changelog/gc_suffixes.dd

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
Memory releated GC options can now be specified with higher accuracy
2+
3+
Memory-related GC options stored in bytes instead of megabytes to
4+
provide fine-tune GC on low memory devices.
5+
6+
Added ability to use suffixes B, K, M or G:
7+
---
8+
extern(C) __gshared string[] rt_options =
9+
[ "gcopt=minPoolSize:4K maxPoolSize:2M incPoolSize:8K" ];
10+
---
11+
12+
Values without suffix treated as megabytes.

src/core/gc/config.d

Lines changed: 72 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ struct Config
1818
ubyte profile; // enable profiling with summary when terminating program
1919
string gc = "conservative"; // select gc implementation conservative|precise|manual
2020

21-
size_t initReserve; // initial reserve (MB)
22-
size_t minPoolSize = 1; // initial and minimum pool size (MB)
23-
size_t maxPoolSize = 64; // maximum pool size (MB)
24-
size_t incPoolSize = 3; // pool size increment (MB)
21+
@MemVal size_t initReserve; // initial reserve (bytes)
22+
@MemVal size_t minPoolSize = 1 << 20; // initial and minimum pool size (bytes)
23+
@MemVal size_t maxPoolSize = 64 << 20; // maximum pool size (bytes)
24+
@MemVal size_t incPoolSize = 3 << 20; // pool size increment (bytes)
2525
uint parallel = 99; // number of additional threads for marking (limited by cpuid.threadsPerCPU-1)
2626
float heapSizeFactor = 2.0; // heap size to used memory ratio
2727
string cleanup = "collect"; // select gc cleanup method none|collect|finalize
@@ -46,20 +46,82 @@ struct Config
4646
if (i) printf("|");
4747
printf("%.*s", cast(int) entry.name.length, entry.name.ptr);
4848
}
49+
auto _initReserve = initReserve.bytes2prettyStruct;
50+
auto _minPoolSize = minPoolSize.bytes2prettyStruct;
51+
auto _maxPoolSize = maxPoolSize.bytes2prettyStruct;
52+
auto _incPoolSize = incPoolSize.bytes2prettyStruct;
4953
printf(" - select gc implementation (default = conservative)
5054
51-
initReserve:N - initial memory to reserve in MB (%lld)
52-
minPoolSize:N - initial and minimum pool size in MB (%lld)
53-
maxPoolSize:N - maximum pool size in MB (%lld)
54-
incPoolSize:N - pool size increment MB (%lld)
55+
initReserve:N - initial memory to reserve in MB (%lld%c)
56+
minPoolSize:N - initial and minimum pool size in MB (%lld%c)
57+
maxPoolSize:N - maximum pool size in MB (%lld%c)
58+
incPoolSize:N - pool size increment MB (%lld%c)
5559
parallel:N - number of additional threads for marking (%lld)
5660
heapSizeFactor:N - targeted heap size to used memory ratio (%g)
5761
cleanup:none|collect|finalize - how to treat live objects when terminating (collect)
62+
63+
Memory-related values can use B, K, M or G suffixes.
5864
".ptr,
59-
cast(long)initReserve, cast(long)minPoolSize,
60-
cast(long)maxPoolSize, cast(long)incPoolSize,
65+
_initReserve.v, _initReserve.u,
66+
_minPoolSize.v, _minPoolSize.u,
67+
_maxPoolSize.v, _maxPoolSize.u,
68+
_incPoolSize.v, _incPoolSize.u,
6169
cast(long)parallel, heapSizeFactor);
6270
}
6371

6472
string errorName() @nogc nothrow { return "GC"; }
6573
}
74+
75+
private struct PrettyBytes
76+
{
77+
long v;
78+
char u; /// unit
79+
}
80+
81+
pure @nogc nothrow:
82+
83+
private PrettyBytes bytes2prettyStruct(size_t val)
84+
{
85+
char c = prettyBytes(val);
86+
87+
return PrettyBytes(val, c);
88+
}
89+
90+
private static char prettyBytes(ref size_t val)
91+
{
92+
char sym = 'B';
93+
94+
if (val == 0)
95+
return sym;
96+
97+
char[3] units = ['K', 'M', 'G'];
98+
99+
foreach (u; units)
100+
if (val % (1 << 10) == 0)
101+
{
102+
val /= (1 << 10);
103+
sym = u;
104+
}
105+
else if (sym != 'B')
106+
break;
107+
108+
return sym;
109+
}
110+
unittest
111+
{
112+
size_t v = 1024;
113+
assert(prettyBytes(v) == 'K');
114+
assert(v == 1);
115+
116+
v = 1025;
117+
assert(prettyBytes(v) == 'B');
118+
assert(v == 1025);
119+
120+
v = 1024UL * 1024 * 1024 * 3;
121+
assert(prettyBytes(v) == 'G');
122+
assert(v == 3);
123+
124+
v = 1024 * 1024 + 1;
125+
assert(prettyBytes(v) == 'B');
126+
assert(v == 1024 * 1024 + 1);
127+
}

src/core/internal/parseoptions.d

Lines changed: 108 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import core.stdc.stdio;
1414
import core.stdc.ctype;
1515
import core.stdc.string;
1616
import core.vararg;
17-
import core.internal.traits : externDFunc;
17+
import core.internal.traits : externDFunc, hasUDA;
1818

1919

2020
@nogc nothrow:
@@ -28,6 +28,9 @@ alias rt_configCallBack = string delegate(string) @nogc nothrow;
2828
alias fn_configOption = string function(string opt, scope rt_configCallBack dg, bool reverse) @nogc nothrow;
2929
alias rt_configOption = externDFunc!("rt.config.rt_configOption", fn_configOption);
3030

31+
/// UDA for field treated as memory value
32+
struct MemVal {}
33+
3134
/**
3235
* initialize members of struct CFG from rt_config options
3336
*
@@ -87,19 +90,27 @@ bool parseOptions(CFG)(ref CFG cfg, string opt)
8790
return optError("Missing argument for", name, errName);
8891
tail = tail[1 .. $];
8992

93+
NAMES_SWITCH:
9094
switch (name)
9195
{
92-
foreach (field; __traits(allMembers, CFG))
96+
static foreach (field; __traits(allMembers, CFG))
9397
{
9498
static if (!is(typeof(__traits(getMember, cfg, field)) == function))
9599
{
96100
case field:
97-
if (!parse(name, tail, __traits(getMember, cfg, field), errName))
101+
bool r;
102+
103+
static if (hasUDA!(__traits(getMember, cfg, field), MemVal))
104+
r = parse(name, tail, __traits(getMember, cfg, field), errName, true);
105+
else
106+
r = parse(name, tail, __traits(getMember, cfg, field), errName);
107+
108+
if (!r)
98109
return false;
99-
break;
110+
111+
break NAMES_SWITCH;
100112
}
101113
}
102-
break;
103114

104115
default:
105116
return optError("Unknown", name, errName);
@@ -156,16 +167,78 @@ inout(char)[] find(alias pred)(inout(char)[] str)
156167
return null;
157168
}
158169

159-
bool parse(T:size_t)(const(char)[] optname, ref inout(char)[] str, ref T res, const(char)[] errName)
170+
bool parse(T : size_t)(const(char)[] optname, ref inout(char)[] str, ref T res, const(char)[] errName, bool mayHaveSuffix = false)
160171
in { assert(str.length); }
161172
do
162173
{
163174
size_t i, v;
164-
for (; i < str.length && isdigit(str[i]); ++i)
165-
v = 10 * v + str[i] - '0';
175+
176+
auto tail = find!(c => c == ' ')(str);
177+
size_t len = str.length - tail.length;
178+
179+
import core.checkedint : mulu;
180+
181+
bool overflowed;
182+
183+
for (; i < len; i++)
184+
{
185+
char c = str[i];
186+
187+
if (isdigit(c))
188+
v = 10 * v + c - '0';
189+
else // non-digit
190+
{
191+
if (mayHaveSuffix && i == len-1) // suffix
192+
{
193+
switch (c)
194+
{
195+
196+
case 'G':
197+
v = mulu(v, 1024 * 1024 * 1024, overflowed);
198+
break;
199+
200+
case 'M':
201+
v = mulu(v, 1024 * 1024, overflowed);
202+
break;
203+
204+
case 'K':
205+
v = mulu(v, 1024, overflowed);
206+
break;
207+
208+
case 'B':
209+
break;
210+
211+
default:
212+
return parseError("value with unit type M, K or B", optname, str, "with suffix");
213+
}
214+
215+
if (overflowed)
216+
return overflowedError(optname, str);
217+
218+
i++;
219+
break;
220+
}
221+
else // unexpected non-digit character
222+
{
223+
i = 0;
224+
break;
225+
}
226+
}
227+
}
166228

167229
if (!i)
168230
return parseError("a number", optname, str, errName);
231+
232+
if (mayHaveSuffix && isdigit(str[len-1]))
233+
{
234+
// No suffix found, default to megabytes
235+
236+
v = mulu(v, 1024 * 1024, overflowed);
237+
238+
if (overflowed)
239+
return overflowedError(optname, str);
240+
}
241+
169242
if (v > res.max)
170243
return parseError("a number " ~ T.max.stringof ~ " or below", optname, str[0 .. i], errName);
171244
str = str[i .. $];
@@ -252,6 +325,16 @@ bool parseError(const scope char[] exp, const scope char[] opt, const scope char
252325
return false;
253326
}
254327

328+
bool overflowedError(const scope char[] opt, const scope char[] got)
329+
{
330+
version (CoreUnittest) if (inUnittest) return false;
331+
332+
fprintf(stderr, "Argument for %.*s option '%.*s' is too big.\n",
333+
cast(int)opt.length, opt.ptr,
334+
cast(int)got.length, got.ptr);
335+
return false;
336+
}
337+
255338
size_t min(size_t a, size_t b) { return a <= b ? a : b; }
256339

257340
version (CoreUnittest) __gshared bool inUnittest;
@@ -267,8 +350,8 @@ unittest
267350
ubyte profile; // enable profiling with summary when terminating program
268351
string gc = "conservative"; // select gc implementation conservative|manual
269352

270-
size_t initReserve; // initial reserve (MB)
271-
size_t minPoolSize = 1; // initial and minimum pool size (MB)
353+
@MemVal size_t initReserve; // initial reserve (bytes)
354+
@MemVal size_t minPoolSize = 1 << 20; // initial and minimum pool size (bytes)
272355
float heapSizeFactor = 2.0; // heap size to used memory ratio
273356

274357
@nogc nothrow:
@@ -297,7 +380,21 @@ unittest
297380

298381
assert(conf.parseOptions("disable:1 minPoolSize:16"));
299382
assert(conf.disable);
300-
assert(conf.minPoolSize == 16);
383+
assert(conf.minPoolSize == 1024 * 1024 * 16);
384+
385+
assert(conf.parseOptions("disable:1 minPoolSize:4096B"));
386+
assert(conf.disable);
387+
assert(conf.minPoolSize == 4096);
388+
389+
assert(conf.parseOptions("disable:1 minPoolSize:2K help"));
390+
assert(conf.disable);
391+
assert(conf.minPoolSize == 2048);
392+
393+
assert(conf.parseOptions("minPoolSize:3G help"));
394+
assert(conf.disable);
395+
assert(conf.minPoolSize == 1024UL * 1024 * 1024 * 3);
396+
397+
assert(!conf.parseOptions("minPoolSize:922337203685477G"), "size_t overflow");
301398

302399
assert(conf.parseOptions("heapSizeFactor:3.1"));
303400
assert(conf.heapSizeFactor == 3.1f);

src/core/internal/traits.d

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -794,3 +794,33 @@ unittest
794794
static assert(!isTrue!(T, "g"));
795795
static assert(!isTrue!(T, "h"));
796796
}
797+
798+
template hasUDA(alias symbol, alias attribute)
799+
{
800+
alias attrs = __traits(getAttributes, symbol);
801+
802+
static foreach (a; attrs)
803+
{
804+
static if (is(a == attribute))
805+
{
806+
enum hasUDA = true;
807+
}
808+
}
809+
810+
static if (!__traits(compiles, (hasUDA == true)))
811+
enum hasUDA = false;
812+
}
813+
814+
unittest
815+
{
816+
struct SomeUDA{}
817+
818+
struct Test
819+
{
820+
int woUDA;
821+
@SomeUDA int withUDA;
822+
}
823+
824+
static assert(hasUDA!(Test.withUDA, SomeUDA));
825+
static assert(!hasUDA!(Test.woUDA, SomeUDA));
826+
}

src/gc/impl/conservative/gc.d

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ class ConservativeGC : GC
173173
gcx.initialize();
174174

175175
if (config.initReserve)
176-
gcx.reserve(config.initReserve << 20);
176+
gcx.reserve(config.initReserve);
177177
if (config.disable)
178178
gcx.disabled++;
179179
}
@@ -1804,7 +1804,7 @@ struct Gcx
18041804
//debug(PRINTF) printf("************Gcx::newPool(npages = %d)****************\n", npages);
18051805

18061806
// Minimum of POOLSIZE
1807-
size_t minPages = (config.minPoolSize << 20) / PAGESIZE;
1807+
size_t minPages = config.minPoolSize / PAGESIZE;
18081808
if (npages < minPages)
18091809
npages = minPages;
18101810
else if (npages > minPages)
@@ -1821,7 +1821,7 @@ struct Gcx
18211821
n = config.minPoolSize + config.incPoolSize * npools;
18221822
if (n > config.maxPoolSize)
18231823
n = config.maxPoolSize; // cap pool size
1824-
n *= (1 << 20) / PAGESIZE; // convert MB to pages
1824+
n /= PAGESIZE; // convert bytes to pages
18251825
if (npages < n)
18261826
npages = n;
18271827
}

0 commit comments

Comments
 (0)