blob: 1d74d86011be33693b6869626166af8d173b5f73 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
#include "runtime/bufferpool/suballocator.h"
#include <new>
#include "runtime/bufferpool/reservation-tracker.h"
#include "util/bit-util.h"
#include "common/names.h"
namespace impala {
constexpr int Suballocator::LOG_MAX_ALLOCATION_BYTES;
constexpr int64_t Suballocator::MAX_ALLOCATION_BYTES;
constexpr int Suballocator::LOG_MIN_ALLOCATION_BYTES;
constexpr int64_t Suballocator::MIN_ALLOCATION_BYTES;
const int Suballocator::NUM_FREE_LISTS;
Suballocator::Suballocator(
BufferPool* pool, BufferPool::ClientHandle* client, int64_t min_buffer_len)
: pool_(pool), client_(client), min_buffer_len_(min_buffer_len), allocated_(0) {}
Suballocator::~Suballocator() {
// All allocations should be free and buffers deallocated.
DCHECK_EQ(allocated_, 0);
for (int i = 0; i < NUM_FREE_LISTS; ++i) {
DCHECK(free_lists_[i] == nullptr);
}
}
Status Suballocator::Allocate(int64_t bytes, unique_ptr<Suballocation>* result) {
DCHECK_GE(bytes, 0);
if (UNLIKELY(bytes > MAX_ALLOCATION_BYTES)) {
return Status(Substitute("Requested memory allocation of $0 bytes, larger than max "
"supported of $1 bytes",
bytes, MAX_ALLOCATION_BYTES));
}
unique_ptr<Suballocation> free_node;
bytes = max(bytes, MIN_ALLOCATION_BYTES);
const int target_list_idx = ComputeListIndex(bytes);
for (int i = target_list_idx; i < NUM_FREE_LISTS; ++i) {
free_node = PopFreeListHead(i);
if (free_node != nullptr) break;
}
if (free_node == nullptr) {
// Unable to find free allocation, need to get more memory from buffer pool.
RETURN_IF_ERROR(AllocateBuffer(bytes, &free_node));
if (free_node == nullptr) {
*result = nullptr;
return Status::OK();
}
}
// Free node may be larger than required.
const int free_list_idx = ComputeListIndex(free_node->len_);
if (free_list_idx != target_list_idx) {
RETURN_IF_ERROR(SplitToSize(move(free_node), bytes, &free_node));
DCHECK(free_node != nullptr);
}
free_node->in_use_ = true;
allocated_ += free_node->len_;
*result = move(free_node);
return Status::OK();
}
int Suballocator::ComputeListIndex(int64_t bytes) const {
return BitUtil::Log2CeilingNonZero64(bytes) - LOG_MIN_ALLOCATION_BYTES;
}
Status Suballocator::AllocateBuffer(int64_t bytes, unique_ptr<Suballocation>* result) {
DCHECK_LE(bytes, MAX_ALLOCATION_BYTES);
const int64_t buffer_len = max(min_buffer_len_, BitUtil::RoundUpToPowerOfTwo(bytes));
if (!client_->IncreaseReservationToFit(buffer_len)) {
*result = nullptr;
return Status::OK();
}
unique_ptr<Suballocation> free_node;
RETURN_IF_ERROR(Suballocation::Create(&free_node));
RETURN_IF_ERROR(pool_->AllocateBuffer(client_, buffer_len, &free_node->buffer_));
free_node->data_ = free_node->buffer_.data();
free_node->len_ = buffer_len;
*result = move(free_node);
return Status::OK();
}
Status Suballocator::SplitToSize(unique_ptr<Suballocation> free_node,
int64_t target_bytes, unique_ptr<Suballocation>* result) {
DCHECK(!free_node->in_use_);
DCHECK_GT(free_node->len_, target_bytes);
const int free_list_idx = ComputeListIndex(free_node->len_);
const int target_list_idx = ComputeListIndex(target_bytes);
// Preallocate nodes to avoid handling allocation failures during splitting.
// Need two nodes per level for the left and right children.
const int num_nodes = (free_list_idx - target_list_idx) * 2;
constexpr int MAX_NUM_NODES = NUM_FREE_LISTS * 2;
unique_ptr<Suballocation> nodes[MAX_NUM_NODES];
for (int i = 0; i < num_nodes; ++i) {
if (!Suballocation::Create(&nodes[i]).ok()) {
// Add the free node to the free list to restore the allocator to an internally
// consistent state.
AddToFreeList(move(free_node));
return Status("Failed to allocate list node in Suballocator");
}
}
// Iteratively split from the current size down to the target size. We will return
// the leftmost descendant node.
int next_node = 0;
for (int i = free_list_idx; i > target_list_idx; --i) {
DCHECK_EQ(free_node->len_, 1LL << (i + LOG_MIN_ALLOCATION_BYTES));
unique_ptr<Suballocation> left_child = move(nodes[next_node++]);
unique_ptr<Suballocation> right_child = move(nodes[next_node++]);
DCHECK_LE(next_node, num_nodes);
const int64_t child_len = free_node->len_ / 2;
left_child->data_ = free_node->data_;
right_child->data_ = free_node->data_ + child_len;
left_child->len_ = right_child->len_ = child_len;
left_child->buddy_ = right_child.get();
right_child->buddy_ = left_child.get();
free_node->in_use_ = true;
left_child->parent_ = move(free_node);
AddToFreeList(move(right_child));
free_node = move(left_child);
}
*result = move(free_node);
return Status::OK();
}
void Suballocator::Free(unique_ptr<Suballocation> allocation) {
if (allocation == nullptr) return;
DCHECK(allocation->in_use_);
allocation->in_use_ = false;
allocated_ -= allocation->len_;
// Iteratively coalesce buddies until the buddy is in use or we get to the root.
// This ensures that all buddies in the free lists are coalesced. I.e. we do not
// have two buddies in the same free list.
unique_ptr<Suballocation> curr_allocation = move(allocation);
while (curr_allocation->buddy_ != nullptr) {
if (curr_allocation->buddy_->in_use_) {
// If the buddy is not free we can't coalesce, just add it to free list.
AddToFreeList(move(curr_allocation));
return;
}
unique_ptr<Suballocation> buddy = RemoveFromFreeList(curr_allocation->buddy_);
curr_allocation = CoalesceBuddies(move(curr_allocation), move(buddy));
}
// Reached root, which is an entire free buffer. We are not using it, so free up memory.
DCHECK(curr_allocation->buffer_.is_open());
pool_->FreeBuffer(client_, &curr_allocation->buffer_);
curr_allocation.reset();
}
void Suballocator::AddToFreeList(unique_ptr<Suballocation> node) {
DCHECK(!node->in_use_);
int list_idx = ComputeListIndex(node->len_);
if (free_lists_[list_idx] != nullptr) {
free_lists_[list_idx]->prev_free_ = node.get();
}
node->next_free_ = move(free_lists_[list_idx]);
DCHECK(node->prev_free_ == nullptr);
free_lists_[list_idx] = move(node);
}
unique_ptr<Suballocation> Suballocator::RemoveFromFreeList(Suballocation* node) {
DCHECK(node != nullptr);
const int list_idx = ComputeListIndex(node->len_);
if (node->next_free_ != nullptr) {
node->next_free_->prev_free_ = node->prev_free_;
}
unique_ptr<Suballocation>* ptr_from_prev = node->prev_free_ == nullptr ?
&free_lists_[list_idx] :
&node->prev_free_->next_free_;
node->prev_free_ = nullptr;
unique_ptr<Suballocation> result = move(*ptr_from_prev);
*ptr_from_prev = move(node->next_free_);
return result;
}
unique_ptr<Suballocation> Suballocator::PopFreeListHead(int list_idx) {
if (free_lists_[list_idx] == nullptr) return nullptr;
unique_ptr<Suballocation> result = move(free_lists_[list_idx]);
DCHECK(result->prev_free_ == nullptr);
if (result->next_free_ != nullptr) {
result->next_free_->prev_free_ = nullptr;
}
free_lists_[list_idx] = move(result->next_free_);
return result;
}
unique_ptr<Suballocation> Suballocator::CoalesceBuddies(
unique_ptr<Suballocation> b1, unique_ptr<Suballocation> b2) {
DCHECK(!b1->in_use_);
DCHECK(!b2->in_use_);
DCHECK_EQ(b1->buddy_, b2.get());
DCHECK_EQ(b2->buddy_, b1.get());
// Only the left child's parent should be present.
DCHECK((b1->parent_ != nullptr) ^ (b2->parent_ != nullptr));
unique_ptr<Suballocation> parent =
b1->parent_ != nullptr ? move(b1->parent_) : move(b2->parent_);
parent->in_use_ = false;
return parent;
}
Status Suballocation::Create(unique_ptr<Suballocation>* new_suballocation) {
// Allocate from system allocator for simplicity. We don't expect this to be
// performance critical or to be used for small allocations where CPU/memory
// overhead of these allocations might be a consideration.
new_suballocation->reset(new (nothrow) Suballocation());
if (*new_suballocation == nullptr) {
return Status(TErrorCode::MEM_ALLOC_FAILED, sizeof(Suballocation));
}
return Status::OK();
}
}