/* Copyright (c) 2005-2021 Intel Corporation Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ //! \file test_malloc_pools.cpp //! \brief Test for [memory_allocation] functionality #define __TBB_NO_IMPLICIT_LINKAGE 1 #include "common/test.h" #define HARNESS_TBBMALLOC_THREAD_SHUTDOWN 1 #include "common/utils.h" #include "common/utils_assert.h" #include "common/spin_barrier.h" #include "common/tls_limit.h" #include "tbb/scalable_allocator.h" #include template static inline T alignUp (T arg, uintptr_t alignment) { return T(((uintptr_t)arg+(alignment-1)) & ~(alignment-1)); } struct PoolSpace: utils::NoCopy { size_t pos; int regions; size_t bufSize; char *space; static const size_t BUF_SIZE = 8*1024*1024; PoolSpace(size_t bufSz = BUF_SIZE) : pos(0), regions(0), bufSize(bufSz), space(new char[bufSize]) { memset(space, 0, bufSize); } ~PoolSpace() { delete []space; } }; static PoolSpace *poolSpace; struct MallocPoolHeader { void *rawPtr; size_t userSize; }; static std::atomic liveRegions; static void *getMallocMem(intptr_t /*pool_id*/, size_t &bytes) { void *rawPtr = malloc(bytes+sizeof(MallocPoolHeader)+1); if (!rawPtr) return NULL; // +1 to check working with unaligned space void *ret = (void *)((uintptr_t)rawPtr+sizeof(MallocPoolHeader)+1); MallocPoolHeader *hdr = (MallocPoolHeader*)ret-1; hdr->rawPtr = rawPtr; hdr->userSize = bytes; liveRegions++; return ret; } static int putMallocMem(intptr_t /*pool_id*/, void *ptr, size_t bytes) { MallocPoolHeader *hdr = (MallocPoolHeader*)ptr-1; ASSERT(bytes == hdr->userSize, "Invalid size in pool callback."); free(hdr->rawPtr); liveRegions--; return 0; } void TestPoolReset() { rml::MemPoolPolicy pol(getMallocMem, putMallocMem); rml::MemoryPool *pool; pool_create_v1(0, &pol, &pool); for (int i=0; i<100; i++) { REQUIRE(pool_malloc(pool, 8)); REQUIRE(pool_malloc(pool, 50*1024)); } int regionsBeforeReset = liveRegions.load(std::memory_order_acquire); bool ok = pool_reset(pool); REQUIRE(ok); for (int i=0; i<100; i++) { REQUIRE(pool_malloc(pool, 8)); REQUIRE(pool_malloc(pool, 50*1024)); } REQUIRE_MESSAGE(regionsBeforeReset == liveRegions.load(std::memory_order_relaxed), "Expected no new regions allocation."); ok = pool_destroy(pool); REQUIRE(ok); REQUIRE_MESSAGE(!liveRegions.load(std::memory_order_relaxed), "Expected all regions were released."); } class SharedPoolRun: utils::NoAssign { static long threadNum; static utils::SpinBarrier startB, mallocDone; static rml::MemoryPool *pool; static void **crossThread, **afterTerm; public: static const int OBJ_CNT = 100; static void init(int num, rml::MemoryPool *pl, void **crThread, void **aTerm) { threadNum = num; pool = pl; crossThread = crThread; afterTerm = aTerm; startB.initialize(threadNum); mallocDone.initialize(threadNum); } void operator()( int id ) const { const int ITERS = 1000; void *local[ITERS]; startB.wait(); for (int i=id*OBJ_CNT; i<(id+1)*OBJ_CNT; i++) { afterTerm[i] = pool_malloc(pool, i%2? 8*1024 : 9*1024); memset(afterTerm[i], i, i%2? 8*1024 : 9*1024); crossThread[i] = pool_malloc(pool, i%2? 9*1024 : 8*1024); memset(crossThread[i], i, i%2? 9*1024 : 8*1024); } for (int i=1; i poolSpace[pool_id].bufSize) return NULL; void *ret = poolSpace[pool_id].space + poolSpace[pool_id].pos; poolSpace[pool_id].pos += bytes; poolSpace[pool_id].regions++; return ret; } int CrossThreadPutMem(intptr_t pool_id, void* /*raw_ptr*/, size_t /*raw_bytes*/) { poolSpace[pool_id].regions--; return 0; } class CrossThreadRun: utils::NoAssign { static long number_of_threads; static utils::SpinBarrier barrier; static rml::MemoryPool **pool; static char **obj; public: static void initBarrier(unsigned thrds) { barrier.initialize(thrds); } static void init(long num) { number_of_threads = num; pool = new rml::MemoryPool*[number_of_threads]; poolSpace = new PoolSpace[number_of_threads]; obj = new char*[number_of_threads]; } static void destroy() { for (long i=0; i used; char* data; public: FixedPoolHeadBase(size_t s) : size(s), used(false) { data = new char[size]; } void *useData(size_t &bytes) { bool wasUsed = used.exchange(true); REQUIRE_MESSAGE(!wasUsed, "The buffer must not be used twice."); bytes = size; return data; } ~FixedPoolHeadBase() { delete []data; } }; template class FixedPoolHead : FixedPoolHeadBase { public: FixedPoolHead() : FixedPoolHeadBase(SIZE) { } }; static void *fixedBufGetMem(intptr_t pool_id, size_t &bytes) { return ((FixedPoolHeadBase*)pool_id)->useData(bytes); } class FixedPoolUse: utils::NoAssign { static utils::SpinBarrier startB; rml::MemoryPool *pool; size_t reqSize; int iters; public: FixedPoolUse(unsigned threads, rml::MemoryPool *p, size_t sz, int it) : pool(p), reqSize(sz), iters(it) { startB.initialize(threads); } void operator()( int /*id*/ ) const { startB.wait(); for (int i=0; iwait(); void *o = pool_malloc(pool, id%2? 64 : 128*1024); ASSERT(!o, "All memory must be consumed."); } }; class FixedPoolSomeMem: utils::NoAssign { utils::SpinBarrier *barrier; rml::MemoryPool *pool; public: FixedPoolSomeMem(utils::SpinBarrier *b, rml::MemoryPool *p) : barrier(b), pool(p) {} void operator()(int id) const { barrier->wait(); utils::Sleep(2*id); void *o = pool_malloc(pool, id%2? 64 : 128*1024); barrier->wait(); pool_free(pool, o); } }; bool haveEnoughSpace(rml::MemoryPool *pool, size_t sz) { if (void *p = pool_malloc(pool, sz)) { pool_free(pool, p); return true; } return false; } void TestFixedBufferPool() { const int ITERS = 7; const size_t MAX_OBJECT = 7*1024*1024; void *ptrs[ITERS]; rml::MemPoolPolicy pol(fixedBufGetMem, NULL, 0, /*fixedSizePool=*/true, /*keepMemTillDestroy=*/false); rml::MemoryPool *pool; { FixedPoolHead head; pool_create_v1((intptr_t)&head, &pol, &pool); { utils::NativeParallelFor( 1, FixedPoolUse(1, pool, MAX_OBJECT, 2) ); for (int i=0; i head; pool_create_v1((intptr_t)&head, &pol, &pool); int p=128; utils::NativeParallelFor( p, FixedPoolUse(p, pool, MAX_OBJECT/p/2, 1) ); bool ok = pool_destroy(pool); REQUIRE(ok); } } static size_t currGranularity; static void *getGranMem(intptr_t /*pool_id*/, size_t &bytes) { REQUIRE_MESSAGE(!(bytes%currGranularity), "Region size mismatch granularity."); return malloc(bytes); } static int putGranMem(intptr_t /*pool_id*/, void *ptr, size_t bytes) { REQUIRE_MESSAGE(!(bytes%currGranularity), "Region size mismatch granularity."); free(ptr); return 0; } void TestPoolGranularity() { rml::MemPoolPolicy pol(getGranMem, putGranMem); const size_t grans[] = {4*1024, 2*1024*1024, 6*1024*1024, 10*1024*1024}; for (unsigned i=0; i getMemSuccessful), "Multiple requests are allowed when unsuccessful request occurred or cannot search in bootstrap memory. "); REQUIRE(!putMemAll); pool_free(pool, o); return pool; } void CheckPoolLeaks(size_t poolsAlwaysAvailable) { const size_t MAX_POOLS = 16*1000; const int ITERS = 20, CREATED_STABLE = 3; rml::MemoryPool *pools[MAX_POOLS]; size_t created, maxCreated = MAX_POOLS; int maxNotChangedCnt = 0; // expecting that for ITERS runs, max number of pools that can be created // can be stabilized and still stable CREATED_STABLE times for (int j=0; j=poolsAlwaysAvailable, "Expect that the reasonable number of pools can be always created."); for (size_t i=0; ipool = act_pool; } }; void TestPoolDetection() { const int POOLS = 4; rml::MemPoolPolicy pol(fixedBufGetMem, NULL, 0, /*fixedSizePool=*/true, /*keepMemTillDestroy=*/false); rml::MemoryPool *pools[POOLS]; FixedPoolHead head[POOLS]; AllocatedObject *objs[POOLS]; for (int i=0; ipool); pool_free(p, objs[i]); } } for (int i=0; iwait(); if (!id) { bool ok = pool_destroy(pool); REQUIRE(ok); REQUIRE_MESSAGE(!liveRegions.load(std::memory_order_relaxed), "Expected all regions were released."); } // other threads must wait till pool destruction, // to not call thread destruction cleanup before this barrier->wait(); } }; void TestNoLeakOnDestroy() { liveRegions.store(0, std::memory_order_release); for (int p=utils::MinThread; p<=utils::MaxThread; p++) { rml::MemPoolPolicy pol(getMallocMem, putMallocMem); utils::SpinBarrier barrier(p); rml::MemoryPool *pool; pool_create_v1(0, &pol, &pool); utils::NativeParallelFor(p, NoLeakOnDestroyRun(pool, &barrier)); } } static int putMallocMemError(intptr_t /*pool_id*/, void *ptr, size_t bytes) { MallocPoolHeader *hdr = (MallocPoolHeader*)ptr-1; REQUIRE_MESSAGE(bytes == hdr->userSize, "Invalid size in pool callback."); free(hdr->rawPtr); liveRegions--; return -1; } void TestDestroyFailed() { rml::MemPoolPolicy pol(getMallocMem, putMallocMemError); rml::MemoryPool *pool; pool_create_v1(0, &pol, &pool); void *ptr = pool_malloc(pool, 16); REQUIRE(ptr); bool fail = pool_destroy(pool); REQUIRE_MESSAGE(fail==false, "putMemPolicyError callback returns error, " "expect pool_destroy() failure"); } void TestPoolMSize() { rml::MemoryPool *pool = CreateUsablePool(1024); const int SZ = 10; // Original allocation requests, random numbers from small to large size_t requestedSz[SZ] = {8, 16, 500, 1000, 2000, 4000, 8000, 1024*1024, 4242+4242, 8484+8484}; // Unlike large objects, small objects do not store its original size along with the object itself // On Power architecture TLS bins are divided differently. size_t allocatedSz[SZ] = #if __powerpc64__ || __ppc64__ || __bgp__ {8, 16, 512, 1024, 2688, 5376, 8064, 1024*1024, 4242+4242, 8484+8484}; #else {8, 16, 512, 1024, 2688, 4032, 8128, 1024*1024, 4242+4242, 8484+8484}; #endif for (int i = 0; i < SZ; i++) { void* obj = pool_malloc(pool, requestedSz[i]); size_t objSize = pool_msize(pool, obj); REQUIRE_MESSAGE(objSize == allocatedSz[i], "pool_msize returned the wrong value"); pool_free(pool, obj); } bool destroyed = pool_destroy(pool); REQUIRE(destroyed); } //! \brief \ref error_guessing TEST_CASE("Too small buffer") { TestTooSmallBuffer(); } //! \brief \ref error_guessing TEST_CASE("Pool reset") { TestPoolReset(); } TEST_CASE("Shared pool") { TestSharedPool(); } //! \brief \ref error_guessing TEST_CASE("Cross thread pools") { TestCrossThreadPools(); } //! \brief \ref interface TEST_CASE("Fixed buffer pool") { TestFixedBufferPool(); } //! \brief \ref interface TEST_CASE("Pool granularity") { TestPoolGranularity(); } //! \brief \ref error_guessing TEST_CASE("Keep pool till destroy") { TestPoolKeepTillDestroy(); } //! \brief \ref error_guessing TEST_CASE("Entries") { TestEntries(); } //! \brief \ref interface TEST_CASE("Pool creation") { TestPoolCreation(); } //! \brief \ref error_guessing TEST_CASE("Pool detection") { TestPoolDetection(); } //! \brief \ref error_guessing TEST_CASE("Lazy bootstrap") { TestLazyBootstrap(); } //! \brief \ref error_guessing TEST_CASE("No leak on destroy") { TestNoLeakOnDestroy(); } //! \brief \ref error_guessing TEST_CASE("Destroy failed") { TestDestroyFailed(); } //! \brief \ref interface TEST_CASE("Pool msize") { TestPoolMSize(); }