SegregatedListsAllocatorBuilder.inl.h 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607
  1. // Copyright (C) 2009-present, 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/SegregatedListsAllocatorBuilder.h>
  6. namespace anki {
  7. template<typename TChunk, typename TInterface, typename TLock, typename TMemoryPool>
  8. SegregatedListsAllocatorBuilder<TChunk, TInterface, TLock, TMemoryPool>::~SegregatedListsAllocatorBuilder()
  9. {
  10. if(!m_chunks.isEmpty())
  11. {
  12. ANKI_UTIL_LOGE("Forgot to free memory");
  13. }
  14. }
  15. template<typename TChunk, typename TInterface, typename TLock, typename TMemoryPool>
  16. U32 SegregatedListsAllocatorBuilder<TChunk, TInterface, TLock, TMemoryPool>::findClass(PtrSize size, PtrSize alignment) const
  17. {
  18. ANKI_ASSERT(size > 0 && alignment > 0);
  19. for(U32 i = 0; i < m_interface.getClassCount(); ++i)
  20. {
  21. PtrSize maxSize;
  22. m_interface.getClassInfo(i, maxSize);
  23. if(size <= maxSize)
  24. {
  25. if(alignment <= maxSize)
  26. {
  27. // Found the class
  28. return i;
  29. }
  30. else
  31. {
  32. // The class found doesn't have the proper alignment. Need to go higher
  33. }
  34. }
  35. }
  36. // Not found
  37. return kMaxU32;
  38. }
  39. template<typename TChunk, typename TInterface, typename TLock, typename TMemoryPool>
  40. Bool SegregatedListsAllocatorBuilder<TChunk, TInterface, TLock, TMemoryPool>::chooseBestFit(PtrSize allocSize, PtrSize allocAlignment,
  41. FreeBlock* blockA, FreeBlock* blockB,
  42. FreeBlock*& bestBlock)
  43. {
  44. ANKI_ASSERT(allocSize > 0 && allocAlignment > 0);
  45. ANKI_ASSERT(blockA || blockB);
  46. bestBlock = nullptr;
  47. PtrSize remainingSizeA = 0;
  48. if(blockA)
  49. {
  50. const PtrSize alignedAddress = getAlignedRoundUp(allocAlignment, blockA->m_address);
  51. const Bool fits = alignedAddress + allocSize <= blockA->m_address + blockA->m_size;
  52. if(fits)
  53. {
  54. bestBlock = blockA;
  55. remainingSizeA = blockA->m_size - allocSize - (alignedAddress - blockA->m_address);
  56. }
  57. }
  58. if(blockB)
  59. {
  60. const PtrSize alignedAddress = getAlignedRoundUp(allocAlignment, blockB->m_address);
  61. const Bool fits = alignedAddress + allocSize <= blockB->m_address + blockB->m_size;
  62. if(fits)
  63. {
  64. if(bestBlock)
  65. {
  66. const PtrSize remainingSizeB = blockB->m_size - allocSize - (alignedAddress - blockB->m_address);
  67. if(remainingSizeB < remainingSizeA)
  68. {
  69. bestBlock = blockB;
  70. }
  71. }
  72. else
  73. {
  74. bestBlock = blockB;
  75. }
  76. }
  77. }
  78. if(bestBlock)
  79. {
  80. return allocSize == bestBlock->m_size;
  81. }
  82. else
  83. {
  84. return false;
  85. }
  86. }
  87. template<typename TChunk, typename TInterface, typename TLock, typename TMemoryPool>
  88. void SegregatedListsAllocatorBuilder<TChunk, TInterface, TLock, TMemoryPool>::placeFreeBlock(PtrSize address, PtrSize size, ChunksIterator chunkIt)
  89. {
  90. ANKI_ASSERT(size > 0 && isAligned(m_interface.getMinSizeAlignment(), size));
  91. ANKI_ASSERT(chunkIt != m_chunks.getEnd());
  92. TChunk& chunk = *(*chunkIt);
  93. ANKI_ASSERT(address + size <= chunk.m_totalSize);
  94. // First search everything to find other coalesced free blocks
  95. U32 leftBlock = kMaxU32;
  96. U32 leftClass = kMaxU32;
  97. U32 rightBlock = kMaxU32;
  98. U32 rightClass = kMaxU32;
  99. for(U32 classIdx = 0; classIdx < m_interface.getClassCount(); ++classIdx)
  100. {
  101. const DynamicArray<FreeBlock, TMemoryPool>& freeLists = chunk.m_freeLists[classIdx];
  102. if(freeLists.getSize() == 0)
  103. {
  104. continue;
  105. }
  106. FreeBlock newBlock;
  107. newBlock.m_address = address;
  108. newBlock.m_size = size;
  109. auto it = std::lower_bound(freeLists.getBegin(), freeLists.getEnd(), newBlock, [](const FreeBlock& a, const FreeBlock& b) {
  110. return a.m_address + a.m_size < b.m_address;
  111. });
  112. if(it == freeLists.getEnd())
  113. {
  114. continue;
  115. }
  116. const U32 pos = U32(it - freeLists.getBegin());
  117. const FreeBlock& freeBlock = freeLists[pos];
  118. if(freeBlock.m_address + freeBlock.m_size == address)
  119. {
  120. // Free block is in the left of the new
  121. ANKI_ASSERT(leftBlock == kMaxU32);
  122. leftBlock = pos;
  123. leftClass = classIdx;
  124. if(pos + 1 < freeLists.getSize())
  125. {
  126. const FreeBlock& freeBlock2 = freeLists[pos + 1];
  127. if(address + size == freeBlock2.m_address)
  128. {
  129. ANKI_ASSERT(rightBlock == kMaxU32);
  130. rightBlock = pos + 1;
  131. rightClass = classIdx;
  132. }
  133. }
  134. }
  135. else if(address + size == freeBlock.m_address)
  136. {
  137. // Free block is in the right of the new
  138. ANKI_ASSERT(rightBlock == kMaxU32);
  139. rightBlock = pos;
  140. rightClass = classIdx;
  141. if(pos > 0)
  142. {
  143. const FreeBlock& freeBlock2 = freeLists[pos - 1];
  144. if(freeBlock2.m_address + freeBlock2.m_size == address)
  145. {
  146. ANKI_ASSERT(leftBlock == kMaxU32);
  147. leftBlock = pos - 1;
  148. leftClass = classIdx;
  149. }
  150. }
  151. }
  152. else
  153. {
  154. // Do nothing
  155. }
  156. if(leftBlock != kMaxU32 && rightBlock != kMaxU32)
  157. {
  158. // Best case, stop searching
  159. break;
  160. }
  161. }
  162. // Merge the new block
  163. FreeBlock newBlock;
  164. newBlock.m_address = address;
  165. newBlock.m_size = size;
  166. if(leftBlock != kMaxU32)
  167. {
  168. const FreeBlock& lblock = chunk.m_freeLists[leftClass][leftBlock];
  169. newBlock.m_address = lblock.m_address;
  170. newBlock.m_size += lblock.m_size;
  171. chunk.m_freeLists[leftClass].erase(chunk.m_freeLists[leftClass].getBegin() + leftBlock);
  172. if(rightBlock != kMaxU32 && rightClass == leftClass)
  173. {
  174. // Both right and left blocks live in the same dynamic array. Due to the erase() above the rights's index is no longer valid, adjust it
  175. ANKI_ASSERT(rightBlock > leftBlock);
  176. --rightBlock;
  177. }
  178. }
  179. if(rightBlock != kMaxU32)
  180. {
  181. const FreeBlock& rblock = chunk.m_freeLists[rightClass][rightBlock];
  182. newBlock.m_size += rblock.m_size;
  183. chunk.m_freeLists[rightClass].erase(chunk.m_freeLists[rightClass].getBegin() + rightBlock);
  184. }
  185. // Store the new block
  186. const U32 newClassIdx = findClass(newBlock.m_size, 1);
  187. ANKI_ASSERT(newClassIdx != kMaxU32);
  188. chunk.m_freeLists[newClassIdx].emplaceBack(newBlock);
  189. std::sort(chunk.m_freeLists[newClassIdx].getBegin(), chunk.m_freeLists[newClassIdx].getEnd(), [](const FreeBlock& a, const FreeBlock& b) {
  190. return a.m_address < b.m_address;
  191. });
  192. // Adjust chunk free size
  193. chunk.m_freeSize += size;
  194. ANKI_ASSERT(chunk.m_freeSize <= chunk.m_totalSize);
  195. if(chunk.m_freeSize == chunk.m_totalSize)
  196. {
  197. // Chunk completely free, delete it
  198. U32 blockCount = 0;
  199. for(U32 classIdx = 0; classIdx < m_interface.getClassCount(); ++classIdx)
  200. {
  201. blockCount += chunk.m_freeLists[classIdx].getSize();
  202. chunk.m_freeLists[classIdx].destroy();
  203. }
  204. chunk.m_freeLists.destroy();
  205. ANKI_ASSERT(blockCount == 1);
  206. m_chunks.erase(chunkIt);
  207. m_interface.deleteChunk(&chunk);
  208. }
  209. }
  210. template<typename TChunk, typename TInterface, typename TLock, typename TMemoryPool>
  211. Error SegregatedListsAllocatorBuilder<TChunk, TInterface, TLock, TMemoryPool>::allocate(PtrSize origSize, PtrSize origAlignment, TChunk*& outChunk,
  212. PtrSize& outOffset)
  213. {
  214. ANKI_ASSERT(origSize > 0 && origAlignment > 0);
  215. const PtrSize size = getAlignedRoundUp(m_interface.getMinSizeAlignment(), origSize);
  216. const PtrSize alignment = max<PtrSize>(m_interface.getMinSizeAlignment(), origAlignment);
  217. // Find starting class
  218. const U32 startingClassIdx = findClass(size, alignment);
  219. if(startingClassIdx == kMaxU32) [[unlikely]]
  220. {
  221. ANKI_UTIL_LOGE("Couldn't find class for allocation of size %zu", origSize);
  222. return Error::kOutOfMemory;
  223. }
  224. LockGuard<TLock> lock(m_lock);
  225. // Sort the chunks because we want to try allocate from the least empty first
  226. std::sort(m_chunks.getBegin(), m_chunks.getEnd(), [](const TChunk* a, const TChunk* b) {
  227. return a->m_freeSize < b->m_freeSize;
  228. });
  229. // Find a free block, its class and its chunk
  230. FreeBlock* freeBlock = nullptr;
  231. ChunksIterator chunkIt = m_chunks.getBegin();
  232. U32 classIdx = kMaxU32;
  233. for(; chunkIt != m_chunks.getEnd(); ++chunkIt)
  234. {
  235. classIdx = startingClassIdx;
  236. while(classIdx < m_interface.getClassCount())
  237. {
  238. // Find the best fit
  239. for(FreeBlock& block : (*chunkIt)->m_freeLists[classIdx])
  240. {
  241. const Bool theBestBlock = chooseBestFit(size, alignment, freeBlock, &block, freeBlock);
  242. if(theBestBlock)
  243. {
  244. break;
  245. }
  246. }
  247. if(freeBlock)
  248. {
  249. // Done
  250. break;
  251. }
  252. else
  253. {
  254. // Check for free blocks in the next class
  255. ++classIdx;
  256. }
  257. }
  258. if(freeBlock)
  259. {
  260. break;
  261. }
  262. }
  263. if(freeBlock == nullptr)
  264. {
  265. // No free blocks, allocate new chunk
  266. // Init the new chunk
  267. PtrSize chunkSize;
  268. TChunk* chunk;
  269. ANKI_CHECK(m_interface.allocateChunk(chunk, chunkSize));
  270. chunk->m_freeLists.resize(m_interface.getClassCount());
  271. if(chunkSize < size)
  272. {
  273. ANKI_UTIL_LOGE("Chunk allocated can't fit the current allocation of %zu", origSize);
  274. chunk->m_freeLists.destroy();
  275. m_interface.deleteChunk(chunk);
  276. return Error::kOutOfMemory;
  277. }
  278. chunk->m_totalSize = chunkSize;
  279. chunk->m_freeSize = 0;
  280. m_chunks.emplaceBack(chunk);
  281. placeFreeBlock(size, chunkSize - size, m_chunks.getBegin() + m_chunks.getSize() - 1);
  282. // Allocate
  283. outChunk = chunk;
  284. outOffset = 0;
  285. }
  286. else
  287. {
  288. // Have a free block, allocate from it
  289. ANKI_ASSERT(chunkIt != m_chunks.getEnd() && freeBlock);
  290. TChunk& chunk = *(*chunkIt);
  291. const FreeBlock fBlock = *freeBlock;
  292. chunk.m_freeLists[classIdx].erase(freeBlock);
  293. freeBlock = nullptr;
  294. ANKI_ASSERT(chunk.m_freeSize >= fBlock.m_size);
  295. chunk.m_freeSize -= fBlock.m_size;
  296. const PtrSize alignedAddress = getAlignedRoundUp(alignment, fBlock.m_address);
  297. if(alignedAddress != fBlock.m_address)
  298. {
  299. // Add a free block because of alignment missmatch
  300. placeFreeBlock(fBlock.m_address, alignedAddress - fBlock.m_address, chunkIt);
  301. }
  302. const PtrSize allocationEnd = alignedAddress + size;
  303. const PtrSize freeBlockEnd = fBlock.m_address + fBlock.m_size;
  304. if(allocationEnd < freeBlockEnd)
  305. {
  306. // Add what remains
  307. placeFreeBlock(allocationEnd, freeBlockEnd - allocationEnd, chunkIt);
  308. }
  309. // Allocate
  310. outChunk = &chunk;
  311. outOffset = alignedAddress;
  312. }
  313. ANKI_ASSERT(outChunk);
  314. ANKI_ASSERT(isAligned(alignment, outOffset));
  315. return Error::kNone;
  316. }
  317. template<typename TChunk, typename TInterface, typename TLock, typename TMemoryPool>
  318. void SegregatedListsAllocatorBuilder<TChunk, TInterface, TLock, TMemoryPool>::free(TChunk* chunk, PtrSize offset, PtrSize size)
  319. {
  320. ANKI_ASSERT(chunk && size);
  321. LockGuard<TLock> lock(m_lock);
  322. ChunksIterator it = m_chunks.getBegin();
  323. for(; it != m_chunks.getEnd(); ++it)
  324. {
  325. if(*it == chunk)
  326. {
  327. break;
  328. }
  329. }
  330. ANKI_ASSERT(it != m_chunks.getEnd());
  331. size = getAlignedRoundUp(m_interface.getMinSizeAlignment(), size);
  332. placeFreeBlock(offset, size, it);
  333. }
  334. template<typename TChunk, typename TInterface, typename TLock, typename TMemoryPool>
  335. Error SegregatedListsAllocatorBuilder<TChunk, TInterface, TLock, TMemoryPool>::validate() const
  336. {
  337. #define ANKI_SLAB_ASSERT(x, ...) \
  338. do \
  339. { \
  340. if(!(x)) [[unlikely]] \
  341. { \
  342. ANKI_UTIL_LOGE(__VA_ARGS__); \
  343. ANKI_DEBUG_BREAK(); \
  344. return Error::kFunctionFailed; \
  345. } \
  346. } while(0)
  347. LockGuard<TLock> lock(m_lock);
  348. U32 chunkIdx = 0;
  349. for(const TChunk* chunk : m_chunks)
  350. {
  351. ANKI_SLAB_ASSERT(chunk->m_totalSize > 0, "Chunk %u: Total size can't be 0", chunkIdx);
  352. ANKI_SLAB_ASSERT(chunk->m_freeSize < chunk->m_totalSize, "Chunk %u: Free size (%zu) should be less than total size (%zu)", chunkIdx,
  353. chunk->m_freeSize, chunk->m_totalSize);
  354. PtrSize freeSize = 0;
  355. for(U32 c = 0; c < m_interface.getClassCount(); ++c)
  356. {
  357. for(U32 i = 0; i < chunk->m_freeLists[c].getSize(); ++i)
  358. {
  359. const FreeBlock& crnt = chunk->m_freeLists[c][i];
  360. ANKI_SLAB_ASSERT(crnt.m_size > 0, "Chunk %u class %u block %u: Block size can't be 0", chunkIdx, c, i);
  361. freeSize += crnt.m_size;
  362. const U32 classIdx = findClass(crnt.m_size, 1);
  363. ANKI_SLAB_ASSERT(classIdx == c, "Chunk %u class %u block %u: Free block not in the correct class", chunkIdx, c, i);
  364. if(i > 0)
  365. {
  366. const FreeBlock& prev = chunk->m_freeLists[c][i - 1];
  367. ANKI_SLAB_ASSERT(prev.m_address < crnt.m_address, "Chunk %u class %u block %u: Previous block should have lower address",
  368. chunkIdx, c, i);
  369. ANKI_SLAB_ASSERT(prev.m_address + prev.m_size < crnt.m_address, "Chunk %u class %u block %u: Block overlaps with previous",
  370. chunkIdx, c, i);
  371. }
  372. }
  373. }
  374. ANKI_SLAB_ASSERT(freeSize == chunk->m_freeSize, "Chunk %u: Free size calculated doesn't match chunk's free size", chunkIdx);
  375. ++chunkIdx;
  376. }
  377. // Now do an expensive check that blocks never overlap with other blocks of other classes
  378. chunkIdx = 0;
  379. for(const TChunk* chunk : m_chunks)
  380. {
  381. for(U32 c = 0; c < m_interface.getClassCount(); ++c)
  382. {
  383. for(U32 i = 0; i < chunk->m_freeLists[c].getSize(); ++i)
  384. {
  385. const FreeBlock& crnt = chunk->m_freeLists[c][i];
  386. for(U32 c2 = 0; c2 < m_interface.getClassCount(); ++c2)
  387. {
  388. for(U32 j = 0; j < chunk->m_freeLists[c2].getSize(); ++j)
  389. {
  390. if(c == c2 && i == j)
  391. {
  392. // Skip "crnt"
  393. continue;
  394. }
  395. const FreeBlock& other = chunk->m_freeLists[c2][j];
  396. // Check coalescing
  397. if(crnt.m_address < other.m_address)
  398. {
  399. ANKI_SLAB_ASSERT(crnt.m_address + crnt.m_size < other.m_address,
  400. "Chunk %u class %u block %u: Block overlaps or should have been merged "
  401. "with block: class %u block %u",
  402. chunkIdx, c, i, c2, j);
  403. }
  404. else if(crnt.m_address > other.m_address)
  405. {
  406. ANKI_SLAB_ASSERT(other.m_address + other.m_size < crnt.m_address,
  407. "Chunk %u class %u block %u: Block overlaps or should have been merged "
  408. "with block: class %u block %u",
  409. chunkIdx, c, i, c2, j);
  410. }
  411. else
  412. {
  413. ANKI_SLAB_ASSERT(false,
  414. "Chunk %u class %u block %u: Block shouldn't be having the same address "
  415. "with: class %u block %u",
  416. chunkIdx, c, i, c2, j);
  417. }
  418. }
  419. }
  420. }
  421. }
  422. ++chunkIdx;
  423. }
  424. #undef ANKI_SLAB_ASSERT
  425. return Error::kNone;
  426. }
  427. template<typename TChunk, typename TInterface, typename TLock, typename TMemoryPool>
  428. void SegregatedListsAllocatorBuilder<TChunk, TInterface, TLock, TMemoryPool>::printFreeBlocks(StringList& strList) const
  429. {
  430. LockGuard<TLock> lock(m_lock);
  431. U32 chunkCount = 0;
  432. for(const TChunk* chunk : m_chunks)
  433. {
  434. strList.pushBackSprintf("Chunk #%u, total size %zu, free size %zu\n", chunkCount, chunk->m_totalSize, chunk->m_freeSize);
  435. for(U32 c = 0; c < m_interface.getClassCount(); ++c)
  436. {
  437. if(chunk->m_freeLists[c].getSize())
  438. {
  439. strList.pushBackSprintf(" Class #%u\n ", c);
  440. }
  441. for(U32 i = 0; i < chunk->m_freeLists[c].getSize(); ++i)
  442. {
  443. const FreeBlock& blk = chunk->m_freeLists[c][i];
  444. strList.pushBackSprintf("| %zu-%zu(%zu) ", blk.m_address, blk.m_address + blk.m_size - 1, blk.m_size);
  445. }
  446. if(chunk->m_freeLists[c].getSize())
  447. {
  448. strList.pushBack("|\n");
  449. }
  450. }
  451. ++chunkCount;
  452. }
  453. }
  454. template<typename TChunk, typename TInterface, typename TLock, typename TMemoryPool>
  455. F32 SegregatedListsAllocatorBuilder<TChunk, TInterface, TLock, TMemoryPool>::computeExternalFragmentation(PtrSize baseSize) const
  456. {
  457. ANKI_ASSERT(baseSize > 0);
  458. LockGuard<TLock> lock(m_lock);
  459. F32 maxFragmentation = 0.0f;
  460. for(const TChunk* chunk : m_chunks)
  461. {
  462. PtrSize largestFreeBlockSize = 0;
  463. for(U32 c = 0; c < m_interface.getClassCount(); ++c)
  464. {
  465. for(const FreeBlock& block : chunk->m_freeLists[c])
  466. {
  467. largestFreeBlockSize = max(largestFreeBlockSize, block.m_size / baseSize);
  468. }
  469. }
  470. const F32 frag = F32(1.0 - F64(largestFreeBlockSize) / F64(chunk->m_freeSize / baseSize));
  471. maxFragmentation = max(maxFragmentation, frag);
  472. }
  473. return maxFragmentation;
  474. }
  475. template<typename TChunk, typename TInterface, typename TLock, typename TMemoryPool>
  476. F32 SegregatedListsAllocatorBuilder<TChunk, TInterface, TLock, TMemoryPool>::computeExternalFragmentationSawicki(PtrSize baseSize) const
  477. {
  478. ANKI_ASSERT(baseSize > 0);
  479. LockGuard<TLock> lock(m_lock);
  480. F32 maxFragmentation = 0.0f;
  481. for(const TChunk* chunk : m_chunks)
  482. {
  483. F64 quality = 0.0;
  484. for(U32 c = 0; c < m_interface.getClassCount(); ++c)
  485. {
  486. for(const FreeBlock& block : chunk->m_freeLists[c])
  487. {
  488. const F64 size = F64(block.m_size / baseSize);
  489. quality += size * size;
  490. }
  491. }
  492. quality = sqrt(quality) / F64(chunk->m_freeSize / baseSize);
  493. const F32 frag = 1.0f - F32(quality * quality);
  494. maxFragmentation = max(maxFragmentation, frag);
  495. }
  496. return maxFragmentation;
  497. }
  498. } // end namespace anki