1 | /* |
2 | Copyright (c) 2005-2019 Intel Corporation |
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 "tbb/scalable_allocator.h" |
18 | #include "tbb/atomic.h" |
19 | #define HARNESS_TBBMALLOC_THREAD_SHUTDOWN 1 |
20 | #include "harness.h" |
21 | #include "harness_barrier.h" |
22 | #include "harness_tls.h" |
23 | #if !__TBB_SOURCE_DIRECTLY_INCLUDED |
24 | #include "harness_tbb_independence.h" |
25 | #endif |
26 | |
27 | template<typename T> |
28 | static inline T alignUp (T arg, uintptr_t alignment) { |
29 | return T(((uintptr_t)arg+(alignment-1)) & ~(alignment-1)); |
30 | } |
31 | |
32 | struct PoolSpace: NoCopy { |
33 | size_t pos; |
34 | int regions; |
35 | size_t bufSize; |
36 | char *space; |
37 | |
38 | static const size_t BUF_SIZE = 8*1024*1024; |
39 | |
40 | PoolSpace(size_t bufSz = BUF_SIZE) : |
41 | pos(0), regions(0), |
42 | bufSize(bufSz), space(new char[bufSize]) { |
43 | memset(space, 0, bufSize); |
44 | } |
45 | ~PoolSpace() { |
46 | delete []space; |
47 | } |
48 | }; |
49 | |
50 | static PoolSpace *poolSpace; |
51 | |
52 | struct { |
53 | void *; |
54 | size_t ; |
55 | }; |
56 | |
57 | static tbb::atomic<int> liveRegions; |
58 | |
59 | static void *getMallocMem(intptr_t /*pool_id*/, size_t &bytes) |
60 | { |
61 | void *rawPtr = malloc(bytes+sizeof(MallocPoolHeader)+1); |
62 | if (!rawPtr) |
63 | return NULL; |
64 | // +1 to check working with unaligned space |
65 | void *ret = (void *)((uintptr_t)rawPtr+sizeof(MallocPoolHeader)+1); |
66 | |
67 | MallocPoolHeader *hdr = (MallocPoolHeader*)ret-1; |
68 | hdr->rawPtr = rawPtr; |
69 | hdr->userSize = bytes; |
70 | |
71 | liveRegions++; |
72 | |
73 | return ret; |
74 | } |
75 | |
76 | static int putMallocMem(intptr_t /*pool_id*/, void *ptr, size_t bytes) |
77 | { |
78 | MallocPoolHeader *hdr = (MallocPoolHeader*)ptr-1; |
79 | ASSERT(bytes == hdr->userSize, "Invalid size in pool callback." ); |
80 | free(hdr->rawPtr); |
81 | |
82 | liveRegions--; |
83 | |
84 | return 0; |
85 | } |
86 | |
87 | void TestPoolReset() |
88 | { |
89 | rml::MemPoolPolicy pol(getMallocMem, putMallocMem); |
90 | rml::MemoryPool *pool; |
91 | |
92 | pool_create_v1(0, &pol, &pool); |
93 | for (int i=0; i<100; i++) { |
94 | ASSERT(pool_malloc(pool, 8), NULL); |
95 | ASSERT(pool_malloc(pool, 50*1024), NULL); |
96 | } |
97 | int regionsBeforeReset = liveRegions; |
98 | bool ok = pool_reset(pool); |
99 | ASSERT(ok, NULL); |
100 | for (int i=0; i<100; i++) { |
101 | ASSERT(pool_malloc(pool, 8), NULL); |
102 | ASSERT(pool_malloc(pool, 50*1024), NULL); |
103 | } |
104 | ASSERT(regionsBeforeReset == liveRegions, |
105 | "Expected no new regions allocation." ); |
106 | ok = pool_destroy(pool); |
107 | ASSERT(ok, NULL); |
108 | ASSERT(!liveRegions, "Expected all regions were released." ); |
109 | } |
110 | |
111 | class SharedPoolRun: NoAssign { |
112 | static long threadNum; |
113 | static Harness::SpinBarrier startB, |
114 | mallocDone; |
115 | static rml::MemoryPool *pool; |
116 | static void **crossThread, |
117 | **afterTerm; |
118 | public: |
119 | static const int OBJ_CNT = 100; |
120 | |
121 | static void init(int num, rml::MemoryPool *pl, void **crThread, void **aTerm) { |
122 | threadNum = num; |
123 | pool = pl; |
124 | crossThread = crThread; |
125 | afterTerm = aTerm; |
126 | startB.initialize(threadNum); |
127 | mallocDone.initialize(threadNum); |
128 | } |
129 | |
130 | void operator()( int id ) const { |
131 | const int ITERS = 1000; |
132 | void *local[ITERS]; |
133 | |
134 | startB.wait(); |
135 | for (int i=id*OBJ_CNT; i<(id+1)*OBJ_CNT; i++) { |
136 | afterTerm[i] = pool_malloc(pool, i%2? 8*1024 : 9*1024); |
137 | memset(afterTerm[i], i, i%2? 8*1024 : 9*1024); |
138 | crossThread[i] = pool_malloc(pool, i%2? 9*1024 : 8*1024); |
139 | memset(crossThread[i], i, i%2? 9*1024 : 8*1024); |
140 | } |
141 | |
142 | for (int i=1; i<ITERS; i+=2) { |
143 | local[i-1] = pool_malloc(pool, 6*1024); |
144 | memset(local[i-1], i, 6*1024); |
145 | local[i] = pool_malloc(pool, 16*1024); |
146 | memset(local[i], i, 16*1024); |
147 | } |
148 | mallocDone.wait(); |
149 | int myVictim = threadNum-id-1; |
150 | for (int i=myVictim*OBJ_CNT; i<(myVictim+1)*OBJ_CNT; i++) |
151 | pool_free(pool, crossThread[i]); |
152 | for (int i=0; i<ITERS; i++) |
153 | pool_free(pool, local[i]); |
154 | } |
155 | }; |
156 | |
157 | long SharedPoolRun::threadNum; |
158 | Harness::SpinBarrier SharedPoolRun::startB, |
159 | SharedPoolRun::mallocDone; |
160 | rml::MemoryPool *SharedPoolRun::pool; |
161 | void **SharedPoolRun::crossThread, |
162 | **SharedPoolRun::afterTerm; |
163 | |
164 | // single pool shared by different threads |
165 | void TestSharedPool() |
166 | { |
167 | rml::MemPoolPolicy pol(getMallocMem, putMallocMem); |
168 | rml::MemoryPool *pool; |
169 | |
170 | pool_create_v1(0, &pol, &pool); |
171 | void **crossThread = new void*[MaxThread * SharedPoolRun::OBJ_CNT]; |
172 | void **afterTerm = new void*[MaxThread * SharedPoolRun::OBJ_CNT]; |
173 | |
174 | for (int p=MinThread; p<=MaxThread; p++) { |
175 | SharedPoolRun::init(p, pool, crossThread, afterTerm); |
176 | SharedPoolRun thr; |
177 | |
178 | void *hugeObj = pool_malloc(pool, 10*1024*1024); |
179 | ASSERT(hugeObj, NULL); |
180 | |
181 | NativeParallelFor( p, thr ); |
182 | |
183 | pool_free(pool, hugeObj); |
184 | for (int i=0; i<p*SharedPoolRun::OBJ_CNT; i++) |
185 | pool_free(pool, afterTerm[i]); |
186 | } |
187 | delete []afterTerm; |
188 | delete []crossThread; |
189 | |
190 | bool ok = pool_destroy(pool); |
191 | ASSERT(ok, NULL); |
192 | ASSERT(!liveRegions, "Expected all regions were released." ); |
193 | } |
194 | |
195 | void *CrossThreadGetMem(intptr_t pool_id, size_t &bytes) |
196 | { |
197 | if (poolSpace[pool_id].pos + bytes > poolSpace[pool_id].bufSize) |
198 | return NULL; |
199 | |
200 | void *ret = poolSpace[pool_id].space + poolSpace[pool_id].pos; |
201 | poolSpace[pool_id].pos += bytes; |
202 | poolSpace[pool_id].regions++; |
203 | |
204 | return ret; |
205 | } |
206 | |
207 | int CrossThreadPutMem(intptr_t pool_id, void* /*raw_ptr*/, size_t /*raw_bytes*/) |
208 | { |
209 | poolSpace[pool_id].regions--; |
210 | return 0; |
211 | } |
212 | |
213 | class CrossThreadRun: NoAssign { |
214 | static long number_of_threads; |
215 | static Harness::SpinBarrier barrier; |
216 | static rml::MemoryPool **pool; |
217 | static char **obj; |
218 | public: |
219 | static void initBarrier(unsigned thrds) { barrier.initialize(thrds); } |
220 | static void init(long num) { |
221 | number_of_threads = num; |
222 | pool = new rml::MemoryPool*[number_of_threads]; |
223 | poolSpace = new PoolSpace[number_of_threads]; |
224 | obj = new char*[number_of_threads]; |
225 | } |
226 | static void destroy() { |
227 | for (long i=0; i<number_of_threads; i++) |
228 | ASSERT(!poolSpace[i].regions, "Memory leak detected" ); |
229 | delete []pool; |
230 | delete []poolSpace; |
231 | delete []obj; |
232 | } |
233 | CrossThreadRun() {} |
234 | void operator()( int id ) const { |
235 | rml::MemPoolPolicy pol(CrossThreadGetMem, CrossThreadPutMem); |
236 | const int objLen = 10*id; |
237 | |
238 | pool_create_v1(id, &pol, &pool[id]); |
239 | obj[id] = (char*)pool_malloc(pool[id], objLen); |
240 | ASSERT(obj[id], NULL); |
241 | memset(obj[id], id, objLen); |
242 | |
243 | { |
244 | const size_t lrgSz = 2*16*1024; |
245 | void *ptrLarge = pool_malloc(pool[id], lrgSz); |
246 | ASSERT(ptrLarge, NULL); |
247 | memset(ptrLarge, 1, lrgSz); |
248 | // consume all small objects |
249 | while (pool_malloc(pool[id], 5 * 1024)); |
250 | // releasing of large object will not give a chance to allocate more |
251 | // since only fixed pool can look at other bins aligned/notAligned |
252 | pool_free(pool[id], ptrLarge); |
253 | ASSERT(!pool_malloc(pool[id], 5*1024), NULL); |
254 | } |
255 | |
256 | barrier.wait(); |
257 | int myPool = number_of_threads-id-1; |
258 | for (int i=0; i<10*myPool; i++) |
259 | ASSERT(myPool==obj[myPool][i], NULL); |
260 | pool_free(pool[myPool], obj[myPool]); |
261 | bool ok = pool_destroy(pool[myPool]); |
262 | ASSERT(ok, NULL); |
263 | } |
264 | }; |
265 | |
266 | long CrossThreadRun::number_of_threads; |
267 | Harness::SpinBarrier CrossThreadRun::barrier; |
268 | rml::MemoryPool **CrossThreadRun::pool; |
269 | char **CrossThreadRun::obj; |
270 | |
271 | // pools created, used and destroyed by different threads |
272 | void TestCrossThreadPools() |
273 | { |
274 | for (int p=MinThread; p<=MaxThread; p++) { |
275 | CrossThreadRun::initBarrier(p); |
276 | CrossThreadRun::init(p); |
277 | NativeParallelFor( p, CrossThreadRun() ); |
278 | for (int i=0; i<p; i++) |
279 | ASSERT(!poolSpace[i].regions, "Region leak detected" ); |
280 | CrossThreadRun::destroy(); |
281 | } |
282 | } |
283 | |
284 | // buffer is too small to pool be created, but must not leak resources |
285 | void TestTooSmallBuffer() |
286 | { |
287 | poolSpace = new PoolSpace(8*1024); |
288 | |
289 | rml::MemPoolPolicy pol(CrossThreadGetMem, CrossThreadPutMem); |
290 | rml::MemoryPool *pool; |
291 | pool_create_v1(0, &pol, &pool); |
292 | bool ok = pool_destroy(pool); |
293 | ASSERT(ok, NULL); |
294 | ASSERT(!poolSpace[0].regions, "No leaks." ); |
295 | |
296 | delete poolSpace; |
297 | } |
298 | |
299 | class FixedPoolHeadBase : NoAssign { |
300 | size_t size; |
301 | intptr_t used; |
302 | char *data; |
303 | public: |
304 | FixedPoolHeadBase(size_t s) : size(s), used(false) { |
305 | data = new char[size]; |
306 | } |
307 | void *useData(size_t &bytes) { |
308 | intptr_t wasUsed = __TBB_FetchAndStoreW(&used, true); |
309 | ASSERT(!wasUsed, "The buffer must not be used twice." ); |
310 | bytes = size; |
311 | return data; |
312 | } |
313 | ~FixedPoolHeadBase() { |
314 | delete []data; |
315 | } |
316 | }; |
317 | |
318 | template<size_t SIZE> |
319 | class FixedPoolHead : FixedPoolHeadBase { |
320 | public: |
321 | FixedPoolHead() : FixedPoolHeadBase(SIZE) { } |
322 | }; |
323 | |
324 | static void *fixedBufGetMem(intptr_t pool_id, size_t &bytes) |
325 | { |
326 | return ((FixedPoolHeadBase*)pool_id)->useData(bytes); |
327 | } |
328 | |
329 | class FixedPoolUse: NoAssign { |
330 | static Harness::SpinBarrier startB; |
331 | rml::MemoryPool *pool; |
332 | size_t reqSize; |
333 | int iters; |
334 | public: |
335 | FixedPoolUse(unsigned threads, rml::MemoryPool *p, size_t sz, int it) : |
336 | pool(p), reqSize(sz), iters(it) { |
337 | startB.initialize(threads); |
338 | } |
339 | void operator()( int /*id*/ ) const { |
340 | startB.wait(); |
341 | for (int i=0; i<iters; i++) { |
342 | void *o = pool_malloc(pool, reqSize); |
343 | ASSERT(o, NULL); |
344 | pool_free(pool, o); |
345 | } |
346 | } |
347 | }; |
348 | |
349 | Harness::SpinBarrier FixedPoolUse::startB; |
350 | |
351 | class FixedPoolNomem: NoAssign { |
352 | Harness::SpinBarrier *startB; |
353 | rml::MemoryPool *pool; |
354 | public: |
355 | FixedPoolNomem(Harness::SpinBarrier *b, rml::MemoryPool *p) : |
356 | startB(b), pool(p) {} |
357 | void operator()(int id) const { |
358 | startB->wait(); |
359 | void *o = pool_malloc(pool, id%2? 64 : 128*1024); |
360 | ASSERT(!o, "All memory must be consumed." ); |
361 | } |
362 | }; |
363 | |
364 | class FixedPoolSomeMem: NoAssign { |
365 | Harness::SpinBarrier *barrier; |
366 | rml::MemoryPool *pool; |
367 | public: |
368 | FixedPoolSomeMem(Harness::SpinBarrier *b, rml::MemoryPool *p) : |
369 | barrier(b), pool(p) {} |
370 | void operator()(int id) const { |
371 | barrier->wait(); |
372 | Harness::Sleep(2*id); |
373 | void *o = pool_malloc(pool, id%2? 64 : 128*1024); |
374 | barrier->wait(); |
375 | pool_free(pool, o); |
376 | } |
377 | }; |
378 | |
379 | bool haveEnoughSpace(rml::MemoryPool *pool, size_t sz) |
380 | { |
381 | if (void *p = pool_malloc(pool, sz)) { |
382 | pool_free(pool, p); |
383 | return true; |
384 | } |
385 | return false; |
386 | } |
387 | |
388 | void TestFixedBufferPool() |
389 | { |
390 | const int ITERS = 7; |
391 | const size_t MAX_OBJECT = 7*1024*1024; |
392 | void *ptrs[ITERS]; |
393 | rml::MemPoolPolicy pol(fixedBufGetMem, NULL, 0, /*fixedSizePool=*/true, |
394 | /*keepMemTillDestroy=*/false); |
395 | rml::MemoryPool *pool; |
396 | { |
397 | FixedPoolHead<MAX_OBJECT + 1024*1024> head; |
398 | |
399 | pool_create_v1((intptr_t)&head, &pol, &pool); |
400 | { |
401 | NativeParallelFor( 1, FixedPoolUse(1, pool, MAX_OBJECT, 2) ); |
402 | |
403 | for (int i=0; i<ITERS; i++) { |
404 | ptrs[i] = pool_malloc(pool, MAX_OBJECT/ITERS); |
405 | ASSERT(ptrs[i], NULL); |
406 | } |
407 | for (int i=0; i<ITERS; i++) |
408 | pool_free(pool, ptrs[i]); |
409 | |
410 | NativeParallelFor( 1, FixedPoolUse(1, pool, MAX_OBJECT, 1) ); |
411 | } |
412 | // each thread asks for an MAX_OBJECT/p/2 object, |
413 | // /2 is to cover fragmentation |
414 | for (int p=MinThread; p<=MaxThread; p++) |
415 | NativeParallelFor( p, FixedPoolUse(p, pool, MAX_OBJECT/p/2, 10000) ); |
416 | { |
417 | const int p=128; |
418 | NativeParallelFor( p, FixedPoolUse(p, pool, MAX_OBJECT/p/2, 1) ); |
419 | } |
420 | { |
421 | size_t maxSz; |
422 | const int p = 512; |
423 | Harness::SpinBarrier barrier(p); |
424 | |
425 | // Find maximal useful object size. Start with MAX_OBJECT/2, |
426 | // as the pool might be fragmented by BootStrapBlocks consumed during |
427 | // FixedPoolRun. |
428 | size_t l, r; |
429 | ASSERT(haveEnoughSpace(pool, MAX_OBJECT/2), NULL); |
430 | for (l = MAX_OBJECT/2, r = MAX_OBJECT + 1024*1024; l < r-1; ) { |
431 | size_t mid = (l+r)/2; |
432 | if (haveEnoughSpace(pool, mid)) |
433 | l = mid; |
434 | else |
435 | r = mid; |
436 | } |
437 | maxSz = l; |
438 | ASSERT(!haveEnoughSpace(pool, maxSz+1), "Expect to find boundary value." ); |
439 | // consume all available memory |
440 | void *largeObj = pool_malloc(pool, maxSz); |
441 | ASSERT(largeObj, NULL); |
442 | void *o = pool_malloc(pool, 64); |
443 | if (o) // pool fragmented, skip FixedPoolNomem |
444 | pool_free(pool, o); |
445 | else |
446 | NativeParallelFor( p, FixedPoolNomem(&barrier, pool) ); |
447 | pool_free(pool, largeObj); |
448 | // keep some space unoccupied |
449 | largeObj = pool_malloc(pool, maxSz-512*1024); |
450 | ASSERT(largeObj, NULL); |
451 | NativeParallelFor( p, FixedPoolSomeMem(&barrier, pool) ); |
452 | pool_free(pool, largeObj); |
453 | } |
454 | bool ok = pool_destroy(pool); |
455 | ASSERT(ok, NULL); |
456 | } |
457 | // check that fresh untouched pool can successfully fulfil requests from 128 threads |
458 | { |
459 | FixedPoolHead<MAX_OBJECT + 1024*1024> head; |
460 | pool_create_v1((intptr_t)&head, &pol, &pool); |
461 | int p=128; |
462 | NativeParallelFor( p, FixedPoolUse(p, pool, MAX_OBJECT/p/2, 1) ); |
463 | bool ok = pool_destroy(pool); |
464 | ASSERT(ok, NULL); |
465 | } |
466 | } |
467 | |
468 | static size_t currGranularity; |
469 | |
470 | static void *getGranMem(intptr_t /*pool_id*/, size_t &bytes) |
471 | { |
472 | ASSERT(!(bytes%currGranularity), "Region size mismatch granularity." ); |
473 | return malloc(bytes); |
474 | } |
475 | |
476 | static int putGranMem(intptr_t /*pool_id*/, void *ptr, size_t bytes) |
477 | { |
478 | ASSERT(!(bytes%currGranularity), "Region size mismatch granularity." ); |
479 | free(ptr); |
480 | return 0; |
481 | } |
482 | |
483 | void TestPoolGranularity() |
484 | { |
485 | rml::MemPoolPolicy pol(getGranMem, putGranMem); |
486 | const size_t grans[] = {4*1024, 2*1024*1024, 6*1024*1024, 10*1024*1024}; |
487 | |
488 | for (unsigned i=0; i<sizeof(grans)/sizeof(grans[0]); i++) { |
489 | pol.granularity = currGranularity = grans[i]; |
490 | rml::MemoryPool *pool; |
491 | |
492 | pool_create_v1(0, &pol, &pool); |
493 | for (int sz=500*1024; sz<16*1024*1024; sz+=101*1024) { |
494 | void *p = pool_malloc(pool, sz); |
495 | ASSERT(p, "Can't allocate memory in pool." ); |
496 | pool_free(pool, p); |
497 | } |
498 | bool ok = pool_destroy(pool); |
499 | ASSERT(ok, NULL); |
500 | } |
501 | } |
502 | |
503 | static size_t putMemAll, getMemAll, getMemSuccessful; |
504 | |
505 | static void *getMemMalloc(intptr_t /*pool_id*/, size_t &bytes) |
506 | { |
507 | getMemAll++; |
508 | void *p = malloc(bytes); |
509 | if (p) |
510 | getMemSuccessful++; |
511 | return p; |
512 | } |
513 | |
514 | static int putMemFree(intptr_t /*pool_id*/, void *ptr, size_t /*bytes*/) |
515 | { |
516 | putMemAll++; |
517 | free(ptr); |
518 | return 0; |
519 | } |
520 | |
521 | void TestPoolKeepTillDestroy() |
522 | { |
523 | const int ITERS = 50*1024; |
524 | void *ptrs[2*ITERS+1]; |
525 | rml::MemPoolPolicy pol(getMemMalloc, putMemFree); |
526 | rml::MemoryPool *pool; |
527 | |
528 | // 1st create default pool that returns memory back to callback, |
529 | // then use keepMemTillDestroy policy |
530 | for (int keep=0; keep<2; keep++) { |
531 | getMemAll = putMemAll = 0; |
532 | if (keep) |
533 | pol.keepAllMemory = 1; |
534 | pool_create_v1(0, &pol, &pool); |
535 | for (int i=0; i<2*ITERS; i+=2) { |
536 | ptrs[i] = pool_malloc(pool, 7*1024); |
537 | ptrs[i+1] = pool_malloc(pool, 10*1024); |
538 | } |
539 | ptrs[2*ITERS] = pool_malloc(pool, 8*1024*1024); |
540 | ASSERT(!putMemAll, NULL); |
541 | for (int i=0; i<2*ITERS; i++) |
542 | pool_free(pool, ptrs[i]); |
543 | pool_free(pool, ptrs[2*ITERS]); |
544 | size_t totalPutMemCalls = putMemAll; |
545 | if (keep) |
546 | ASSERT(!putMemAll, NULL); |
547 | else { |
548 | ASSERT(putMemAll, NULL); |
549 | putMemAll = 0; |
550 | } |
551 | size_t getCallsBefore = getMemAll; |
552 | void *p = pool_malloc(pool, 8*1024*1024); |
553 | ASSERT(p, NULL); |
554 | if (keep) |
555 | ASSERT(getCallsBefore == getMemAll, "Must not lead to new getMem call" ); |
556 | size_t putCallsBefore = putMemAll; |
557 | bool ok = pool_reset(pool); |
558 | ASSERT(ok, NULL); |
559 | ASSERT(putCallsBefore == putMemAll, "Pool is not releasing memory during reset." ); |
560 | ok = pool_destroy(pool); |
561 | ASSERT(ok, NULL); |
562 | ASSERT(putMemAll, NULL); |
563 | totalPutMemCalls += putMemAll; |
564 | ASSERT(getMemAll == totalPutMemCalls, "Memory leak detected." ); |
565 | } |
566 | |
567 | } |
568 | |
569 | static bool memEqual(char *buf, size_t size, int val) |
570 | { |
571 | bool memEq = true; |
572 | for (size_t k=0; k<size; k++) |
573 | if (buf[k] != val) |
574 | memEq = false; |
575 | return memEq; |
576 | } |
577 | |
578 | void TestEntries() |
579 | { |
580 | const int SZ = 4; |
581 | const int ALGN = 4; |
582 | size_t size[SZ] = {8, 8000, 9000, 100*1024}; |
583 | size_t algn[ALGN] = {8, 64, 4*1024, 8*1024*1024}; |
584 | |
585 | rml::MemPoolPolicy pol(getGranMem, putGranMem); |
586 | currGranularity = 1; // not check granularity in the test |
587 | rml::MemoryPool *pool; |
588 | |
589 | pool_create_v1(0, &pol, &pool); |
590 | for (int i=0; i<SZ; i++) |
591 | for (int j=0; j<ALGN; j++) { |
592 | char *p = (char*)pool_aligned_malloc(pool, size[i], algn[j]); |
593 | ASSERT(p && 0==((uintptr_t)p & (algn[j]-1)), NULL); |
594 | memset(p, j, size[i]); |
595 | |
596 | size_t curr_algn = algn[rand() % ALGN]; |
597 | size_t curr_sz = size[rand() % SZ]; |
598 | char *p1 = (char*)pool_aligned_realloc(pool, p, curr_sz, curr_algn); |
599 | ASSERT(p1 && 0==((uintptr_t)p1 & (curr_algn-1)), NULL); |
600 | ASSERT(memEqual(p1, min(size[i], curr_sz), j), NULL); |
601 | |
602 | memset(p1, j+1, curr_sz); |
603 | size_t curr_sz1 = size[rand() % SZ]; |
604 | char *p2 = (char*)pool_realloc(pool, p1, curr_sz1); |
605 | ASSERT(p2, NULL); |
606 | ASSERT(memEqual(p2, min(curr_sz1, curr_sz), j+1), NULL); |
607 | |
608 | pool_free(pool, p2); |
609 | } |
610 | |
611 | bool ok = pool_destroy(pool); |
612 | ASSERT(ok, NULL); |
613 | |
614 | bool fail = rml::pool_destroy(NULL); |
615 | ASSERT(!fail, NULL); |
616 | fail = rml::pool_reset(NULL); |
617 | ASSERT(!fail, NULL); |
618 | } |
619 | |
620 | rml::MemoryPool *CreateUsablePool(size_t size) |
621 | { |
622 | rml::MemoryPool *pool; |
623 | rml::MemPoolPolicy okPolicy(getMemMalloc, putMemFree); |
624 | |
625 | putMemAll = getMemAll = getMemSuccessful = 0; |
626 | rml::MemPoolError res = pool_create_v1(0, &okPolicy, &pool); |
627 | if (res != rml::POOL_OK) { |
628 | ASSERT(!getMemAll && !putMemAll, "No callbacks after fail." ); |
629 | return NULL; |
630 | } |
631 | void *o = pool_malloc(pool, size); |
632 | if (!getMemSuccessful) { |
633 | // no memory from callback, valid reason to leave |
634 | ASSERT(!o, "The pool must be unusable." ); |
635 | return NULL; |
636 | } |
637 | ASSERT(o, "Created pool must be useful." ); |
638 | ASSERT(getMemSuccessful == 1 || getMemSuccessful == 5 || getMemAll > getMemSuccessful, |
639 | "Multiple requests are allowed when unsuccessful request occurred or cannot search in bootstrap memory. " ); |
640 | ASSERT(!putMemAll, NULL); |
641 | pool_free(pool, o); |
642 | |
643 | return pool; |
644 | } |
645 | |
646 | void CheckPoolLeaks(size_t poolsAlwaysAvailable) |
647 | { |
648 | const size_t MAX_POOLS = 16*1000; |
649 | const int ITERS = 20, CREATED_STABLE = 3; |
650 | rml::MemoryPool *pools[MAX_POOLS]; |
651 | size_t created, maxCreated = MAX_POOLS; |
652 | int maxNotChangedCnt = 0; |
653 | |
654 | // expecting that for ITERS runs, max number of pools that can be created |
655 | // can be stabilized and still stable CREATED_STABLE times |
656 | for (int j=0; j<ITERS && maxNotChangedCnt<CREATED_STABLE; j++) { |
657 | for (created=0; created<maxCreated; created++) { |
658 | rml::MemoryPool *p = CreateUsablePool(1024); |
659 | if (!p) |
660 | break; |
661 | pools[created] = p; |
662 | } |
663 | ASSERT(created>=poolsAlwaysAvailable, |
664 | "Expect that the reasonable number of pools can be always created." ); |
665 | for (size_t i=0; i<created; i++) { |
666 | bool ok = pool_destroy(pools[i]); |
667 | ASSERT(ok, NULL); |
668 | } |
669 | if (created < maxCreated) { |
670 | maxCreated = created; |
671 | maxNotChangedCnt = 0; |
672 | } else |
673 | maxNotChangedCnt++; |
674 | } |
675 | ASSERT(maxNotChangedCnt == CREATED_STABLE, "The number of created pools must be stabilized." ); |
676 | } |
677 | |
678 | void TestPoolCreation() |
679 | { |
680 | putMemAll = getMemAll = getMemSuccessful = 0; |
681 | |
682 | rml::MemPoolPolicy nullPolicy(NULL, putMemFree), |
683 | emptyFreePolicy(getMemMalloc, NULL), |
684 | okPolicy(getMemMalloc, putMemFree); |
685 | rml::MemoryPool *pool; |
686 | |
687 | rml::MemPoolError res = pool_create_v1(0, &nullPolicy, &pool); |
688 | ASSERT(res==rml::INVALID_POLICY, "pool with empty pAlloc can't be created" ); |
689 | res = pool_create_v1(0, &emptyFreePolicy, &pool); |
690 | ASSERT(res==rml::INVALID_POLICY, "pool with empty pFree can't be created" ); |
691 | ASSERT(!putMemAll && !getMemAll, "no callback calls are expected" ); |
692 | res = pool_create_v1(0, &okPolicy, &pool); |
693 | ASSERT(res==rml::POOL_OK, NULL); |
694 | bool ok = pool_destroy(pool); |
695 | ASSERT(ok, NULL); |
696 | ASSERT(putMemAll == getMemSuccessful, "no leaks after pool_destroy" ); |
697 | |
698 | // 32 is a guess for a number of pools that is acceptable everywere |
699 | CheckPoolLeaks(32); |
700 | // try to consume all but 16 TLS keys |
701 | LimitTLSKeysTo limitTLSTo(16); |
702 | // ...and check that we can create at least 16 pools |
703 | CheckPoolLeaks(16); |
704 | } |
705 | |
706 | struct AllocatedObject { |
707 | rml::MemoryPool *pool; |
708 | }; |
709 | |
710 | const size_t BUF_SIZE = 1024*1024; |
711 | |
712 | class PoolIdentityCheck : NoAssign { |
713 | rml::MemoryPool** const pools; |
714 | AllocatedObject** const objs; |
715 | public: |
716 | PoolIdentityCheck(rml::MemoryPool** p, AllocatedObject** o) : pools(p), objs(o) {} |
717 | void operator()(int id) const { |
718 | objs[id] = (AllocatedObject*)pool_malloc(pools[id], BUF_SIZE/2); |
719 | ASSERT(objs[id], NULL); |
720 | rml::MemoryPool *act_pool = rml::pool_identify(objs[id]); |
721 | ASSERT(act_pool == pools[id], NULL); |
722 | |
723 | for (size_t total=0; total<2*BUF_SIZE; total+=256) { |
724 | AllocatedObject *o = (AllocatedObject*)pool_malloc(pools[id], 256); |
725 | ASSERT(o, NULL); |
726 | act_pool = rml::pool_identify(o); |
727 | ASSERT(act_pool == pools[id], NULL); |
728 | pool_free(act_pool, o); |
729 | } |
730 | if( id&1 ) { // make every second returned object "small" |
731 | pool_free(act_pool, objs[id]); |
732 | objs[id] = (AllocatedObject*)pool_malloc(pools[id], 16); |
733 | ASSERT(objs[id], NULL); |
734 | } |
735 | objs[id]->pool = act_pool; |
736 | } |
737 | }; |
738 | |
739 | void TestPoolDetection() |
740 | { |
741 | const int POOLS = 4; |
742 | rml::MemPoolPolicy pol(fixedBufGetMem, NULL, 0, /*fixedSizePool=*/true, |
743 | /*keepMemTillDestroy=*/false); |
744 | rml::MemoryPool *pools[POOLS]; |
745 | FixedPoolHead<BUF_SIZE*POOLS> head[POOLS]; |
746 | AllocatedObject *objs[POOLS]; |
747 | |
748 | for (int i=0; i<POOLS; i++) |
749 | pool_create_v1((intptr_t)(head+i), &pol, &pools[i]); |
750 | // if object somehow released to different pools, subsequent allocation |
751 | // from affected pools became impossible |
752 | for (int k=0; k<10; k++) { |
753 | PoolIdentityCheck check(pools, objs); |
754 | if( k&1 ) |
755 | NativeParallelFor( POOLS, check); |
756 | else |
757 | for (int i=0; i<POOLS; i++) check(i); |
758 | |
759 | for (int i=0; i<POOLS; i++) { |
760 | rml::MemoryPool *p = rml::pool_identify(objs[i]); |
761 | ASSERT(p == objs[i]->pool, NULL); |
762 | pool_free(p, objs[i]); |
763 | } |
764 | } |
765 | for (int i=0; i<POOLS; i++) { |
766 | bool ok = pool_destroy(pools[i]); |
767 | ASSERT(ok, NULL); |
768 | } |
769 | } |
770 | |
771 | void TestLazyBootstrap() |
772 | { |
773 | rml::MemPoolPolicy pol(getMemMalloc, putMemFree); |
774 | const size_t sizes[] = {8, 9*1024, 0}; |
775 | |
776 | for (int i=0; sizes[i]; i++) { |
777 | rml::MemoryPool *pool = CreateUsablePool(sizes[i]); |
778 | bool ok = pool_destroy(pool); |
779 | ASSERT(ok, NULL); |
780 | ASSERT(getMemSuccessful == putMemAll, "No leak." ); |
781 | } |
782 | } |
783 | |
784 | class NoLeakOnDestroyRun: NoAssign { |
785 | rml::MemoryPool *pool; |
786 | Harness::SpinBarrier *barrier; |
787 | public: |
788 | NoLeakOnDestroyRun(rml::MemoryPool *p, Harness::SpinBarrier *b) : pool(p), barrier(b) {} |
789 | void operator()(int id) const { |
790 | void *p = pool_malloc(pool, id%2? 8 : 9000); |
791 | ASSERT(p && liveRegions, NULL); |
792 | barrier->wait(); |
793 | if (!id) { |
794 | bool ok = pool_destroy(pool); |
795 | ASSERT(ok, NULL); |
796 | ASSERT(!liveRegions, "Expected all regions were released." ); |
797 | } |
798 | // other threads must wait till pool destruction, |
799 | // to not call thread destruction cleanup before this |
800 | barrier->wait(); |
801 | } |
802 | }; |
803 | |
804 | void TestNoLeakOnDestroy() |
805 | { |
806 | liveRegions = 0; |
807 | for (int p=MinThread; p<=MaxThread; p++) { |
808 | rml::MemPoolPolicy pol(getMallocMem, putMallocMem); |
809 | Harness::SpinBarrier barrier(p); |
810 | rml::MemoryPool *pool; |
811 | |
812 | pool_create_v1(0, &pol, &pool); |
813 | NativeParallelFor(p, NoLeakOnDestroyRun(pool, &barrier)); |
814 | } |
815 | } |
816 | |
817 | |
818 | static int putMallocMemError(intptr_t /*pool_id*/, void *ptr, size_t bytes) |
819 | { |
820 | MallocPoolHeader *hdr = (MallocPoolHeader*)ptr-1; |
821 | ASSERT(bytes == hdr->userSize, "Invalid size in pool callback." ); |
822 | free(hdr->rawPtr); |
823 | |
824 | liveRegions--; |
825 | |
826 | return -1; |
827 | } |
828 | |
829 | void TestDestroyFailed() |
830 | { |
831 | rml::MemPoolPolicy pol(getMallocMem, putMallocMemError); |
832 | rml::MemoryPool *pool; |
833 | pool_create_v1(0, &pol, &pool); |
834 | void *ptr = pool_malloc(pool, 16); |
835 | ASSERT(ptr, NULL); |
836 | bool fail = pool_destroy(pool); |
837 | ASSERT(fail==false, "putMemPolicyError callback returns error, " |
838 | "expect pool_destroy() failure" ); |
839 | } |
840 | |
841 | void TestPoolMSize() { |
842 | rml::MemoryPool *pool = CreateUsablePool(1024); |
843 | |
844 | const int SZ = 10; |
845 | // Original allocation requests, random numbers from small to large |
846 | size_t requestedSz[SZ] = {8, 16, 500, 1000, 2000, 4000, 8000, 1024*1024, 4242+4242, 8484+8484}; |
847 | |
848 | // Unlike large objects, small objects do not store its original size along with the object itself |
849 | // On Power architecture TLS bins are divided differently. |
850 | size_t allocatedSz[SZ] = |
851 | #if __powerpc64__ || __ppc64__ || __bgp__ |
852 | {8, 16, 512, 1024, 2688, 5376, 8064, 1024*1024, 4242+4242, 8484+8484}; |
853 | #else |
854 | {8, 16, 512, 1024, 2688, 4032, 8128, 1024*1024, 4242+4242, 8484+8484}; |
855 | #endif |
856 | for (int i = 0; i < SZ; i++) { |
857 | void* obj = pool_malloc(pool, requestedSz[i]); |
858 | size_t objSize = pool_msize(pool, obj); |
859 | ASSERT(objSize == allocatedSz[i], "pool_msize returned the wrong value" ); |
860 | pool_free(pool, obj); |
861 | } |
862 | bool destroyed = pool_destroy(pool); |
863 | ASSERT(destroyed, NULL); |
864 | } |
865 | |
866 | int TestMain () { |
867 | TestTooSmallBuffer(); |
868 | TestPoolReset(); |
869 | TestSharedPool(); |
870 | TestCrossThreadPools(); |
871 | TestFixedBufferPool(); |
872 | TestPoolGranularity(); |
873 | TestPoolKeepTillDestroy(); |
874 | TestEntries(); |
875 | TestPoolCreation(); |
876 | TestPoolDetection(); |
877 | TestLazyBootstrap(); |
878 | TestNoLeakOnDestroy(); |
879 | TestDestroyFailed(); |
880 | TestPoolMSize(); |
881 | |
882 | return Harness::Done; |
883 | } |
884 | |