/** * Copyright (c) 2021 OceanBase * OceanBase Database Proxy(ODP) is licensed under Mulan PubL v2. * You can use this software according to the terms and conditions of the Mulan PubL v2. * You may obtain a copy of Mulan PubL v2 at: * http://license.coscl.org.cn/MulanPubL-2.0 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. * See the Mulan PubL v2 for more details. */ #include "object_set.h" #include "lib/allocator/ob_mod_define.h" #include "lib/allocator/ob_tc_malloc.h" #include "lib/ob_define.h" #include "block_mgr.h" using namespace oceanbase::lib; const double ObjectSet::FREE_LISTS_BUILD_RATIO = .1; const double ObjectSet::BLOCK_CACHE_RATIO = .1; ObjectSet::ObjectSet() : mutex_(), bs_(), mod_set_(), blist_(NULL), last_remainder_(NULL), bm_(NULL), free_lists_(NULL), alloc_bytes_(0), used_bytes_(0), hold_bytes_(0), allocs_(0), normal_alloc_bytes_(0), normal_used_bytes_(0), normal_hold_bytes_(0) { } ObjectSet::~ObjectSet() { reset(); if (blist_ != NULL) { free_block(blist_); blist_ = NULL; } } AObject *ObjectSet::alloc_object( const uint64_t size, const ObMemAttr &attr) { const uint64_t alloc_size = size; const uint64_t adj_size = MAX(alloc_size, MIN_AOBJECT_SIZE); const uint64_t all_size = adj_size + AOBJECT_META_SIZE; AObject *obj = NULL; if (alloc_size > UINT32_MAX || 0 == alloc_size) { // not support } else if (all_size <= INTACT_NORMAL_AOBJECT_SIZE) { const uint32_t cls = (uint32_t)(1+ ((all_size - 1) / AOBJECT_CELL_BYTES)); obj = alloc_normal_object(cls, attr); if (NULL != obj) { normal_alloc_bytes_ += alloc_size; normal_used_bytes_ += all_size; } } else { obj = alloc_big_object(adj_size, attr); abort_unless(NULL == obj || obj->in_use_); } if (NULL != obj) { abort_unless(obj->in_use_); abort_unless(obj->is_valid()); obj->mod_id_ = static_cast(attr.mod_id_); reinterpret_cast(obj->data_[alloc_size]) = AOBJECT_TAIL_MAGIC_CODE; obj->alloc_bytes_ = static_cast(alloc_size); allocs_++; alloc_bytes_ += alloc_size; used_bytes_ += all_size; mod_set_.mod_update( attr.mod_id_, obj->hold(), static_cast(obj->alloc_bytes_)); } return obj; } AObject *ObjectSet::realloc_object( AObject *obj, const uint64_t size, const ObMemAttr &attr) { AObject *new_obj = NULL; uint64_t copy_size = 0; if (NULL == obj) { new_obj = alloc_object(size, attr); } else { abort_unless(obj->is_valid()); if (obj->is_large_) { copy_size = MIN(obj->alloc_bytes_, size); } else { copy_size = MIN( size, (obj->nobjs_ - META_CELLS) * AOBJECT_CELL_BYTES); } new_obj = alloc_object(size, attr); if (NULL != new_obj && copy_size != 0) { memmove(new_obj->data_, obj->data_, copy_size); } free_object(obj); } return new_obj; } AObject *ObjectSet::alloc_normal_object(const uint32_t cls, const ObMemAttr &attr) { AObject *obj = NULL; // best fit if (NULL != bm_ && NULL != free_lists_ && bm_->isset(cls)) { obj = (*free_lists_)[cls]; take_off_free_object(obj); obj->in_use_ = true; } // last remainder if (NULL == obj && NULL != last_remainder_) { if (cls <= last_remainder_->nobjs_) { obj = split_obj(last_remainder_, cls, last_remainder_); obj->in_use_ = true; } } // next first fit if (NULL == obj && NULL != bm_ && NULL != free_lists_) { obj = get_free_object(cls); if (obj != NULL) { obj->in_use_ = true; } } // alloc new block, split into two part: // // 1. with cls cells that return to user // 2. the remainder as last remainder // if (NULL == obj) { if (NULL != bm_ && NULL != free_lists_ && NULL != last_remainder_) { AObject * const lrback = last_remainder_; last_remainder_ = NULL; add_free_object(merge_obj(lrback)); } ABlock *block = alloc_block(ABLOCK_SIZE, attr); if (NULL != block) { normal_hold_bytes_ += ABLOCK_SIZE; AObject *remainder = NULL; obj = new (block->data_) AObject(); obj->nobjs_ = static_cast(CELLS_PER_BLOCK); obj = split_obj(obj, cls, remainder); obj->in_use_ = true; if (remainder != NULL) { last_remainder_ = remainder; } } } return obj; } AObject *ObjectSet::get_free_object(const uint32_t cls) { AObject *obj = NULL; if (NULL != bm_ && NULL != free_lists_) { const int ffs = bm_->find_first_significant(cls); if (ffs >= 0) { if (free_lists_[ffs] != NULL) { obj = (*free_lists_)[ffs]; } } if (ffs >= 0 && NULL != obj && ffs >= static_cast(cls)) { if (NULL != last_remainder_ && obj->block() == last_remainder_->block() && (obj->phy_next(obj->nobjs_) == last_remainder_ || last_remainder_->phy_next(last_remainder_->nobjs_) == obj)) { last_remainder_ = merge_obj(last_remainder_); } else { if (NULL != last_remainder_) { add_free_object(last_remainder_); last_remainder_ = NULL; } take_off_free_object(obj); last_remainder_ = obj; } obj = split_obj(last_remainder_, cls, last_remainder_); } } if (NULL != obj) { abort_unless(obj->is_valid()); } return obj; } void ObjectSet::add_free_object(AObject *obj) { abort_unless(NULL != obj); abort_unless(NULL != bm_ && NULL != free_lists_); abort_unless(obj->is_valid()); if (obj->nobjs_ >= MIN_FREE_CELLS) { AObject *&head = (*free_lists_)[obj->nobjs_]; if (bm_->isset(obj->nobjs_)) { obj->prev_ = head->prev_; obj->next_ = head; obj->prev_->next_ = obj; obj->next_->prev_ = obj; head = obj; } else { bm_->set(obj->nobjs_); obj->prev_ = obj; obj->next_ = obj; head = obj; } } } void ObjectSet::take_off_free_object(AObject *obj) { abort_unless(NULL != obj); abort_unless(NULL != bm_ && NULL != free_lists_); abort_unless(obj->is_valid()); if (obj->in_use_) { } else if (obj->nobjs_ < MIN_FREE_CELLS) { } else if (obj->next_ == obj) { bm_->unset(obj->nobjs_); } else { AObject *&head = (*free_lists_)[obj->nobjs_]; if (head == obj) { head = head->next_; } obj->prev_->next_ = obj->next_; obj->next_->prev_ = obj->prev_; } } void ObjectSet::free_normal_object(AObject *obj) { abort_unless(NULL != obj); abort_unless(obj->is_valid()); normal_alloc_bytes_ -= obj->alloc_bytes_; normal_used_bytes_ -= obj->nobjs_ * AOBJECT_CELL_BYTES; if (NULL == bm_ || NULL == free_lists_) { obj->in_use_ = false; } else { AObject *newobj = merge_obj(obj); if (newobj->nobjs_ == CELLS_PER_BLOCK) { // we can cache `BLOCK_CACHE_RATIO` of hold blocks if (normal_used_bytes_ == 0 || ((double)normal_used_bytes_ > (double)normal_hold_bytes_ * (1. - BLOCK_CACHE_RATIO))) { add_free_object(newobj); } else { hold_bytes_ -= ABLOCK_SIZE; normal_hold_bytes_ -= ABLOCK_SIZE; free_block(newobj->block()); } } else { add_free_object(newobj); } } } ABlock *ObjectSet::alloc_block(const uint64_t size, const ObMemAttr &attr) { ABlock *block = bs_.alloc_block(size, attr); if (NULL != block) { if (NULL != blist_) { block->prev_ = blist_->prev_; block->next_ = blist_; blist_->prev_->next_ = block; blist_->prev_ = block; } else { block->prev_ = block->next_ = block; blist_ = block; } hold_bytes_ += size; block->obj_set_ = this; } return block; } void ObjectSet::free_block(ABlock *block) { abort_unless(NULL != block); abort_unless(block->is_valid()); if (block == blist_) { blist_ = blist_->next_; if (block == blist_) { blist_ = NULL; } } block->prev_->next_ = block->next_; block->next_->prev_ = block->prev_; // The pbmgr shouldn't be NULL or there'll be memory leak. bs_.free_block(block); } AObject *ObjectSet::alloc_big_object(const uint64_t size, const ObMemAttr &attr) { AObject *obj = NULL; ABlock *block = alloc_block(size + AOBJECT_META_SIZE, attr); if (NULL != block) { obj = new (block->data_) AObject(); obj->is_large_ = true; obj->in_use_ = true; obj->alloc_bytes_ = static_cast(size); } return obj; } void ObjectSet::free_big_object(AObject *obj) { abort_unless(NULL != obj); abort_unless(NULL != obj->block()); abort_unless(obj->is_valid()); hold_bytes_ -= obj->alloc_bytes_ + AOBJECT_META_SIZE; free_block(obj->block()); } void ObjectSet::free_object(AObject *obj) { abort_unless(obj != NULL); abort_unless(obj->is_valid()); abort_unless( AOBJECT_TAIL_MAGIC_CODE == reinterpret_cast(obj->data_[obj->alloc_bytes_])); // OB_LOG(DEBUG, "free", "ptr", P(obj->data_)); if (obj->MAGIC_CODE_ == AOBJECT_MAGIC_CODE || obj->MAGIC_CODE_ == BIG_AOBJECT_MAGIC_CODE) { abort_unless(obj->in_use_); const int64_t hold = obj->hold(); const int64_t used = obj->alloc_bytes_; mod_set_.mod_update(obj->mod_id_, -hold, -used); alloc_bytes_ -= obj->alloc_bytes_; used_bytes_ -= hold; obj->in_use_ = false; if (!obj->is_large_) { free_normal_object(obj); } else { free_big_object(obj); } } // 1. havn't created free lists // 2. touch free lists build ratio if (NULL == bm_ && NULL == free_lists_ && normal_hold_bytes_ > ABLOCK_SIZE && (static_cast(normal_used_bytes_) < static_cast(normal_hold_bytes_) * (1. - FREE_LISTS_BUILD_RATIO))) { build_free_lists(); last_remainder_ = NULL; } } void ObjectSet::reset() { while (NULL != blist_) { if (blist_->next_ == blist_) { // The only block last_remainder_ = new (blist_->data_) AObject(); last_remainder_->nobjs_ = static_cast(CELLS_PER_BLOCK); last_remainder_->in_use_ = false; normal_hold_bytes_ = hold_bytes_ = ABLOCK_SIZE; break; } free_block(blist_); } bm_ = NULL; free_lists_ = NULL; // last_remainder_ = NULL; alloc_bytes_ = 0; used_bytes_ = 0; allocs_ = 0; normal_alloc_bytes_ = 0; normal_used_bytes_ = 0; mod_set_.reset(); } bool ObjectSet::build_free_lists() { abort_unless(NULL == bm_ && NULL == free_lists_); ObMemAttr attr; attr.mod_id_ = common::ObModIds::OB_OBJ_FREELISTS; ABlock *new_block = alloc_block(sizeof (FreeLists) + sizeof (BitMap), attr); OB_LOG(INFO, "build free lists"); if (NULL != new_block) { free_lists_ = new (new_block->data_) FreeLists[0]; bm_ = new (&new_block->data_[sizeof (FreeLists)]) BitMap(); // the new block is at tail of blist_, the BREAK and CONTINUE is // necessary otherwise the code will be very ugly and bug prone. for (ABlock *block = blist_; block != new_block; block = block->next_) { AObject *obj = reinterpret_cast(block->data_); abort_unless(obj->is_valid()); // ignore large object if (!obj->is_large_) { for (;;) { while (obj->in_use_ && !obj->is_last()) { AObject *next_obj = obj->phy_next(obj->nobjs_); next_obj->nobjs_prev_ = obj->nobjs_; obj = next_obj; abort_unless(obj->is_valid()); } if (obj->is_last()) { obj->nobjs_ = static_cast(CELLS_PER_BLOCK - obj->obj_offset_); if (!obj->in_use_) { add_free_object(obj); } break; } // the first object not in use AObject *first = obj; obj = obj->phy_next(obj->nobjs_); abort_unless(obj->is_valid()); while (!obj->in_use_ && !obj->is_last()) { obj = obj->phy_next(obj->nobjs_); abort_unless(obj->is_valid()); } if (obj->is_last() && !obj->in_use_) { first->nobjs_ = static_cast(CELLS_PER_BLOCK - first->obj_offset_); add_free_object(first); break; } else { first->nobjs_ = static_cast(obj->obj_offset_ - first->obj_offset_); obj->nobjs_prev_ = first->nobjs_; add_free_object(first); } } // for one block } // if (!obj->is_large_) } // for all block } return new_block != NULL; }