1 ///
2 module std.experimental.allocator.building_blocks.segregator;
3
4 import std.experimental.allocator.common;
5
6 /**
7 Dispatches allocations (and deallocations) between two allocators ($(D
8 SmallAllocator) and $(D LargeAllocator)) depending on the size allocated, as
9 follows. All allocations smaller than or equal to $(D threshold) will be
10 dispatched to $(D SmallAllocator). The others will go to $(D LargeAllocator).
11
12 If both allocators are $(D shared), the $(D Segregator) will also offer $(D
13 shared) methods.
14 */
Segregator(size_t threshold,SmallAllocator,LargeAllocator)15 struct Segregator(size_t threshold, SmallAllocator, LargeAllocator)
16 {
17 import std.algorithm.comparison : min;
18 import std.traits : hasMember;
19 import std.typecons : Ternary;
20
21 static if (stateSize!SmallAllocator) private SmallAllocator _small;
22 else private alias _small = SmallAllocator.instance;
23 static if (stateSize!LargeAllocator) private LargeAllocator _large;
24 else private alias _large = LargeAllocator.instance;
25
26 version (StdDdoc)
27 {
28 /**
29 The alignment offered is the minimum of the two allocators' alignment.
30 */
31 enum uint alignment;
32 /**
33 This method is defined only if at least one of the allocators defines
34 it. The good allocation size is obtained from $(D SmallAllocator) if $(D
35 s <= threshold), or $(D LargeAllocator) otherwise. (If one of the
36 allocators does not define $(D goodAllocSize), the default
37 implementation in this module applies.)
38 */
39 static size_t goodAllocSize(size_t s);
40 /**
41 The memory is obtained from $(D SmallAllocator) if $(D s <= threshold),
42 or $(D LargeAllocator) otherwise.
43 */
44 void[] allocate(size_t);
45 /**
46 This method is defined if both allocators define it, and forwards to
47 $(D SmallAllocator) or $(D LargeAllocator) appropriately.
48 */
49 void[] alignedAllocate(size_t, uint);
50 /**
51 This method is defined only if at least one of the allocators defines
52 it. If $(D SmallAllocator) defines $(D expand) and $(D b.length +
53 delta <= threshold), the call is forwarded to $(D SmallAllocator). If $(D
54 LargeAllocator) defines $(D expand) and $(D b.length > threshold), the
55 call is forwarded to $(D LargeAllocator). Otherwise, the call returns
56 $(D false).
57 */
58 bool expand(ref void[] b, size_t delta);
59 /**
60 This method is defined only if at least one of the allocators defines
61 it. If $(D SmallAllocator) defines $(D reallocate) and $(D b.length <=
62 threshold && s <= threshold), the call is forwarded to $(D
63 SmallAllocator). If $(D LargeAllocator) defines $(D expand) and $(D
64 b.length > threshold && s > threshold), the call is forwarded to $(D
65 LargeAllocator). Otherwise, the call returns $(D false).
66 */
67 bool reallocate(ref void[] b, size_t s);
68 /**
69 This method is defined only if at least one of the allocators defines
70 it, and work similarly to $(D reallocate).
71 */
72 bool alignedReallocate(ref void[] b, size_t s);
73 /**
74 This method is defined only if both allocators define it. The call is
75 forwarded to $(D SmallAllocator) if $(D b.length <= threshold), or $(D
76 LargeAllocator) otherwise.
77 */
78 Ternary owns(void[] b);
79 /**
80 This function is defined only if both allocators define it, and forwards
81 appropriately depending on $(D b.length).
82 */
83 bool deallocate(void[] b);
84 /**
85 This function is defined only if both allocators define it, and calls
86 $(D deallocateAll) for them in turn.
87 */
88 bool deallocateAll();
89 /**
90 This function is defined only if both allocators define it, and returns
91 the conjunction of $(D empty) calls for the two.
92 */
93 Ternary empty();
94 }
95
96 /**
97 Composite allocators involving nested instantiations of $(D Segregator) make
98 it difficult to access individual sub-allocators stored within. $(D
99 allocatorForSize) simplifies the task by supplying the allocator nested
100 inside a $(D Segregator) that is responsible for a specific size $(D s).
101
102 Example:
103 ----
104 alias A = Segregator!(300,
105 Segregator!(200, A1, A2),
106 A3);
107 A a;
108 static assert(typeof(a.allocatorForSize!10) == A1);
109 static assert(typeof(a.allocatorForSize!250) == A2);
110 static assert(typeof(a.allocatorForSize!301) == A3);
111 ----
112 */
113 ref auto allocatorForSize(size_t s)()
114 {
115 static if (s <= threshold)
116 static if (is(SmallAllocator == Segregator!(Args), Args...))
117 return _small.allocatorForSize!s;
118 else return _small;
119 else
120 static if (is(LargeAllocator == Segregator!(Args), Args...))
121 return _large.allocatorForSize!s;
122 else return _large;
123 }
124
125 enum uint alignment = min(SmallAllocator.alignment,
126 LargeAllocator.alignment);
127
128 private template Impl()
129 {
130 size_t goodAllocSize(size_t s)
131 {
132 return s <= threshold
133 ? _small.goodAllocSize(s)
134 : _large.goodAllocSize(s);
135 }
136
137 void[] allocate(size_t s)
138 {
139 return s <= threshold ? _small.allocate(s) : _large.allocate(s);
140 }
141
142 static if (hasMember!(SmallAllocator, "alignedAllocate")
143 && hasMember!(LargeAllocator, "alignedAllocate"))
144 void[] alignedAllocate(size_t s, uint a)
145 {
146 return s <= threshold
147 ? _small.alignedAllocate(s, a)
148 : _large.alignedAllocate(s, a);
149 }
150
151 static if (hasMember!(SmallAllocator, "expand")
152 || hasMember!(LargeAllocator, "expand"))
153 bool expand(ref void[] b, size_t delta)
154 {
155 if (!delta) return true;
156 if (b.length + delta <= threshold)
157 {
158 // Old and new allocations handled by _small
159 static if (hasMember!(SmallAllocator, "expand"))
160 return _small.expand(b, delta);
161 else
162 return false;
163 }
164 if (b.length > threshold)
165 {
166 // Old and new allocations handled by _large
167 static if (hasMember!(LargeAllocator, "expand"))
168 return _large.expand(b, delta);
169 else
170 return false;
171 }
172 // Oops, cross-allocator transgression
173 return false;
174 }
175
176 static if (hasMember!(SmallAllocator, "reallocate")
177 || hasMember!(LargeAllocator, "reallocate"))
178 bool reallocate(ref void[] b, size_t s)
179 {
180 static if (hasMember!(SmallAllocator, "reallocate"))
181 if (b.length <= threshold && s <= threshold)
182 {
183 // Old and new allocations handled by _small
184 return _small.reallocate(b, s);
185 }
186 static if (hasMember!(LargeAllocator, "reallocate"))
187 if (b.length > threshold && s > threshold)
188 {
189 // Old and new allocations handled by _large
190 return _large.reallocate(b, s);
191 }
192 // Cross-allocator transgression
193 return .reallocate(this, b, s);
194 }
195
196 static if (hasMember!(SmallAllocator, "alignedReallocate")
197 || hasMember!(LargeAllocator, "alignedReallocate"))
198 bool reallocate(ref void[] b, size_t s)
199 {
200 static if (hasMember!(SmallAllocator, "alignedReallocate"))
201 if (b.length <= threshold && s <= threshold)
202 {
203 // Old and new allocations handled by _small
204 return _small.alignedReallocate(b, s);
205 }
206 static if (hasMember!(LargeAllocator, "alignedReallocate"))
207 if (b.length > threshold && s > threshold)
208 {
209 // Old and new allocations handled by _large
210 return _large.alignedReallocate(b, s);
211 }
212 // Cross-allocator transgression
213 return .alignedReallocate(this, b, s);
214 }
215
216 static if (hasMember!(SmallAllocator, "owns")
217 && hasMember!(LargeAllocator, "owns"))
218 Ternary owns(void[] b)
219 {
220 return Ternary(b.length <= threshold
221 ? _small.owns(b) : _large.owns(b));
222 }
223
224 static if (hasMember!(SmallAllocator, "deallocate")
225 && hasMember!(LargeAllocator, "deallocate"))
226 bool deallocate(void[] data)
227 {
228 return data.length <= threshold
229 ? _small.deallocate(data)
230 : _large.deallocate(data);
231 }
232
233 static if (hasMember!(SmallAllocator, "deallocateAll")
234 && hasMember!(LargeAllocator, "deallocateAll"))
235 bool deallocateAll()
236 {
237 // Use & insted of && to evaluate both
238 return _small.deallocateAll() & _large.deallocateAll();
239 }
240
241 static if (hasMember!(SmallAllocator, "empty")
242 && hasMember!(LargeAllocator, "empty"))
243 Ternary empty()
244 {
245 return _small.empty && _large.empty;
246 }
247
248 static if (hasMember!(SmallAllocator, "resolveInternalPointer")
249 && hasMember!(LargeAllocator, "resolveInternalPointer"))
250 Ternary resolveInternalPointer(const void* p, ref void[] result)
251 {
252 Ternary r = _small.resolveInternalPointer(p, result);
253 return r == Ternary.no ? _large.resolveInternalPointer(p, result) : r;
254 }
255 }
256
257 private enum sharedMethods =
258 !stateSize!SmallAllocator
259 && !stateSize!LargeAllocator
260 && is(typeof(SmallAllocator.instance) == shared)
261 && is(typeof(LargeAllocator.instance) == shared);
262
263 static if (sharedMethods)
264 {
265 static shared Segregator instance;
266 shared { mixin Impl!(); }
267 }
268 else
269 {
270 static if (!stateSize!SmallAllocator && !stateSize!LargeAllocator)
271 static __gshared Segregator instance;
272 mixin Impl!();
273 }
274 }
275
276 ///
277 @system unittest
278 {
279 import std.experimental.allocator.building_blocks.free_list : FreeList;
280 import std.experimental.allocator.gc_allocator : GCAllocator;
281 import std.experimental.allocator.mallocator : Mallocator;
282 alias A =
283 Segregator!(
284 1024 * 4,
285 Segregator!(
286 128, FreeList!(Mallocator, 0, 128),
287 GCAllocator),
288 Segregator!(
289 1024 * 1024, Mallocator,
290 GCAllocator)
291 );
292 A a;
293 auto b = a.allocate(200);
294 assert(b.length == 200);
295 a.deallocate(b);
296 }
297
298 /**
299 A $(D Segregator) with more than three arguments expands to a composition of
300 elemental $(D Segregator)s, as illustrated by the following example:
301
302 ----
303 alias A =
304 Segregator!(
305 n1, A1,
306 n2, A2,
307 n3, A3,
308 A4
309 );
310 ----
311
312 With this definition, allocation requests for $(D n1) bytes or less are directed
313 to $(D A1); requests between $(D n1 + 1) and $(D n2) bytes (inclusive) are
314 directed to $(D A2); requests between $(D n2 + 1) and $(D n3) bytes (inclusive)
315 are directed to $(D A3); and requests for more than $(D n3) bytes are directed
316 to $(D A4). If some particular range should not be handled, $(D NullAllocator)
317 may be used appropriately.
318
319 */
320 template Segregator(Args...)
321 if (Args.length > 3)
322 {
323 // Binary search
324 private enum cutPoint = ((Args.length - 2) / 4) * 2;
325 static if (cutPoint >= 2)
326 {
327 alias Segregator = .Segregator!(
328 Args[cutPoint],
329 .Segregator!(Args[0 .. cutPoint], Args[cutPoint + 1]),
330 .Segregator!(Args[cutPoint + 2 .. $])
331 );
332 }
333 else
334 {
335 // Favor small sizes
336 alias Segregator = .Segregator!(
337 Args[0],
338 Args[1],
339 .Segregator!(Args[2 .. $])
340 );
341 }
342 }
343
344 ///
345 @system unittest
346 {
347 import std.experimental.allocator.building_blocks.free_list : FreeList;
348 import std.experimental.allocator.gc_allocator : GCAllocator;
349 import std.experimental.allocator.mallocator : Mallocator;
350 alias A =
351 Segregator!(
352 128, FreeList!(Mallocator, 0, 128),
353 1024 * 4, GCAllocator,
354 1024 * 1024, Mallocator,
355 GCAllocator
356 );
357 A a;
358 auto b = a.allocate(201);
359 assert(b.length == 201);
360 a.deallocate(b);
361 }
362