blob: 460aba5c4e56b64163f2a6b36810fcec116c55a9 [file] [log] [blame]
// Copyright 2010 Google Inc. All Rights Reserved
//
// 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 "kudu/util/memory/arena.h"
#include <algorithm>
#include <memory>
#include <mutex>
using std::min;
using std::unique_ptr;
namespace kudu {
template <bool THREADSAFE>
const size_t ArenaBase<THREADSAFE>::kMinimumChunkSize = 16;
// The max size of our allocations is set to this magic number
// corresponding to 127 tcmalloc pages (each being 8KB). tcmalloc
// internally keeps a free-list of spans up to this size. Larger
// allocations have to go through a linear search through free
// space, which can get quite slow in a fragmented heap.
//
// See the definition of kMaxPages in tcmalloc/src/common.h
// as well as https://github.com/gperftools/gperftools/issues/535
// for a description of the performance issue.
constexpr int kMaxTcmallocFastAllocation = 8192 * 127;
template <bool THREADSAFE>
ArenaBase<THREADSAFE>::ArenaBase(BufferAllocator* buffer_allocator,
size_t initial_buffer_size)
: buffer_allocator_(buffer_allocator),
max_buffer_size_(kMaxTcmallocFastAllocation),
arena_footprint_(0) {
AddComponent(CHECK_NOTNULL(NewComponent(initial_buffer_size, 0)));
}
template <bool THREADSAFE>
ArenaBase<THREADSAFE>::ArenaBase(size_t initial_buffer_size)
: ArenaBase<THREADSAFE>(HeapBufferAllocator::Get(),
initial_buffer_size) {
}
template <bool THREADSAFE>
void ArenaBase<THREADSAFE>::SetMaxBufferSize(size_t size) {
DCHECK_LE(size, kMaxTcmallocFastAllocation);
max_buffer_size_ = size;
}
template <bool THREADSAFE>
void *ArenaBase<THREADSAFE>::AllocateBytesFallback(const size_t size, const size_t align) {
std::lock_guard<mutex_type> lock(component_lock_);
// It's possible another thread raced with us and already allocated
// a new component, in which case we should try the "fast path" again
Component* cur = AcquireLoadCurrent();
void * result = cur->AllocateBytesAligned(size, align);
if (PREDICT_FALSE(result != nullptr)) return result;
// Really need to allocate more space.
size_t next_component_size = min(2 * cur->size(), max_buffer_size_);
// But, allocate enough, even if the request is large. In this case,
// might violate the "max_buffer_size_" bound.
// Component allocation is guaranteed to be 16-byte aligned, see NewComponent(),
// but we also need to support higher alignment values of 32 and 64 bytes and
// hence we add padding so that first request to allocate bytes after new
// component creation doesn't fail.
size_t aligned_size;
if (align <= 16) {
aligned_size = size;
} else {
DCHECK(align == 32 || align == 64);
aligned_size = size + align - 16;
}
if (next_component_size < aligned_size) {
next_component_size = aligned_size;
}
// If soft quota is exhausted we will only get the "minimal" amount of memory
// we ask for. In this case if we always use "aligned_size" as minimal, we may degrade
// to allocating a lot of tiny components, one for each string added to the
// arena. This would be very inefficient, so let's first try something between
// "aligned_size" and "next_component_size". If it fails due to hard quota being
// exhausted, we'll fall back to using "aligned_size" as minimal.
size_t minimal = (aligned_size + next_component_size) / 2;
CHECK_LE(aligned_size, minimal);
CHECK_LE(minimal, next_component_size);
// Now, just make sure we can actually get the memory.
Component* component = NewComponent(next_component_size, minimal);
if (component == nullptr) {
component = NewComponent(next_component_size, aligned_size);
}
if (!component) return nullptr;
// Now, must succeed. The component has at least 'size' bytes.
result = component->AllocateBytesAligned(size, align);
CHECK(result != nullptr);
// Now add it to the arena.
AddComponent(component);
return result;
}
template <bool THREADSAFE>
typename ArenaBase<THREADSAFE>::Component* ArenaBase<THREADSAFE>::NewComponent(
size_t requested_size,
size_t minimum_size) {
Buffer* buffer = buffer_allocator_->BestEffortAllocate(requested_size,
minimum_size);
if (buffer == nullptr) return nullptr;
CHECK_EQ(reinterpret_cast<uintptr_t>(buffer->data()) & (16 - 1), 0)
<< "Components should be 16-byte aligned: " << buffer->data();
ASAN_POISON_MEMORY_REGION(buffer->data(), buffer->size());
return new Component(buffer);
}
// LOCKING: component_lock_ must be held by the current thread.
template <bool THREADSAFE>
void ArenaBase<THREADSAFE>::AddComponent(ArenaBase::Component *component) {
ReleaseStoreCurrent(component);
arena_.push_back(unique_ptr<Component>(component));
arena_footprint_ += component->size();
}
template <bool THREADSAFE>
void ArenaBase<THREADSAFE>::Reset() {
std::lock_guard<mutex_type> lock(component_lock_);
if (PREDICT_FALSE(arena_.size() > 1)) {
unique_ptr<Component> last = std::move(arena_.back());
arena_.clear();
arena_.emplace_back(std::move(last));
ReleaseStoreCurrent(arena_[0].get());
}
arena_.back()->Reset();
arena_footprint_ = arena_.back()->size();
#ifndef NDEBUG
// In debug mode release the last component too for (hopefully) better
// detection of memory-related bugs (invalid shallow copies, etc.).
size_t last_size = arena_.back()->size();
arena_.clear();
AddComponent(CHECK_NOTNULL(NewComponent(last_size, 0)));
arena_footprint_ = 0;
#endif
}
template <bool THREADSAFE>
size_t ArenaBase<THREADSAFE>::memory_footprint() const {
std::lock_guard<mutex_type> lock(component_lock_);
return arena_footprint_;
}
// Explicit instantiation.
template class ArenaBase<true>;
template class ArenaBase<false>;
} // namespace kudu