Loki Allocator 有三个类 这玩意对比起 pool allocator 的优点就在于 它有把内存还给系统
但是它作为一个 内存分配器 却在里面使用了 vector 作为容器… 按道理来说 应该在其内部实现一个简易版的 vector 才不会那么奇怪 但也没所谓 只是 先用了一次标准库的分配器 后续使用容器的时候 就可以用 Loki 分配器了.
- Chunk
- FixedAllocator
- SmallObjAllocator
Chunk 解剖
Chunk 是整个分配器的最底层 里面主要是三个 成员变量
1 2 3 4 5 6
| unsigned char * pData_;
unsigned char firstAvailableBlock_;
unsigned char blocksAvailable_;
|
Chunk 的几个关键函数 已进行 剪裁 和 修改
Chunk 分配
分配流程很简单 就是 申请了内存以后 将每个小区块的第一个字节设置为索引 排好号
取的流程:
- 从 当前可用区块的第一块索引中取 得当前可用区块
- 当前可用区块会被返回
- 被返回的可用区块中的索引 被设置到 当前可用区块索引去(这样下一次就会使用到它)
- 可用区块数目 - 1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| firstAvailableBlock_ = 4 blocksAvailable_ = 63 ------------------------- 4 ------------------------- 0 ------------------------- 3 ------------------------- 1 ------------------------- 2 <- 即将被分出去 ------------------------- 6 ------------------------- 7 ------------------------- ... ------------------------- 64 ------------------------- 首先 因为当前可用区块索引 为 4 所以会找到 索引号为 2的区块 其次 找到了以后 这个区块的索引号 会被设置到 当前可用区块索引去 这样下次就会用索引为 2 的区块 然后 区块可用数 - 1 ------------------------- firstAvailableBlock_ = 2 blocksAvailable_ = 62 ------------------------- 4 ------------------------- 0 ------------------------- 3 <-即将被分出去 ------------------------- 1 ------------------------- 已经被分配了 ------------------------- 6 ------------------------- 7 ------------------------- ... ------------------------- 64 -------------------------
|
Chunk 回收
存的流程:
- 首先 我们要明确 回收回来的区块 下次分配优先分配
- 计算 回收回来的区块 应该放到哪里 (idx 为索引)
- 那么 当前可用区块的索引号就应该设置为 idx 这样才能保证下次一定先用它
- 那之前的索引号 就会被设置到 回收回来的区块的索引中去(分配完回收的区块后 就会分配之前本应该被分配的区块 后来因为回收了新的区块 而没被分配出去的区块 好绕口…)
- 当前可用区块 + 1
Chunk 函数实现
这块设计我个人觉得有一点不好的地方 就是 Chunk::Init
的时候 会传入 blockSize
但是 Chunk::Allocate
也会传入 blockSize
如果 这两块的 blockSize
不一致 则会出错
个人想法是 在 Init 的时候 将 blockSize
存入 data member 中去 在 Allocator 则不再传入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| bool Chunk::Init( ::std::size_t blockSize, unsigned char blocks ) { const ::std::size_t allocSize = blockSize * blocks; pData_ = static_cast< unsigned char * >( ::operator new ( allocSize ) ); Reset( blockSize, blocks ); return true; }
void Chunk::Reset(::std::size_t blockSize, unsigned char blocks) { firstAvailableBlock_ = 0; blocksAvailable_ = blocks; unsigned char i = 0; for ( unsigned char * p = pData_; i != blocks; p += blockSize ) { *p = ++i; } }
void Chunk::Release() { ::operator delete ( pData_ ); } void* Chunk::Allocate(::std::size_t blockSize) { if ( 0 == blocksAvailable_ ) { return nullptr; } unsigned char * pResult = pData_ + (firstAvailableBlock_ * blockSize); firstAvailableBlock_ = *pResult; --blocksAvailable_; return pResult; } void Chunk::Deallocate(void* p, ::std::size_t blockSize) { unsigned char* toRelease = static_cast<unsigned char*>(p); unsigned char index = static_cast< unsigned char >( ( toRelease - pData_ ) / blockSize);
*toRelease = firstAvailableBlock_; firstAvailableBlock_ = index; ++blocksAvailable_; }
|
FixedAllocator 解剖
FixedAllocator 里面存着 vector
1 2 3 4 5 6 7 8 9 10 11 12 13
| typedef ::std::vector< Chunk > Chunks; Chunks chunks_;
Chunk * allocChunk_;
Chunk * deallocChunk_;
Chunk * emptyChunk_;
|
FixedAllocator 分配
分配流程 简单的说就是 用 Chunk 去分配 那么 FixedAllocator 最主要的任务就是去找 能够分配内存的 Chunk
- 先看看 allocChunk_(指向上次分配过区块的Chunk) 有没有效 有效则直接分配
- 无效 则去找 emptyChunk_(指向一个空Chunk) 有效直接分配
- 无效 则依次遍历 vector 找到 其中一个 能分配区块的 Chunk
- 找不到 则直接 创建一个新的 Chunk 并将其 push_back 到 vector 中
FixedAllocator 回收
回收流程 就是先去找 要回收的区块在哪个 Chunk 中 这里先从 上一次回收过区块的 Chunk 中查看 再从 上一次分配过区块的 Chunk 中查看 如果都没有 则用一种 特殊的搜索方法 就是从 上一次回收过区块的 Chunk 作为临界点 每次循环 一次向上找 一次向下找 直到找到以后 再去调用释放函数 如果发生了全回收 则 看看是否已经有一块全回收了(因为要2块全回收才释放掉其中一块 避免突然又要用到那块Chunk)
FixedAllocator 函数实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
| void * FixedAllocator::Allocate( void ) { if ( ( nullptr == allocChunk_ ) || 0 == allocChunk_->blocksAvailable_ ) { if ( nullptr != emptyChunk_ ) { allocChunk_ = emptyChunk_; emptyChunk_ = nullptr; } else { Chunks::iterator i = chunks_.begin(); for ( ; ; ++i ) { if ( chunks_.end() == i ) { chunks_.push_back(Chunk()); Chunk &newChunk = chunks_.back(); newChunk.Init(blockSize_, numBlocks_); allocChunk_ = &newChunk; deallocChunk_ = &chunks_.front(); break; } if ( i->blocksAvailable_ > 0) { allocChunk_ = &*i; break; } } } } else if ( allocChunk_ == emptyChunk_) { emptyChunk_ = nullptr; } void * place = allocChunk_->Allocate( blockSize_ ); return place; } bool FixedAllocator::Deallocate( void * p ) { Chunk * foundChunk = nullptr; const ::std::size_t chunkLength = numBlocks_ * blockSize_; if ( p >= deallocChunk_->pData_ && p < deallocChunk_->pData_ + chunkLength ) { foundChunk = deallocChunk_; } else if ( p >= allocChunk_->pData_ && p < allocChunk_->pData_ + chunkLength ) { foundChunk = allocChunk_; } else { foundChunk = VicinityFind( p ); } if ( nullptr == foundChunk ) { return false; } deallocChunk_ = foundChunk; DoDeallocate(p);
return true; } Chunk * FixedAllocator::VicinityFind( void * p ) const { if ( chunks_.empty() ) { return nullptr; }
const ::std::size_t chunkLength = numBlocks_ * blockSize_;
Chunk * lo = deallocChunk_; Chunk * hi = deallocChunk_ + 1; const Chunk * loBound = &chunks_.front(); const Chunk * hiBound = &chunks_.back() + 1;
if ( hi == hiBound ) { hi = nullptr; }
for (;;) { if (lo) {
if ( p >= lo->pData_ && p < lo->pData_ + chunkLength ) { return lo; } if ( lo == loBound ) { lo = nullptr; if ( nullptr == hi ) { break; } } else { --lo; } }
if (hi) { if ( p >= hi->pData_ && p < hi->pData_ + chunkLength ) { return hi; } if ( ++hi == hiBound ) { hi = nullptr; if ( nullptr == lo ) { break; } } } } return nullptr; }
void FixedAllocator::DoDeallocate(void* p) { deallocChunk_->Deallocate(p, blockSize_); if ( deallocChunk_->blocksAvailable_ == numBlocks_ ) { if ( nullptr != emptyChunk_ ) { Chunk * lastChunk = &chunks_.back(); if ( lastChunk == deallocChunk_ ) { deallocChunk_ = emptyChunk_; } else if ( lastChunk != emptyChunk_ ) { ::std::swap( *emptyChunk_, *lastChunk ); } lastChunk->Release(); chunks_.pop_back(); if ( ( allocChunk_ == lastChunk ) || allocChunk_->blocksAvailable_ == 0 ) { allocChunk_ = deallocChunk_; } } emptyChunk_ = deallocChunk_; } }
|
SmallObjAllocator 解剖
1 2 3 4 5 6
| ::Loki::Private::FixedAllocator * pool_;
const ::std::size_t maxSmallObjectSize_;
const std::size_t objectAlignSize_;
|
SmallObjAllocator 分配实现
分配内存 简单的说 就是从 vector找合适的 FixedAllocator 然后再用底层的 FixedAllocator 去找 Chunk 分配内存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| void * SmallObjAllocator::Allocate( ::std::size_t numBytes, bool doThrow ) { if ( numBytes > GetMaxObjectSize() ) { return DefaultAllocator( numBytes, doThrow ); }
if ( 0 == numBytes ) { numBytes = 1; } const ::std::size_t index = GetOffset( numBytes, GetAlignment() ) - 1; const ::std::size_t allocCount = GetOffset( GetMaxObjectSize(), GetAlignment() );
FixedAllocator & allocator = pool_[ index ]; void * place = allocator.Allocate();
if ( ( nullptr == place ) && TrimExcessMemory() ) { place = allocator.Allocate(); }
if ( ( nullptr == place ) && doThrow ) { throw std::bad_alloc(); } return place; }
|
这里有一个有意思的点 SmallObjAllocator::TrimExcessMemory()
这个函数 会遍历所有的 FixedAllocator 然后 调用 FixedAllocator::TrimChunkList()
这里面有一段代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| bool FixedAllocator::TrimChunkList( void ) {
{ Chunks temp( chunks_ ); temp.swap( chunks_ ); }
return true; }
|
SmallObjAllocator 回收实现
回收内存 找回收的内存对应的 FixedAllocator 然后调用下一层的 FixedAllocator 去进行真的是回收操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| void SmallObjAllocator::Deallocate( void * p ) { if ( nullptr == p ) { return; }
FixedAllocator * pAllocator = nullptr; const ::std::size_t allocCount = GetOffset( GetMaxObjectSize(), GetAlignment() ); Chunk * chunk = nullptr;
for ( ::std::size_t ii = 0; ii < allocCount; ++ii ) { chunk = pool_[ ii ].HasBlock( p ); if ( nullptr != chunk ) { pAllocator = &pool_[ ii ]; break; } } if ( nullptr == pAllocator ) { DefaultDeallocator( p ); return; }
const bool found = pAllocator->Deallocate( p, chunk ); }
|