ClassAllocatorBuilder.cpp 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. // Copyright (C) 2009-2022, Panagiotis Christopoulos Charitos and contributors.
  2. // All rights reserved.
  3. // Code licensed under the BSD License.
  4. // http://www.anki3d.org/LICENSE
  5. #include <AnKi/Util/ClassAllocatorBuilder.h>
  6. #include <AnKi/Util/List.h>
  7. #include <AnKi/Util/Thread.h>
  8. #include <AnKi/Util/BitSet.h>
  9. #include <Tests/Framework/Framework.h>
  10. #include <random>
  11. #include <algorithm>
  12. using namespace anki;
  13. namespace {
  14. class Chunk : public IntrusiveListEnabled<Chunk>
  15. {
  16. public:
  17. Chunk() = default;
  18. void* m_memory;
  19. BitSet<128> m_inUseSuballocations = {false};
  20. U32 m_suballocationCount;
  21. void* m_class;
  22. PtrSize m_size;
  23. };
  24. class Interface
  25. {
  26. public:
  27. class Class
  28. {
  29. public:
  30. Class(PtrSize suballocationSize, PtrSize cluster)
  31. : m_suballocationSize(suballocationSize)
  32. , m_chunkSize(cluster)
  33. {
  34. }
  35. PtrSize m_suballocationSize;
  36. PtrSize m_chunkSize;
  37. };
  38. Array<Class, 7> m_classes = {{Class(256, 16_KB), Class(4_KB, 256_KB), Class(128_KB, 8_MB), Class(1_MB, 32_MB),
  39. Class(16_MB, 128_MB), Class(64_MB, 256_MB), Class(128_MB, 256_MB)}};
  40. static constexpr PtrSize MAX_SIZE = 128_MB;
  41. PtrSize m_crntSize = 0;
  42. Error allocateChunk(U32 classIdx, Chunk*& chunk)
  43. {
  44. PtrSize size = m_classes[classIdx].m_chunkSize;
  45. if(m_crntSize + size > MAX_SIZE)
  46. {
  47. return Error::OUT_OF_MEMORY;
  48. }
  49. PtrSize alignment = 256;
  50. chunk = new Chunk;
  51. chunk->m_memory = mallocAligned(size, alignment);
  52. chunk->m_size = size;
  53. m_crntSize += size;
  54. return Error::NONE;
  55. }
  56. void freeChunk(Chunk* chunk)
  57. {
  58. m_crntSize -= chunk->m_size;
  59. freeAligned(chunk->m_memory);
  60. delete chunk;
  61. }
  62. static constexpr U32 getClassCount()
  63. {
  64. return 7;
  65. }
  66. void getClassInfo(U32 classIdx, PtrSize& chunkSize, PtrSize& suballocationSize) const
  67. {
  68. suballocationSize = m_classes[classIdx].m_suballocationSize;
  69. chunkSize = m_classes[classIdx].m_chunkSize;
  70. }
  71. };
  72. } // namespace
  73. static inline U32 floorPow2(U32 v)
  74. {
  75. v |= v >> 16;
  76. v |= v >> 8;
  77. v |= v >> 4;
  78. v |= v >> 2;
  79. v |= v >> 1;
  80. v++;
  81. return v >> 1;
  82. }
  83. ANKI_TEST(Util, ClassAllocatorBuilder)
  84. {
  85. HeapAllocator<U8> alloc(allocAligned, nullptr);
  86. ClassAllocatorBuilder<Chunk, Interface, Mutex> calloc;
  87. calloc.init(alloc);
  88. std::mt19937 gen(0);
  89. const U SHIFT = 15;
  90. std::discrete_distribution<U> dis(16 * SHIFT, 0.0, F32(SHIFT), [](F32 c) {
  91. return exp2(-0.5 * c);
  92. });
  93. auto nextAllocSize = [&]() -> U {
  94. U size = U(256.0 * exp2(F64(dis(gen)) / 16.0));
  95. return size;
  96. };
  97. std::vector<std::pair<Chunk*, PtrSize>> allocations;
  98. const U TEST_COUNT = 100;
  99. const U ITERATIONS = 20;
  100. const U maxAlignment = 256;
  101. auto getRandAlignment = [&]() -> U {
  102. U out = rand() % maxAlignment;
  103. out = nextPowerOfTwo(out);
  104. out = max<U>(1, out);
  105. return out;
  106. };
  107. for(U tests = 0; tests < TEST_COUNT; ++tests)
  108. {
  109. for(U i = 0; i < ITERATIONS; ++i)
  110. {
  111. // Fill up the heap.
  112. while(1)
  113. {
  114. const PtrSize size = nextAllocSize();
  115. Chunk* chunk;
  116. PtrSize offset;
  117. const U alignment = getRandAlignment();
  118. if(calloc.allocate(size, alignment, chunk, offset))
  119. {
  120. break;
  121. }
  122. ANKI_TEST_EXPECT_EQ(isAligned(alignment, offset), true);
  123. allocations.push_back({chunk, offset});
  124. }
  125. std::shuffle(allocations.begin(), allocations.end(), gen);
  126. PtrSize halfSize = (allocations.size() * 3) / 4;
  127. for(PtrSize i = halfSize; i < allocations.size(); ++i)
  128. {
  129. calloc.free(allocations[i].first, allocations[i].second);
  130. }
  131. allocations.erase(allocations.begin() + halfSize, allocations.end());
  132. }
  133. // The heap should be roughly half-full now, so test fragmentation.
  134. const U32 freeSize = U32(Interface::MAX_SIZE - calloc.getInterface().m_crntSize);
  135. U32 baseFreeSize = floorPow2(freeSize);
  136. const U32 BASE_SIZE = 256;
  137. const F32 BIAS = 0.0;
  138. const F32 POWER = 1.2f;
  139. const F32 OFFSET = -1.0f;
  140. F32 bestCase = 0.0;
  141. {
  142. // Best case is when we can allocate once for every bit that is set in the pow2 structure.
  143. U32 freeBits = freeSize / BASE_SIZE;
  144. for(U32 bit = 0; bit < 32; bit++)
  145. {
  146. if(freeBits & (1u << bit))
  147. {
  148. bestCase += (pow(POWER, F32(BIAS + F32(bit))) + OFFSET) * F32(BASE_SIZE << bit);
  149. }
  150. }
  151. }
  152. F32 score = 0.0;
  153. while(baseFreeSize >= BASE_SIZE)
  154. {
  155. Chunk* chunk;
  156. PtrSize offset;
  157. const U alignment = getRandAlignment();
  158. while(calloc.allocate(baseFreeSize, alignment, chunk, offset) == Error::NONE)
  159. {
  160. ANKI_TEST_EXPECT_EQ(isAligned(alignment, offset), true);
  161. score += (pow(POWER, (log2(F32(baseFreeSize / BASE_SIZE)) + BIAS)) + OFFSET) * F32(baseFreeSize);
  162. allocations.push_back({chunk, offset});
  163. }
  164. baseFreeSize >>= 1;
  165. }
  166. printf("Score: %.3f\n", score / bestCase);
  167. // Cleanup
  168. for(auto& h : allocations)
  169. {
  170. calloc.free(h.first, h.second);
  171. }
  172. allocations.clear();
  173. }
  174. }