| // 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 |