1 | /** |
2 | * Copyright (c) Glow Contributors. See CONTRIBUTORS file. |
3 | * |
4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
5 | * you may not use this file except in compliance with the License. |
6 | * You may obtain a copy of the License at |
7 | * |
8 | * http://www.apache.org/licenses/LICENSE-2.0 |
9 | * |
10 | * Unless required by applicable law or agreed to in writing, software |
11 | * distributed under the License is distributed on an "AS IS" BASIS, |
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 | * See the License for the specific language governing permissions and |
14 | * limitations under the License. |
15 | */ |
16 | |
17 | #include "glow/CodeGen/MemoryAllocator.h" |
18 | |
19 | #include "gtest/gtest.h" |
20 | |
21 | #include <fstream> |
22 | |
23 | #ifndef GLOW_DATA_PATH |
24 | #define GLOW_DATA_PATH |
25 | #endif |
26 | |
27 | using namespace glow; |
28 | |
29 | TEST(MemAlloc, simple) { |
30 | MemoryAllocator MA("test" , 1000); |
31 | void *handle = reinterpret_cast<void *>(1); |
32 | |
33 | // Can't allocate huge chunks. |
34 | EXPECT_EQ(MA.allocate(100000, handle), MemoryAllocator::npos); |
35 | |
36 | // First chunk needs to start at zero. |
37 | EXPECT_EQ(MA.allocate(500, handle), 0); |
38 | |
39 | // Second chunk must not be zero. |
40 | EXPECT_NE(MA.allocate(500, handle), 0); |
41 | } |
42 | |
43 | TEST(MemAlloc, holes) { |
44 | MemoryAllocator MA("test" , 1000); |
45 | void *handle0 = reinterpret_cast<void *>(0); |
46 | void *handle1 = reinterpret_cast<void *>(1); |
47 | void *handle2 = reinterpret_cast<void *>(2); |
48 | void *handle4 = reinterpret_cast<void *>(4); |
49 | |
50 | MA.allocate(10, handle0); |
51 | auto p1 = MA.allocate(10, handle1); |
52 | MA.allocate(10, handle2); |
53 | |
54 | MA.deallocate(handle1); |
55 | auto maxMemoryUsageBefore = MA.getMaxMemoryUsage(); |
56 | auto p4 = MA.allocate(10, handle4); |
57 | auto maxMemoryUsageAfter = MA.getMaxMemoryUsage(); |
58 | |
59 | // Check that p4 was allocated on top of the freed p1. |
60 | EXPECT_EQ(p4, p1); |
61 | // Max memory usage should not be affected, as a hole was found and used. |
62 | EXPECT_EQ(maxMemoryUsageBefore, maxMemoryUsageAfter); |
63 | |
64 | MA.deallocate(handle0); |
65 | MA.deallocate(handle2); |
66 | } |
67 | |
68 | /// Check some properties of the first-fit allocation strategy. |
69 | TEST(MemAlloc, firstFitAllocation) { |
70 | MemoryAllocator MA("test" , 1000); |
71 | void *handle = reinterpret_cast<void *>(10000); |
72 | void *handle0 = reinterpret_cast<void *>(0); |
73 | void *handle1 = reinterpret_cast<void *>(1); |
74 | void *handle2 = reinterpret_cast<void *>(2); |
75 | void *handle3 = reinterpret_cast<void *>(3); |
76 | void *handle4 = reinterpret_cast<void *>(4); |
77 | |
78 | // Allocate three blocks of sizes 30, 20 and 10 and allocate blocks of size 5 |
79 | // between them. |
80 | auto p0 = MA.allocate(30, handle0); |
81 | MA.allocate(5, handle); |
82 | MA.allocate(20, handle1); |
83 | MA.allocate(5, handle); |
84 | auto p2 = MA.allocate(10, handle2); |
85 | MA.allocate(5, handle); |
86 | |
87 | // Free blocks p0, p1 and p2. |
88 | MA.deallocate(handle0); |
89 | MA.deallocate(handle1); |
90 | MA.deallocate(handle2); |
91 | |
92 | // Try to allocate a block of size 10. |
93 | auto maxMemoryUsageBefore = MA.getMaxMemoryUsage(); |
94 | auto p3 = MA.allocate(10, handle3); |
95 | auto maxMemoryUsageAfter = MA.getMaxMemoryUsage(); |
96 | |
97 | // Check that p4 was allocated on top of the freed p0, because the allocator |
98 | // uses the first-fit algorithm. Best-fit would have taken the block of p2. |
99 | EXPECT_EQ(p3, p0); |
100 | // Max memory usage should not be affected, as a hole was found and used. |
101 | EXPECT_EQ(maxMemoryUsageBefore, maxMemoryUsageAfter); |
102 | |
103 | // Allocate 100 bytes. Since the first-fit cannot find any big enough hole |
104 | // between allocations, the allocator would allocate this block in the free |
105 | // space after all existing allocations. |
106 | maxMemoryUsageBefore = MA.getMaxMemoryUsage(); |
107 | auto p4 = MA.allocate(100, handle4); |
108 | maxMemoryUsageAfter = MA.getMaxMemoryUsage(); |
109 | EXPECT_GT(p4, p2); |
110 | // Max memory usage should be increased. |
111 | EXPECT_LT(maxMemoryUsageBefore, maxMemoryUsageAfter); |
112 | } |
113 | |
114 | TEST(MemAlloc, dealloc) { |
115 | MemoryAllocator MA("test" , 1000); |
116 | void *handle0 = reinterpret_cast<void *>(0); |
117 | void *handle1 = reinterpret_cast<void *>(1); |
118 | void *handle2 = reinterpret_cast<void *>(2); |
119 | void *handle3 = reinterpret_cast<void *>(3); |
120 | void *handle4 = reinterpret_cast<void *>(4); |
121 | void *handle5 = reinterpret_cast<void *>(5); |
122 | void *handle6 = reinterpret_cast<void *>(6); |
123 | |
124 | auto p0 = MA.allocate(10, handle0); |
125 | auto p1 = MA.allocate(10, handle1); |
126 | auto p2 = MA.allocate(10, handle2); |
127 | auto p3 = MA.allocate(10, handle3); |
128 | |
129 | auto p4 = MA.allocate(10, handle4); |
130 | |
131 | auto p5 = MA.allocate(0, handle5); |
132 | auto p6 = MA.allocate(0, handle6); |
133 | |
134 | EXPECT_EQ(p0, 0); |
135 | EXPECT_NE(p1, MemoryAllocator::npos); |
136 | EXPECT_NE(p2, MemoryAllocator::npos); |
137 | EXPECT_NE(p3, MemoryAllocator::npos); |
138 | EXPECT_NE(p4, MemoryAllocator::npos); |
139 | EXPECT_NE(p5, MemoryAllocator::npos); |
140 | EXPECT_NE(p6, MemoryAllocator::npos); |
141 | |
142 | // Deallocate in some arbitrary order. |
143 | MA.deallocate(handle0); |
144 | MA.deallocate(handle5); |
145 | MA.deallocate(handle2); |
146 | MA.deallocate(handle1); |
147 | MA.deallocate(handle6); |
148 | MA.deallocate(handle3); |
149 | // Check that it is possible to deallocate using the associated handle. |
150 | MA.deallocate(handle4); |
151 | #ifndef NDEBUG |
152 | // Check that deallocating a non-allocated or already deallocated buffer |
153 | // should result in an assertion failure. |
154 | ASSERT_DEATH_IF_SUPPORTED(MA.deallocate(handle3), "Unknown handle" ); |
155 | #endif |
156 | // Check that after deallocating everything we start allocating from zero. |
157 | EXPECT_EQ(MA.allocate(10, handle0), 0); |
158 | } |
159 | |
160 | TEST(MemAlloc, dealloc2) { |
161 | MemoryAllocator MA("test" , 10000); |
162 | std::vector<uint64_t> allocations; |
163 | |
164 | for (int i = 0; i < 100; i++) { |
165 | // Create odd-sized allocations. |
166 | const void *handle = reinterpret_cast<void *>(i); |
167 | auto p0 = MA.allocate(10 + i % 4, handle); |
168 | EXPECT_TRUE(MA.hasAddress(handle)); |
169 | EXPECT_TRUE(MA.hasHandle(p0)); |
170 | |
171 | EXPECT_NE(p0, MemoryAllocator::npos); |
172 | allocations.emplace_back(p0); |
173 | |
174 | if (allocations.size() > 20) { |
175 | MA.deallocate(MA.getHandle(allocations[0])); |
176 | allocations.erase(allocations.begin()); |
177 | } |
178 | } |
179 | // Drain the allocator. |
180 | while (!allocations.empty()) { |
181 | MA.deallocate(MA.getHandle(allocations[0])); |
182 | allocations.erase(allocations.begin()); |
183 | } |
184 | |
185 | // Check that after deallocating everything we start allocating from zero. |
186 | const void *handle = reinterpret_cast<void *>(0); |
187 | EXPECT_EQ(MA.allocate(10, handle), 0); |
188 | } |
189 | |
190 | TEST(MemAlloc, allocateToTheMax) { |
191 | MemoryAllocator MA("test" , 128); |
192 | void *handle0 = reinterpret_cast<void *>(0); |
193 | void *handle1 = reinterpret_cast<void *>(1); |
194 | auto p0 = MA.allocate(64, handle0); |
195 | auto p1 = MA.allocate(64, handle1); |
196 | |
197 | EXPECT_EQ(p0, 0); |
198 | EXPECT_NE(p1, MemoryAllocator::npos); |
199 | |
200 | MA.deallocate(handle0); |
201 | MA.deallocate(handle1); |
202 | |
203 | EXPECT_EQ(MA.getMaxMemoryUsage(), 128); |
204 | } |
205 | |
206 | TEST(MemAlloc, testContains) { |
207 | MemoryAllocator MA("test" , 1000); |
208 | void *handle = reinterpret_cast<void *>(0); |
209 | |
210 | EXPECT_EQ(MA.allocate(200, handle), 0); |
211 | |
212 | // Offset 100 should be inside an allocated block. |
213 | EXPECT_TRUE(MA.contains(100)); |
214 | // Offset 300 should not be inside an allocated block. |
215 | EXPECT_FALSE(MA.contains(300)); |
216 | } |
217 | |
218 | TEST(MemAlloc, testHandles) { |
219 | MemoryAllocator MA("test" , 1000); |
220 | // Define a set of handles to be used. |
221 | void *handle1 = reinterpret_cast<void *>(1); |
222 | void *handle2 = reinterpret_cast<void *>(2); |
223 | (void)handle2; |
224 | void *handle3 = reinterpret_cast<void *>(3); |
225 | // Allocate a block of memory p1, but do not associate any handle with it. |
226 | auto p1 = MA.allocate(10, handle1); |
227 | // Check that handle1 is associated with the allocated address p1. |
228 | EXPECT_EQ(MA.getHandle(p1), handle1); |
229 | // Check that the address p1 is associated with the handle handle1. |
230 | EXPECT_EQ(MA.getAddress(handle1), p1); |
231 | |
232 | // Allocate a block of memory p3 and associate a handle handle3 with it. |
233 | auto p3 = MA.allocate(10, handle3); |
234 | // The associated handle of p3 should be handle3. |
235 | EXPECT_EQ(MA.getHandle(p3), handle3); |
236 | // The address associated with handle3 should be p3. |
237 | EXPECT_EQ(MA.getAddress(handle3), p3); |
238 | // Deallocate the memory. |
239 | MA.deallocate(handle3); |
240 | // Check that after deallocation there is no handle is associated with the |
241 | // allocated address. |
242 | EXPECT_FALSE(MA.hasHandle(p3)); |
243 | // Check that after deallocation there is no address is associated with |
244 | // handle3. |
245 | EXPECT_FALSE(MA.hasAddress(handle3)); |
246 | |
247 | MA.reset(); |
248 | p1 = MA.allocate(10, handle1); |
249 | #ifndef NDEBUG |
250 | // Deallocating handle2 should result in an assertion failure. |
251 | ASSERT_DEATH_IF_SUPPORTED(MA.deallocate(handle2), "Unknown handle" ); |
252 | #endif |
253 | } |
254 | |
255 | TEST(MemAlloc, testEviction) { |
256 | MemoryAllocator MA("test" , 1024); |
257 | // Define a set of handles to be used. |
258 | void *handle1 = reinterpret_cast<void *>(1); |
259 | void *handle2 = reinterpret_cast<void *>(2); |
260 | void *handle3 = reinterpret_cast<void *>(3); |
261 | void *handle4 = reinterpret_cast<void *>(4); |
262 | void *handle5 = reinterpret_cast<void *>(5); |
263 | std::vector<const void *> evicted; |
264 | |
265 | // Allocation of 500 from 1000 bytes should not trigger any eviction. |
266 | auto p1 = MA.allocate(500, handle1, {}, evicted); |
267 | EXPECT_NE(p1, MA.npos); |
268 | EXPECT_TRUE(evicted.empty()); |
269 | |
270 | // Allocation of 400 from remaining 500 bytes should not trigger any eviction. |
271 | auto p2 = MA.allocate(400, handle2, {}, evicted); |
272 | EXPECT_NE(p2, MA.npos); |
273 | EXPECT_TRUE(evicted.empty()); |
274 | |
275 | // Allocation of 400 from remaining 100 bytes should trigger the eviction. |
276 | auto p3 = MA.allocate(400, handle3, {}, evicted); |
277 | // The allocation should be successful. |
278 | EXPECT_NE(p3, MA.npos); |
279 | EXPECT_EQ(evicted.size(), 1); |
280 | |
281 | // Allocation of 2000 bytes is impossible. It should not should trigger any |
282 | // eviction. |
283 | evicted.clear(); |
284 | auto p4 = MA.allocate(2000, handle4, {}, evicted); |
285 | EXPECT_EQ(p4, MA.npos); |
286 | EXPECT_EQ(evicted.size(), 0); |
287 | |
288 | // Allocation of 1024 bytes only possible if all other allocated blocks are |
289 | // evicted. |
290 | evicted.clear(); |
291 | auto p5 = MA.allocate(1024, handle5, {}, evicted); |
292 | EXPECT_NE(p5, MA.npos); |
293 | EXPECT_EQ(evicted.size(), 2); |
294 | |
295 | // Check how eviction works with a non-empty doNotEvict set. |
296 | MA.reset(); |
297 | evicted.clear(); |
298 | // Allocate 3 blocks, 256 bytes each. |
299 | p1 = MA.allocate(256, handle1, {}, evicted); |
300 | EXPECT_EQ(p1, 0); |
301 | p2 = MA.allocate(256, handle2, {}, evicted); |
302 | EXPECT_EQ(p2, 256); |
303 | p3 = MA.allocate(256, handle3, {}, evicted); |
304 | EXPECT_EQ(p3, 512); |
305 | // No blocks should be evicted until now. |
306 | EXPECT_EQ(evicted.size(), 0); |
307 | // Try to allocate a block of size 512. Without a doNotEvict set and using a |
308 | // first-fit eviction strategy, the allocator would have to evict blocks p1 |
309 | // and p2 to satisfy this request. But due to providing a doNotEvict set which |
310 | // forbids the eviction of p1, the allocator should evict p2 and p3 and |
311 | // allocate the 512 bytes at the same address as p2. |
312 | std::set<const void *> doNotEvict{handle1}; |
313 | p4 = MA.allocate(512, handle4, doNotEvict, evicted); |
314 | EXPECT_EQ(p4, p2); |
315 | EXPECT_EQ(evicted.size(), 2); |
316 | EXPECT_EQ(evicted[0], handle2); |
317 | EXPECT_EQ(evicted[1], handle3); |
318 | } |
319 | |
320 | TEST(MemAlloc, testGetSize) { |
321 | MemoryAllocator MA("test" , 1024); |
322 | void *handle0 = reinterpret_cast<void *>(0); |
323 | void *handle1 = reinterpret_cast<void *>(1); |
324 | // Allocate two memory blocks and checks that the size of the allocated blocks |
325 | // is reported correctly. |
326 | MA.allocate(10, handle0); |
327 | EXPECT_EQ(MA.getSize(handle0), 10); |
328 | MA.allocate(200, handle1); |
329 | EXPECT_EQ(MA.getSize(handle1), 200); |
330 | } |
331 | |
332 | TEST(MemAlloc, testGetMemorySize) { |
333 | MemoryAllocator MA1("test1" , 1024); |
334 | EXPECT_EQ(MA1.getMemorySize(), 1024); |
335 | MemoryAllocator MA2("test1" , 102); |
336 | EXPECT_EQ(MA2.getMemorySize(), 102); |
337 | } |
338 | |
339 | TEST(MemAlloc, testAlignment) { |
340 | MemoryAllocator MA1("test1" , 1024, 128); |
341 | MemoryAllocator MA2("test2" , 1024, 256); |
342 | |
343 | void *handle0 = reinterpret_cast<void *>(0); |
344 | void *handle1 = reinterpret_cast<void *>(1); |
345 | void *handle2 = reinterpret_cast<void *>(2); |
346 | void *handle3 = reinterpret_cast<void *>(3); |
347 | |
348 | // Both allocators start at zero. |
349 | auto p0 = MA1.allocate(10, handle0); |
350 | auto p1 = MA2.allocate(10, handle1); |
351 | EXPECT_EQ(p0, 0); |
352 | EXPECT_EQ(p1, 0); |
353 | |
354 | // Second allocation starts at the alignment boundary. |
355 | auto p2 = MA1.allocate(10, handle2); |
356 | auto p3 = MA2.allocate(10, handle3); |
357 | EXPECT_EQ(p2, 128); |
358 | EXPECT_EQ(p3, 256); |
359 | } |
360 | |
361 | /// ---------------------------------------------------------------------------- |
362 | /// Allocate all segments at once |
363 | /// ---------------------------------------------------------------------------- |
364 | /// Test efficiency for allocateAll. |
365 | TEST(MemAlloc, testAllocateAllEfficiency) { |
366 | |
367 | void *handle0 = reinterpret_cast<void *>(0); |
368 | void *handle1 = reinterpret_cast<void *>(1); |
369 | void *handle2 = reinterpret_cast<void *>(2); |
370 | |
371 | // Simple allocate. |
372 | MemoryAllocator MA1("test" , 0, 10); |
373 | MA1.allocate(10, handle0); |
374 | MA1.allocate(10, handle1); |
375 | MA1.deallocate(handle0); |
376 | MA1.allocate(20, handle2); |
377 | MA1.deallocate(handle1); |
378 | MA1.deallocate(handle2); |
379 | EXPECT_EQ(MA1.getMaxMemoryUsage(), 40); |
380 | EXPECT_FLOAT_EQ(MA1.getAllocationEfficiency(), 0.75); |
381 | |
382 | // Allocate all. |
383 | MemoryAllocator MA2("test" , 0, 10); |
384 | std::list<Allocation> allocList; |
385 | allocList.emplace_back(handle0, true, 10); |
386 | allocList.emplace_back(handle1, true, 10); |
387 | allocList.emplace_back(handle0, false, 0); |
388 | allocList.emplace_back(handle2, true, 20); |
389 | allocList.emplace_back(handle1, false, 0); |
390 | allocList.emplace_back(handle2, false, 0); |
391 | MA2.allocateAll(allocList); |
392 | EXPECT_EQ(MA2.getMaxMemoryUsage(), 30); |
393 | EXPECT_FLOAT_EQ(MA2.getAllocationEfficiency(), 1.00); |
394 | } |
395 | |
396 | /// Test alignment for allocateAll. |
397 | TEST(MemAlloc, testAllocateAllAlignment) { |
398 | MemoryAllocator MA("test" , 192, 64); |
399 | |
400 | void *handle0 = reinterpret_cast<void *>(0); |
401 | void *handle1 = reinterpret_cast<void *>(1); |
402 | |
403 | std::list<Allocation> allocList; |
404 | allocList.emplace_back(handle0, true, 65); |
405 | allocList.emplace_back(handle1, true, 64); |
406 | allocList.emplace_back(handle0, false, 0); |
407 | allocList.emplace_back(handle1, false, 0); |
408 | uint64_t usedSize = MA.allocateAll(allocList); |
409 | |
410 | EXPECT_EQ(usedSize, 192); |
411 | EXPECT_EQ(MA.getSize(handle0), 65); |
412 | EXPECT_EQ(MA.getSize(handle1), 64); |
413 | EXPECT_EQ(MA.getAddress(handle0), 0); |
414 | EXPECT_EQ(MA.getAddress(handle1), 128); |
415 | EXPECT_EQ(MA.getSegment(handle0).size(), 65); |
416 | EXPECT_EQ(MA.getSegment(handle1).size(), 64); |
417 | EXPECT_EQ(MA.getSegment(handle0).begin, 0); |
418 | EXPECT_EQ(MA.getSegment(handle1).begin, 128); |
419 | EXPECT_EQ(MA.getMaxMemoryUsage(), 192); |
420 | EXPECT_FLOAT_EQ(MA.getAllocationEfficiency(), 1.0); |
421 | } |
422 | |
423 | /// Test invalid number of allocations for allocateAll. |
424 | TEST(MemAlloc, testAllocateAllInvalidNumAllocs) { |
425 | MemoryAllocator MA("test" , 0, 64); |
426 | void *handle = reinterpret_cast<void *>(0); |
427 | std::list<Allocation> allocList; |
428 | allocList.emplace_back(handle, true, 10); |
429 | #ifndef NDEBUG |
430 | ASSERT_DEATH_IF_SUPPORTED(MA.allocateAll(allocList), |
431 | "Allocations are invalid!" ); |
432 | #endif |
433 | } |
434 | |
435 | /// Test invalid FREE before ALLOC for allocateAll. |
436 | TEST(MemAlloc, testAllocateAllInvalidFreeBeforeAlloc) { |
437 | MemoryAllocator MA("test" , 0, 64); |
438 | void *handle = reinterpret_cast<void *>(0); |
439 | std::list<Allocation> allocList; |
440 | allocList.emplace_back(handle, false, 0); |
441 | allocList.emplace_back(handle, true, 10); |
442 | #ifndef NDEBUG |
443 | ASSERT_DEATH_IF_SUPPORTED(MA.allocateAll(allocList), |
444 | "Allocations are invalid!" ); |
445 | #endif |
446 | } |
447 | |
448 | /// Test invalid handles for allocateAll. |
449 | TEST(MemAlloc, testAllocateAllInvalidHandles1) { |
450 | MemoryAllocator MA("test" , 0, 64); |
451 | void *handle = reinterpret_cast<void *>(0); |
452 | std::list<Allocation> allocList; |
453 | allocList.emplace_back(handle, true, 10); |
454 | allocList.emplace_back(handle, true, 10); |
455 | allocList.emplace_back(handle, false, 0); |
456 | allocList.emplace_back(handle, false, 0); |
457 | #ifndef NDEBUG |
458 | ASSERT_DEATH_IF_SUPPORTED(MA.allocateAll(allocList), |
459 | "Allocations are invalid!" ); |
460 | #endif |
461 | } |
462 | |
463 | /// Test invalid handles for allocateAll. |
464 | TEST(MemAlloc, testAllocateAllInvalidHandles2) { |
465 | MemoryAllocator MA("test" , 0, 64); |
466 | void *handle0 = reinterpret_cast<void *>(0); |
467 | void *handle1 = reinterpret_cast<void *>(0); |
468 | std::list<Allocation> allocList; |
469 | allocList.emplace_back(handle0, true, 10); |
470 | allocList.emplace_back(handle0, true, 10); |
471 | allocList.emplace_back(handle1, false, 0); |
472 | allocList.emplace_back(handle1, false, 0); |
473 | #ifndef NDEBUG |
474 | ASSERT_DEATH_IF_SUPPORTED(MA.allocateAll(allocList), |
475 | "Allocations are invalid!" ); |
476 | #endif |
477 | } |
478 | |
479 | /// Test allocating segment of size 0 with allocateAll. |
480 | TEST(MemAlloc, testAllocateAllSize0) { |
481 | MemoryAllocator MA("test" , 0, 64); |
482 | void *handle = reinterpret_cast<void *>(0); |
483 | std::list<Allocation> allocList; |
484 | allocList.emplace_back(handle, true, 0); |
485 | allocList.emplace_back(handle, false, 0); |
486 | uint64_t usedSize = MA.allocateAll(allocList); |
487 | EXPECT_EQ(usedSize, 0); |
488 | EXPECT_EQ(MA.getSize(handle), 0); |
489 | EXPECT_EQ(MA.getAddress(handle), 0); |
490 | EXPECT_EQ(MA.getSegment(handle).size(), 0); |
491 | EXPECT_EQ(MA.getSegment(handle).begin, 0); |
492 | } |
493 | |
494 | /// Test allocating multiple segments of size 0 with allocateAll. |
495 | TEST(MemAlloc, testAllocateAllMultipleSize0) { |
496 | void *handle0 = reinterpret_cast<void *>(0); |
497 | void *handle1 = reinterpret_cast<void *>(1); |
498 | void *handle2 = reinterpret_cast<void *>(2); |
499 | void *handle3 = reinterpret_cast<void *>(3); |
500 | void *handle4 = reinterpret_cast<void *>(4); |
501 | void *handle5 = reinterpret_cast<void *>(5); |
502 | |
503 | MemoryAllocator MA("test" , 0, 10); |
504 | std::list<Allocation> allocList; |
505 | allocList.emplace_back(handle5, true, 0); |
506 | allocList.emplace_back(handle0, true, 10); |
507 | allocList.emplace_back(handle3, true, 0); |
508 | allocList.emplace_back(handle1, true, 10); |
509 | allocList.emplace_back(handle4, true, 0); |
510 | allocList.emplace_back(handle0, false, 0); |
511 | allocList.emplace_back(handle2, true, 20); |
512 | allocList.emplace_back(handle3, false, 0); |
513 | allocList.emplace_back(handle1, false, 0); |
514 | allocList.emplace_back(handle4, false, 0); |
515 | allocList.emplace_back(handle2, false, 0); |
516 | allocList.emplace_back(handle5, false, 0); |
517 | |
518 | uint64_t usedSize = MA.allocateAll(allocList); |
519 | EXPECT_EQ(usedSize, 30); |
520 | EXPECT_EQ(MA.getMaxMemoryUsage(), 30); |
521 | EXPECT_FLOAT_EQ(MA.getAllocationEfficiency(), 1.00); |
522 | EXPECT_EQ(MA.getSize(handle3), 0); |
523 | EXPECT_EQ(MA.getAddress(handle3), 0); |
524 | EXPECT_EQ(MA.getSegment(handle3).size(), 0); |
525 | EXPECT_EQ(MA.getSegment(handle3).begin, 0); |
526 | EXPECT_EQ(MA.getSize(handle4), 0); |
527 | EXPECT_EQ(MA.getAddress(handle4), 0); |
528 | EXPECT_EQ(MA.getSegment(handle4).size(), 0); |
529 | EXPECT_EQ(MA.getSegment(handle4).begin, 0); |
530 | EXPECT_EQ(MA.getSize(handle5), 0); |
531 | EXPECT_EQ(MA.getAddress(handle5), 0); |
532 | EXPECT_EQ(MA.getSegment(handle5).size(), 0); |
533 | EXPECT_EQ(MA.getSegment(handle5).begin, 0); |
534 | } |
535 | |
536 | /// Test empty allocs for allocateAll. |
537 | TEST(MemAlloc, testAllocateAllEmptyAlloc) { |
538 | MemoryAllocator MA("test" , 0, 64); |
539 | std::list<Allocation> allocList; |
540 | uint64_t usedSize = MA.allocateAll(allocList); |
541 | EXPECT_EQ(usedSize, 0); |
542 | } |
543 | |
544 | /// Test memory overflow for allocateAll. |
545 | TEST(MemAlloc, testAllocateAllMemOverflow) { |
546 | MemoryAllocator MA("test" , 10, 64); |
547 | void *handle = reinterpret_cast<void *>(0); |
548 | std::list<Allocation> allocList; |
549 | allocList.emplace_back(handle, true, 100); |
550 | allocList.emplace_back(handle, false, 0); |
551 | uint64_t usedSize = MA.allocateAll(allocList); |
552 | EXPECT_EQ(usedSize, MemoryAllocator::npos); |
553 | #ifndef NDEBUG |
554 | ASSERT_DEATH_IF_SUPPORTED(MA.getSize(handle), "Unknown handle" ); |
555 | #endif |
556 | } |
557 | |
558 | /// Utility function to test allocation for a given model using \p alignment |
559 | /// and the allocations \p allocs. The expected memory usage and efficiency |
560 | /// are \p expectedUsedSize and \p expectedEfficiency. |
561 | static void testAllocateAllForModel(size_t alignment, |
562 | const std::list<Allocation> &allocs, |
563 | uint64_t expectedUsedSize, |
564 | float expectedEfficiency) { |
565 | MemoryAllocator MA("mem" , 0, alignment); |
566 | uint64_t usedSize = MA.allocateAll(allocs); |
567 | EXPECT_EQ(usedSize, expectedUsedSize); |
568 | for (const auto &alloc : allocs) { |
569 | if (alloc.alloc) { |
570 | EXPECT_EQ(MA.getSize(alloc.handle), alloc.size); |
571 | } |
572 | }; |
573 | EXPECT_FLOAT_EQ(MA.getAllocationEfficiency(), expectedEfficiency); |
574 | } |
575 | |
576 | /// Test memory allocation for multiple models using a text file with the |
577 | /// following format: |
578 | /// MODEL <model_name> |
579 | /// ALIGN <alignment> |
580 | /// ALLOC <id> <size> |
581 | /// FREE <id> |
582 | /// MEM <expected_memory_usage> |
583 | /// EFF <expected_efficiency> |
584 | TEST(MemAlloc, testAllocateAllForModels) { |
585 | std::string modelName; |
586 | unsigned modelIdx = 1; |
587 | size_t alignment; |
588 | uint64_t expectedUsedSize; |
589 | float expectedEfficiency; |
590 | std::list<Allocation> allocs; |
591 | std::ifstream fs; |
592 | std::string filename(GLOW_DATA_PATH |
593 | "tests/unittests/MemoryAllocatorTestModels.txt" ); |
594 | fs.open(filename); |
595 | assert(fs.is_open() && "Error opening file!" ); |
596 | std::string line; |
597 | while (std::getline(fs, line)) { |
598 | // Skip comments. |
599 | if (line.front() == '#') { |
600 | continue; |
601 | } |
602 | // Read model parameters. |
603 | std::stringstream ss(line); |
604 | std::string key; |
605 | ss >> key; |
606 | if (key == "MODEL" ) { |
607 | ss >> modelName; |
608 | } else if (key == "ALIGN" ) { |
609 | ss >> alignment; |
610 | } else if (key == "ALLOC" ) { |
611 | size_t id; |
612 | uint64_t size; |
613 | ss >> id >> size; |
614 | allocs.emplace_back(id, /* alloc */ true, size); |
615 | } else if (key == "FREE" ) { |
616 | size_t id; |
617 | ss >> id; |
618 | allocs.emplace_back(id, /* alloc */ false, 0); |
619 | } else if (key == "MEM" ) { |
620 | ss >> expectedUsedSize; |
621 | } else if (key == "EFF" ) { |
622 | ss >> expectedEfficiency; |
623 | } |
624 | // Test allocation for model. |
625 | if (key == "EFF" ) { |
626 | std::cout << "[" << modelIdx++ |
627 | << "] Testing memory allocation for model: " << modelName |
628 | << "\n" ; |
629 | testAllocateAllForModel(alignment, allocs, expectedUsedSize, |
630 | expectedEfficiency); |
631 | allocs.clear(); |
632 | } |
633 | } |
634 | fs.close(); |
635 | } |
636 | |
637 | /// Test allocating multiple functions with memory reusage for allocateAll. |
638 | TEST(MemAlloc, testAllocateAllMultipleFunctionsWithReuse) { |
639 | |
640 | // Allocation sequence for 1st function. |
641 | void *handle0 = reinterpret_cast<void *>(0); |
642 | void *handle1 = reinterpret_cast<void *>(1); |
643 | std::list<Allocation> allocList1; |
644 | allocList1.emplace_back(handle0, true, 9); |
645 | allocList1.emplace_back(handle1, true, 9); |
646 | allocList1.emplace_back(handle0, false, 0); |
647 | allocList1.emplace_back(handle1, false, 0); |
648 | |
649 | // Allocation sequence for 2nd function. |
650 | void *handle2 = reinterpret_cast<void *>(2); |
651 | void *handle3 = reinterpret_cast<void *>(3); |
652 | std::list<Allocation> allocList2; |
653 | allocList2.emplace_back(handle2, true, 19); |
654 | allocList2.emplace_back(handle3, true, 19); |
655 | allocList2.emplace_back(handle2, false, 0); |
656 | allocList2.emplace_back(handle3, false, 0); |
657 | |
658 | // Allocate all for both functions. |
659 | MemoryAllocator MA("test" , 0, 10); |
660 | uint64_t usedSize1 = MA.allocateAll(allocList1); |
661 | uint64_t usedSize2 = MA.allocateAll(allocList2, /* reuseMemory */ true); |
662 | |
663 | EXPECT_EQ(usedSize1, 20); |
664 | EXPECT_EQ(usedSize2, 40); |
665 | EXPECT_EQ(MA.getSize(handle0), 9); |
666 | EXPECT_EQ(MA.getSize(handle1), 9); |
667 | EXPECT_EQ(MA.getSize(handle2), 19); |
668 | EXPECT_EQ(MA.getSize(handle3), 19); |
669 | EXPECT_EQ(MA.getSegment(handle0).size(), 9); |
670 | EXPECT_EQ(MA.getSegment(handle1).size(), 9); |
671 | EXPECT_EQ(MA.getSegment(handle2).size(), 19); |
672 | EXPECT_EQ(MA.getSegment(handle3).size(), 19); |
673 | EXPECT_EQ(MA.getAddress(handle0), 0); |
674 | EXPECT_EQ(MA.getAddress(handle1), 10); |
675 | EXPECT_EQ(MA.getAddress(handle2), 0); |
676 | EXPECT_EQ(MA.getAddress(handle3), 20); |
677 | EXPECT_EQ(MA.getSegment(handle0).begin, 0); |
678 | EXPECT_EQ(MA.getSegment(handle1).begin, 10); |
679 | EXPECT_EQ(MA.getSegment(handle2).begin, 0); |
680 | EXPECT_EQ(MA.getSegment(handle3).begin, 20); |
681 | EXPECT_EQ(MA.getMaxMemoryUsage(), 40); |
682 | EXPECT_FLOAT_EQ(MA.getAllocationEfficiency(), 1.0); |
683 | } |
684 | |
685 | /// Test allocating multiple functions without memory reusage for allocateAll. |
686 | TEST(MemAlloc, testAllocateAllMultipleFunctionsWithoutReuse) { |
687 | |
688 | // Allocation sequence for 1st function. |
689 | void *handle0 = reinterpret_cast<void *>(0); |
690 | void *handle1 = reinterpret_cast<void *>(1); |
691 | std::list<Allocation> allocList1; |
692 | allocList1.emplace_back(handle0, true, 9); |
693 | allocList1.emplace_back(handle1, true, 9); |
694 | allocList1.emplace_back(handle0, false, 0); |
695 | allocList1.emplace_back(handle1, false, 0); |
696 | |
697 | // Allocation sequence for 2nd function. |
698 | void *handle2 = reinterpret_cast<void *>(2); |
699 | void *handle3 = reinterpret_cast<void *>(3); |
700 | std::list<Allocation> allocList2; |
701 | allocList2.emplace_back(handle2, true, 19); |
702 | allocList2.emplace_back(handle3, true, 19); |
703 | allocList2.emplace_back(handle2, false, 0); |
704 | allocList2.emplace_back(handle3, false, 0); |
705 | |
706 | // Allocate all for both functions. |
707 | MemoryAllocator MA("test" , 0, 10); |
708 | uint64_t usedSize1 = MA.allocateAll(allocList1); |
709 | uint64_t usedSize2 = MA.allocateAll(allocList2, /* reuseMemory */ false); |
710 | |
711 | EXPECT_EQ(usedSize1, 20); |
712 | EXPECT_EQ(usedSize2, 60); |
713 | EXPECT_EQ(MA.getSize(handle0), 9); |
714 | EXPECT_EQ(MA.getSize(handle1), 9); |
715 | EXPECT_EQ(MA.getSize(handle2), 19); |
716 | EXPECT_EQ(MA.getSize(handle3), 19); |
717 | EXPECT_EQ(MA.getSegment(handle0).size(), 9); |
718 | EXPECT_EQ(MA.getSegment(handle1).size(), 9); |
719 | EXPECT_EQ(MA.getSegment(handle2).size(), 19); |
720 | EXPECT_EQ(MA.getSegment(handle3).size(), 19); |
721 | EXPECT_EQ(MA.getAddress(handle0), 0); |
722 | EXPECT_EQ(MA.getAddress(handle1), 10); |
723 | EXPECT_EQ(MA.getAddress(handle2), 20); |
724 | EXPECT_EQ(MA.getAddress(handle3), 40); |
725 | EXPECT_EQ(MA.getSegment(handle0).begin, 0); |
726 | EXPECT_EQ(MA.getSegment(handle1).begin, 10); |
727 | EXPECT_EQ(MA.getSegment(handle2).begin, 20); |
728 | EXPECT_EQ(MA.getSegment(handle3).begin, 40); |
729 | EXPECT_EQ(MA.getMaxMemoryUsage(), 60); |
730 | EXPECT_FLOAT_EQ(MA.getAllocationEfficiency(), 1.0); |
731 | } |
732 | |
733 | /// Test allocating multiple functions without memory reusage and in which the |
734 | /// allocation for 2nd function fails due to unsufficient memory. |
735 | TEST(MemAlloc, testAllocateAllMultipleFunctionsWithoutReuseFail) { |
736 | |
737 | // Allocation sequence for 1st function. |
738 | void *handle0 = reinterpret_cast<void *>(0); |
739 | void *handle1 = reinterpret_cast<void *>(1); |
740 | std::list<Allocation> allocList1; |
741 | allocList1.emplace_back(handle0, true, 9); |
742 | allocList1.emplace_back(handle1, true, 9); |
743 | allocList1.emplace_back(handle0, false, 0); |
744 | allocList1.emplace_back(handle1, false, 0); |
745 | |
746 | // Allocation sequence for 2nd function. |
747 | void *handle2 = reinterpret_cast<void *>(2); |
748 | void *handle3 = reinterpret_cast<void *>(3); |
749 | std::list<Allocation> allocList2; |
750 | allocList2.emplace_back(handle2, true, 19); |
751 | allocList2.emplace_back(handle3, true, 19); |
752 | allocList2.emplace_back(handle2, false, 0); |
753 | allocList2.emplace_back(handle3, false, 0); |
754 | |
755 | // Allocate all for both functions. |
756 | MemoryAllocator MA("test" , 20, 10); |
757 | uint64_t usedSize1 = MA.allocateAll(allocList1); |
758 | uint64_t usedSize2 = MA.allocateAll(allocList2, /* reuseMemory */ false); |
759 | |
760 | EXPECT_EQ(usedSize1, 20); |
761 | EXPECT_EQ(usedSize2, MemoryAllocator::npos); |
762 | EXPECT_EQ(MA.getSize(handle0), 9); |
763 | EXPECT_EQ(MA.getSize(handle1), 9); |
764 | EXPECT_EQ(MA.getSegment(handle0).size(), 9); |
765 | EXPECT_EQ(MA.getSegment(handle1).size(), 9); |
766 | EXPECT_EQ(MA.getAddress(handle0), 0); |
767 | EXPECT_EQ(MA.getAddress(handle1), 10); |
768 | EXPECT_EQ(MA.getSegment(handle0).begin, 0); |
769 | EXPECT_EQ(MA.getSegment(handle1).begin, 10); |
770 | EXPECT_EQ(MA.getMaxMemoryUsage(), 20); |
771 | EXPECT_FLOAT_EQ(MA.getAllocationEfficiency(), 1.0); |
772 | } |
773 | |