1 /* 2 Copyright (c) 2005-2022 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 18 //! \file test_scalable_allocator.cpp 19 //! \brief Test for [memory_allocation.scalable_allocator] functionality 20 21 // Test whether scalable_allocator complies with the requirements in 20.1.5 of ISO C++ Standard (1998). 22 23 #define __TBB_NO_IMPLICIT_LINKAGE 1 24 25 #if _WIN32 || _WIN64 26 #define _CRT_SECURE_NO_WARNINGS 27 #endif 28 29 #define __TBB_EXTRA_DEBUG 1 // enables additional checks 30 #define TBB_PREVIEW_MEMORY_POOL 1 31 #define HARNESS_TBBMALLOC_THREAD_SHUTDOWN 1 32 33 #include "common/test.h" 34 #include "common/utils.h" 35 #include "common/utils_assert.h" 36 #include "common/custom_allocators.h" 37 38 39 #include "tbb/memory_pool.h" 40 #include "tbb/scalable_allocator.h" 41 42 // The actual body of the tests 43 #include "common/allocator_test_common.h" 44 #include "common/allocator_stl_test_common.h" 45 46 // #include "harness_allocator.h" 47 48 #if _MSC_VER 49 #include <windows.h> 50 #endif /* _MSC_VER */ 51 52 typedef StaticCountingAllocator<tbb::memory_pool_allocator<char>> cnt_alloc_t; 53 typedef LocalCountingAllocator<std::allocator<char> > cnt_provider_t; 54 55 class MinimalAllocator : cnt_provider_t { 56 public: 57 typedef char value_type; 58 MinimalAllocator() { 59 // REMARK("%p::ctor\n", this); 60 } 61 MinimalAllocator(const MinimalAllocator&s) : cnt_provider_t(s) { 62 // REMARK("%p::ctor(%p)\n", this, &s); 63 } 64 ~MinimalAllocator() { 65 /* REMARK("%p::dtor: alloc=%u/%u free=%u/%u\n", this, unsigned(items_allocated),unsigned(allocations), 66 unsigned(items_freed), unsigned(frees) ); */ 67 REQUIRE((allocations==frees && items_allocated==items_freed)); 68 if( allocations ) { // non-temporal copy 69 // TODO: describe consumption requirements 70 REQUIRE(items_allocated>cnt_alloc_t::items_allocated); 71 } 72 } 73 void *allocate(size_t sz) { 74 void *p = cnt_provider_t::allocate(sz); 75 // REMARK("%p::allocate(%u) = %p\n", this, unsigned(sz), p); 76 return p; 77 } 78 void deallocate(void *p, size_t sz) { 79 REQUIRE(allocations>frees); 80 // REMARK("%p::deallocate(%p, %u)\n", this, p, unsigned(sz)); 81 cnt_provider_t::deallocate(std::allocator_traits<cnt_provider_t>::pointer(p), sz); 82 } 83 }; 84 85 class NullAllocator { 86 public: 87 typedef char value_type; 88 NullAllocator() { } 89 NullAllocator(const NullAllocator&) { } 90 ~NullAllocator() { } 91 void *allocate(size_t) { return nullptr; } 92 void deallocate(void *, size_t) { REQUIRE(false); } 93 }; 94 95 void TestZeroSpaceMemoryPool() 96 { 97 tbb::memory_pool<NullAllocator> pool; 98 bool allocated = pool.malloc(16) || pool.malloc(9*1024); 99 REQUIRE_MESSAGE(!allocated, "Allocator with no memory must not allocate anything."); 100 } 101 102 #if !TBB_USE_EXCEPTIONS 103 struct FixedPool { 104 void *buf; 105 size_t size; 106 bool used; 107 FixedPool(void *a_buf, size_t a_size) : buf(a_buf), size(a_size), used(false) {} 108 }; 109 110 static void *fixedBufGetMem(intptr_t pool_id, size_t &bytes) 111 { 112 if (((FixedPool*)pool_id)->used) 113 return nullptr; 114 115 ((FixedPool*)pool_id)->used = true; 116 bytes = ((FixedPool*)pool_id)->size; 117 return bytes? ((FixedPool*)pool_id)->buf : nullptr; 118 } 119 #endif 120 121 /* test that pools in small space are either usable or not created 122 (i.e., exception raised) */ 123 void TestSmallFixedSizePool() 124 { 125 char *buf; 126 bool allocated = false; 127 128 for (size_t sz = 0; sz < 64*1024; sz = sz? 3*sz : 3) { 129 buf = (char*)malloc(sz); 130 #if TBB_USE_EXCEPTIONS 131 try { 132 tbb::fixed_pool pool(buf, sz); 133 /* Check that pool is usable, i.e. such an allocation exists, 134 that can be fulfilled from the pool. 16B allocation fits in 16KB slabs, 135 so it requires at least 16KB. Requirement of 9KB allocation is more modest. 136 */ 137 allocated = pool.malloc( 16 ) || pool.malloc( 9*1024 ); 138 } catch (std::invalid_argument&) { 139 REQUIRE_MESSAGE(!sz, "expect std::invalid_argument for zero-sized pool only"); 140 } catch (...) { 141 REQUIRE_MESSAGE(false, "wrong exception type;"); 142 } 143 #else 144 /* Do not test high-level pool interface because pool ctor emit exception 145 on creation failure. Instead test same functionality via low-level interface. 146 TODO: add support for configuration with disabled exceptions to pools. 147 */ 148 rml::MemPoolPolicy pol(fixedBufGetMem, nullptr, 0, /*fixedSizePool=*/true, 149 /*keepMemTillDestroy=*/false); 150 rml::MemoryPool *pool; 151 FixedPool fixedPool(buf, sz); 152 153 rml::MemPoolError ret = pool_create_v1((intptr_t)&fixedPool, &pol, &pool); 154 155 if (ret == rml::POOL_OK) { 156 allocated = pool_malloc(pool, 16) || pool_malloc(pool, 9*1024); 157 pool_destroy(pool); 158 } else 159 REQUIRE_MESSAGE(ret == rml::NO_MEMORY, "Expected that pool either valid or have no memory to be created"); 160 #endif 161 free(buf); 162 } 163 REQUIRE_MESSAGE(allocated, "Maximal buf size should be enough to create working fixed_pool"); 164 #if TBB_USE_EXCEPTIONS 165 try { 166 tbb::fixed_pool pool(nullptr, 10*1024*1024); 167 REQUIRE_MESSAGE(false, "Useless allocator with no memory must not be created"); 168 } catch (std::invalid_argument&) { 169 } catch (...) { 170 REQUIRE_MESSAGE(false, "wrong exception type; expected invalid_argument"); 171 } 172 #endif 173 } 174 175 //! Testing ISO C++ allocator requirements 176 //! \brief \ref interface \ref requirement 177 TEST_CASE("Allocator concept") { 178 #if _MSC_VER && !__TBBMALLOC_NO_IMPLICIT_LINKAGE && !__TBB_WIN8UI_SUPPORT 179 #ifdef _DEBUG 180 REQUIRE_MESSAGE((!GetModuleHandle("tbbmalloc.dll") && GetModuleHandle("tbbmalloc_debug.dll")), 181 "test linked with wrong (non-debug) tbbmalloc library"); 182 #else 183 REQUIRE_MESSAGE((!GetModuleHandle("tbbmalloc_debug.dll") && GetModuleHandle("tbbmalloc.dll")), 184 "test linked with wrong (debug) tbbmalloc library"); 185 #endif // _DEBUG 186 #endif // _MSC_VER && !__TBBMALLOC_NO_IMPLICIT_LINKAGE 187 // allocate/deallocate 188 TestAllocator<tbb::scalable_allocator<void>>(Concept); 189 { 190 tbb::memory_pool<tbb::scalable_allocator<int>> pool; 191 TestAllocator(Concept, tbb::memory_pool_allocator<void>(pool)); 192 }{ 193 // tbb::memory_pool<MinimalAllocator> pool; 194 // cnt_alloc_t alloc( tbb::memory_pool_allocator<char>(pool) ); // double parentheses to avoid function declaration 195 // TestAllocator(Concept, alloc); 196 }{ 197 static char buf[1024*1024*4]; 198 tbb::fixed_pool pool(buf, sizeof(buf)); 199 const char *text = "this is a test";// 15 bytes 200 char *p1 = (char*)pool.malloc( 16 ); 201 REQUIRE(p1); 202 strcpy(p1, text); 203 char *p2 = (char*)pool.realloc( p1, 15 ); 204 REQUIRE_MESSAGE( (p2 && !strcmp(p2, text)), "realloc broke memory" ); 205 206 TestAllocator(Concept, tbb::memory_pool_allocator<void>(pool) ); 207 208 // try allocate almost entire buf keeping some reasonable space for internals 209 char *p3 = (char*)pool.realloc( p2, sizeof(buf)-128*1024 ); 210 REQUIRE_MESSAGE( p3, "defragmentation failed" ); 211 REQUIRE_MESSAGE( !strcmp(p3, text), "realloc broke memory" ); 212 for( size_t sz = 10; sz < sizeof(buf); sz *= 2) { 213 REQUIRE( pool.malloc( sz ) ); 214 pool.recycle(); 215 } 216 217 TestAllocator(Concept, tbb::memory_pool_allocator<void>(pool) ); 218 }{ 219 // Two nested level allocators case with fixed pool allocator as an underlying layer 220 // serving allocRawMem requests for the top level scalable allocator 221 typedef tbb::memory_pool<tbb::memory_pool_allocator<char, tbb::fixed_pool> > NestedPool; 222 223 static char buffer[8*1024*1024]; 224 tbb::fixed_pool fixedPool(buffer, sizeof(buffer)); 225 // Underlying fixed pool allocator 226 tbb::memory_pool_allocator<char, tbb::fixed_pool> fixedPoolAllocator(fixedPool); 227 // Memory pool that handles fixed pool allocator 228 NestedPool nestedPool(fixedPoolAllocator); 229 // Top level memory pool allocator 230 tbb::memory_pool_allocator<char, NestedPool> nestedAllocator(nestedPool); 231 232 TestAllocator(Concept, nestedAllocator); 233 } 234 tbb::memory_pool<tbb::scalable_allocator<int>> mpool; 235 236 tbb::memory_pool_allocator<int> mpalloc(mpool); 237 238 TestAllocator<tbb::memory_pool_allocator<int>>(Concept, mpalloc); 239 TestAllocator<tbb::memory_pool_allocator<void>>(Concept, mpalloc); 240 241 // operator== 242 TestAllocator<tbb::scalable_allocator<void>>(Comparison); 243 TestAllocator<tbb::memory_pool_allocator<void>>(Comparison, tbb::memory_pool_allocator<void>(mpool)); 244 TestAllocator<tbb::memory_pool_allocator<int>>(Comparison, mpalloc); 245 TestAllocator<tbb::memory_pool_allocator<void>>(Comparison, mpalloc); 246 } 247 248 #if TBB_USE_EXCEPTIONS 249 //! Testing exception guarantees 250 //! \brief \ref requirement 251 TEST_CASE("Exceptions") { 252 TestAllocator<tbb::scalable_allocator<void>>(Exceptions); 253 } 254 #endif /* TBB_USE_EXCEPTIONS */ 255 256 //! Testing allocators thread safety (should not introduce data races) 257 //! \brief \ref requirements 258 TEST_CASE("Thread safety") { 259 TestAllocator<tbb::scalable_allocator<void>>(ThreadSafety); 260 } 261 262 //! Test that pools in small space are either usable or not created (i.e., exception raised) 263 //! \brief \ref error_guessing 264 TEST_CASE("Small fixed pool") { 265 TestSmallFixedSizePool(); 266 } 267 268 //! Test that allocator with no memory must not allocate anything. 269 //! \brief \ref error_guessing 270 TEST_CASE("Zero space pool") { 271 TestZeroSpaceMemoryPool(); 272 } 273 274 #if TBB_ALLOCATOR_TRAITS_BROKEN 275 //! Testing allocator traits is broken 276 //! \brief \ref error_guessing 277 TEST_CASE("Broken allocator concept") { 278 TestAllocator<tbb::scalable_allocator<void>>(Broken); 279 280 tbb::memory_pool<tbb::scalable_allocator<int>> mpool; 281 TestAllocator<tbb::memory_pool_allocator<void>>(Broken, tbb::memory_pool_allocator<void>(mpool)); 282 } 283 #endif 284 285 286 //! Testing allocators compatibility with STL containers 287 //! \brief \ref interface 288 TEST_CASE("Integration with STL containers") { 289 TestAllocatorWithSTL<tbb::scalable_allocator<void> >(); 290 tbb::memory_pool<tbb::scalable_allocator<int> > mpool; 291 TestAllocatorWithSTL(tbb::memory_pool_allocator<void>(mpool) ); 292 static char buf[1024*1024*4]; 293 tbb::fixed_pool fpool(buf, sizeof(buf)); 294 TestAllocatorWithSTL(tbb::memory_pool_allocator<void>(fpool) ); 295 } 296 297 #if __TBB_CPP17_MEMORY_RESOURCE_PRESENT 298 //! Testing memory resources compatibility with STL containers through std::pmr::polymorphic_allocator 299 //! \brief \ref interface 300 TEST_CASE("polymorphic_allocator test") { 301 REQUIRE_MESSAGE(!tbb::scalable_memory_resource()->is_equal(*std::pmr::get_default_resource()), 302 "Scalable resource shouldn't be equal to standard resource." ); 303 REQUIRE_MESSAGE(tbb::scalable_memory_resource()->is_equal(*tbb::scalable_memory_resource()), 304 "Memory that was allocated by one scalable resource should be deallocated by any other instance."); 305 306 typedef std::pmr::polymorphic_allocator<void> pmr_alloc_t; 307 TestAllocatorWithSTL(pmr_alloc_t(tbb::scalable_memory_resource())); 308 } 309 #endif 310 311