File indexing completed on 2024-04-28 03:53:10
0001 /* 0002 This file is part of the KDE libraries 0003 0004 SPDX-FileCopyrightText: 1999 Waldo Bastian <bastian@kde.org> 0005 SPDX-FileCopyrightText: 2002 Michael Matz <matz@kde.org> 0006 0007 SPDX-License-Identifier: LGPL-2.0-or-later 0008 */ 0009 0010 /* Fast zone memory allocator with deallocation support, for use as obstack 0011 or as general purpose allocator. It does no compaction. If the usage 0012 pattern is non-optimal it might waste some memory while running. E.g. 0013 allocating many small things at once, and then deallocating only every 0014 second one, there is a high chance, that actually no memory is freed. 0015 */ 0016 0017 #include "kzoneallocator_p.h" 0018 0019 #include <kcompletion_debug.h> 0020 0021 #include <QList> 0022 0023 #include <stdio.h> 0024 0025 class KZoneAllocator::MemBlock 0026 { 0027 public: 0028 MemBlock(size_t s) 0029 : size(s) 0030 , ref(0) 0031 , older(nullptr) 0032 , newer(nullptr) 0033 { 0034 begin = new char[s]; 0035 } 0036 ~MemBlock() 0037 { 0038 delete[] begin; 0039 } 0040 MemBlock(const MemBlock &) = delete; 0041 MemBlock &operator=(const MemBlock &) = delete; 0042 bool is_in(void *ptr) const 0043 { 0044 return !(begin > (char *)ptr || (begin + size) <= (char *)ptr); 0045 } 0046 size_t size; 0047 unsigned int ref; 0048 char *begin; 0049 MemBlock *older; 0050 MemBlock *newer; 0051 }; 0052 0053 class KZoneAllocator::Private 0054 { 0055 public: 0056 Private() 0057 : currentBlock(nullptr) 0058 , blockSize(1) 0059 , blockOffset(0) 0060 , log2(0) 0061 , num_blocks(0) 0062 , hashList(nullptr) 0063 , hashSize(0) 0064 , hashDirty(true) 0065 { 0066 } 0067 0068 /** One block is 'current' to satisfy requests. @internal */ 0069 MemBlock *currentBlock; 0070 /** Store block size from constructor. @internal */ 0071 quintptr blockSize; 0072 /** Store offset into current block; size-offset is free. @internal */ 0073 quintptr blockOffset; 0074 /** base-2 log of the block size. @internal */ 0075 unsigned int log2; 0076 /** Count total number of allocated blocks. @internal */ 0077 unsigned int num_blocks; 0078 /** Collection of lists of blocks, for lookups. @internal */ 0079 MemList **hashList; 0080 /** Count of hashes. @internal */ 0081 unsigned int hashSize; 0082 /** Flag the hashes as in need of reorganization. @internal */ 0083 bool hashDirty; 0084 }; 0085 0086 KZoneAllocator::KZoneAllocator(unsigned long _blockSize) 0087 : d(new Private) 0088 { 0089 while (d->blockSize < _blockSize) { 0090 d->blockSize <<= 1; 0091 d->log2++; 0092 } 0093 0094 /* Make sure, that a block is allocated at the first time allocate() 0095 is called (even with a 0 size). */ 0096 d->blockOffset = d->blockSize + 1; 0097 } 0098 0099 KZoneAllocator::~KZoneAllocator() 0100 { 0101 unsigned int count = 0; 0102 if (d->hashList) { 0103 /* No need to maintain the different lists in d->hashList[] anymore. 0104 I.e. no need to use delBlock(). */ 0105 for (unsigned int i = 0; i < d->hashSize; i++) { 0106 delete d->hashList[i]; 0107 } 0108 delete[] d->hashList; 0109 d->hashList = nullptr; 0110 } 0111 MemBlock *next; 0112 for (; d->currentBlock; d->currentBlock = next) { 0113 next = d->currentBlock->older; 0114 delete d->currentBlock; 0115 count++; 0116 } 0117 #ifndef NDEBUG // as this is called quite late in the app, we don't care 0118 // to use qDebug 0119 if (count > 1) { 0120 fprintf(stderr, "zone still contained %u blocks", count); 0121 } 0122 #endif 0123 delete d; 0124 } 0125 0126 void KZoneAllocator::insertHash(MemBlock *b) 0127 { 0128 quintptr adr = ((quintptr)b->begin) & (~(d->blockSize - 1)); 0129 quintptr end = ((quintptr)b->begin) + d->blockSize; 0130 while (adr < end) { 0131 quintptr key = adr >> d->log2; 0132 key = key & (d->hashSize - 1); 0133 if (!d->hashList[key]) { 0134 d->hashList[key] = new QList<MemBlock *>; 0135 } 0136 d->hashList[key]->append(b); 0137 adr += d->blockSize; 0138 } 0139 } 0140 0141 /** Add a new memory block to the pool of blocks, 0142 and reorganize the hash lists if needed. 0143 @param b block to add 0144 @internal 0145 */ 0146 void KZoneAllocator::addBlock(MemBlock *b) 0147 { 0148 b->newer = nullptr; 0149 b->older = d->currentBlock; 0150 if (d->currentBlock) { 0151 b->older->newer = b; 0152 } 0153 d->currentBlock = b; 0154 d->num_blocks++; 0155 /* If we either have no d->hashList at all, or since it's last construction 0156 there are now many more blocks we reconstruct the list. But don't 0157 make it larger than a certain maximum. */ 0158 if (d->hashList && ((d->num_blocks / 4) > d->hashSize && d->hashSize < 64 * 1024)) { 0159 d->hashDirty = true; 0160 } 0161 /* Only insert this block into the hashlists, if we aren't going to 0162 reconstruct them anyway. */ 0163 if (d->hashList && !d->hashDirty) { 0164 insertHash(b); 0165 } 0166 } 0167 0168 /** Reinitialize hash list. @internal */ 0169 void KZoneAllocator::initHash() 0170 { 0171 if (d->hashList) { 0172 for (unsigned int i = 0; i < d->hashSize; i++) { 0173 delete d->hashList[i]; 0174 } 0175 delete[] d->hashList; 0176 d->hashList = nullptr; 0177 } 0178 d->hashSize = 1; 0179 while (d->hashSize < d->num_blocks) { 0180 d->hashSize <<= 1; 0181 } 0182 if (d->hashSize < 1024) { 0183 d->hashSize = 1024; 0184 } 0185 if (d->hashSize > 64 * 1024) { 0186 d->hashSize = 64 * 1024; 0187 } 0188 d->hashList = new QList<MemBlock *> *[d->hashSize]; 0189 memset(d->hashList, 0, sizeof(QList<MemBlock *> *) * d->hashSize); 0190 d->hashDirty = false; 0191 for (MemBlock *b = d->currentBlock; b; b = b->older) { 0192 insertHash(b); 0193 } 0194 } 0195 0196 /** Delete a memory block. This @em really returns the memory to the heap. 0197 @param b block to delete 0198 @internal 0199 */ 0200 void KZoneAllocator::delBlock(MemBlock *b) 0201 { 0202 /* Update also the hashlists if we aren't going to reconstruct them 0203 soon. */ 0204 if (d->hashList && !d->hashDirty) { 0205 quintptr adr = ((quintptr)b->begin) & (~(d->blockSize - 1)); 0206 quintptr end = ((quintptr)b->begin) + d->blockSize; 0207 while (adr < end) { 0208 quintptr key = adr >> d->log2; 0209 key = key & (d->hashSize - 1); 0210 if (d->hashList[key]) { 0211 QList<MemBlock *> *list = d->hashList[key]; 0212 QList<MemBlock *>::Iterator it = list->begin(); 0213 QList<MemBlock *>::Iterator endit = list->end(); 0214 for (; it != endit; ++it) { 0215 if (*it == b) { 0216 list->erase(it); 0217 break; 0218 } 0219 } 0220 } 0221 adr += d->blockSize; 0222 } 0223 } 0224 if (b->older) { 0225 b->older->newer = b->newer; 0226 } 0227 if (b->newer) { 0228 b->newer->older = b->older; 0229 } 0230 if (b == d->currentBlock) { 0231 d->currentBlock = nullptr; 0232 d->blockOffset = d->blockSize; 0233 } 0234 delete b; 0235 d->num_blocks--; 0236 } 0237 0238 void *KZoneAllocator::allocate(size_t _size) 0239 { 0240 // Use the size of (void *) as alignment 0241 const size_t alignment = sizeof(void *) - 1; 0242 _size = (_size + alignment) & ~alignment; 0243 0244 if ((unsigned long)_size + d->blockOffset > d->blockSize) { 0245 if (_size > d->blockSize) { 0246 qCDebug(KCOMPLETION_LOG, "KZoneAllocator: allocating more than %zu bytes", (size_t)d->blockSize); 0247 return nullptr; 0248 } 0249 addBlock(new MemBlock(d->blockSize)); 0250 d->blockOffset = 0; 0251 // qDebug ("Allocating block #%d (%x)\n", d->num_blocks, d->currentBlock->begin); 0252 } 0253 void *result = (void *)(d->currentBlock->begin + d->blockOffset); 0254 d->currentBlock->ref++; 0255 d->blockOffset += _size; 0256 return result; 0257 } 0258 0259 void KZoneAllocator::deallocate(void *ptr) 0260 { 0261 if (d->hashDirty) { 0262 initHash(); 0263 } 0264 0265 quintptr key = (((quintptr)ptr) >> d->log2) & (d->hashSize - 1); 0266 const QList<MemBlock *> *list = d->hashList[key]; 0267 if (!list) { 0268 /* Can happen with certain usage pattern of intermixed free_since() 0269 and deallocate(). */ 0270 // qDebug("Uhoh"); 0271 return; 0272 } 0273 QList<MemBlock *>::ConstIterator it = list->begin(); 0274 QList<MemBlock *>::ConstIterator endit = list->end(); 0275 for (; it != endit; ++it) { 0276 MemBlock *cur = *it; 0277 if (cur->is_in(ptr)) { 0278 if (!--cur->ref) { 0279 if (cur != d->currentBlock) { 0280 delBlock(cur); 0281 } else { 0282 d->blockOffset = 0; 0283 } 0284 } 0285 return; 0286 } 0287 } 0288 /* Can happen with certain usage pattern of intermixed free_since() 0289 and deallocate(). */ 0290 // qDebug("Uhoh2"); 0291 } 0292 0293 void KZoneAllocator::free_since(void *ptr) 0294 { 0295 /* If we have a d->hashList and it's not yet dirty, see, if we will dirty 0296 it by removing too many blocks. This will make the below delBlock()s 0297 faster. */ 0298 if (d->hashList && !d->hashDirty) { 0299 const MemBlock *b; 0300 unsigned int removed = 0; 0301 for (b = d->currentBlock; b; b = b->older, removed++) { 0302 if (b->is_in(ptr)) { 0303 break; 0304 } 0305 } 0306 if (d->hashSize >= 4 * (d->num_blocks - removed)) { 0307 d->hashDirty = true; 0308 } 0309 } 0310 while (d->currentBlock && !d->currentBlock->is_in(ptr)) { 0311 d->currentBlock = d->currentBlock->older; 0312 delBlock(d->currentBlock->newer); 0313 } 0314 d->blockOffset = ((char *)ptr) - d->currentBlock->begin; 0315 }