| // 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/memory.h" |
| |
| #ifdef __aarch64__ |
| #define _mm_free(p) free(p) |
| #define _mm_malloc(a, b) malloc(a) |
| #else |
| #include <mm_malloc.h> |
| #endif //__aarch64__ |
| |
| #include <algorithm> |
| #include <cstdlib> |
| #include <cstring> |
| |
| #include <gflags/gflags.h> |
| |
| #include "kudu/util/alignment.h" |
| #include "kudu/util/flag_tags.h" |
| #include "kudu/util/mem_tracker.h" |
| #include "kudu/util/memory/overwrite.h" |
| |
| using std::copy; |
| using std::min; |
| |
| // TODO(onufry) - test whether the code still tests OK if we set this to true, |
| // or remove this code and add a test that Google allocator does not change it's |
| // contract - 16-aligned in -c opt and %16 == 8 in debug. |
| DEFINE_bool(allocator_aligned_mode, false, |
| "Use 16-byte alignment instead of 8-byte, " |
| "unless explicitly specified otherwise - to boost SIMD"); |
| TAG_FLAG(allocator_aligned_mode, hidden); |
| |
| namespace kudu { |
| |
| namespace { |
| static char dummy_buffer[0] = {}; |
| } |
| |
| Buffer::~Buffer() { |
| #if !defined(NDEBUG) && !defined(ADDRESS_SANITIZER) |
| // "unrolling" the string "BAD" makes for a much more efficient |
| // OverwriteWithPattern call in debug mode, so we can keep this |
| // useful bit of code without tests going slower! |
| // |
| // In ASAN mode, we don't bother with this, because when we free the memory, ASAN will |
| // prevent us from accessing it anyway. |
| OverwriteWithPattern(reinterpret_cast<char*>(data_), size_, |
| "BADBADBADBADBADBADBADBADBADBADBAD" |
| "BADBADBADBADBADBADBADBADBADBADBAD" |
| "BADBADBADBADBADBADBADBADBADBADBAD"); |
| #endif |
| if (allocator_ != nullptr) allocator_->FreeInternal(this); |
| } |
| |
| void BufferAllocator::LogAllocation(size_t requested, |
| size_t minimal, |
| Buffer* buffer) { |
| if (buffer == nullptr) { |
| LOG(WARNING) << "Memory allocation failed. " |
| << "Number of bytes requested: " << requested |
| << ", minimal: " << minimal; |
| return; |
| } |
| if (buffer->size() < requested) { |
| LOG(WARNING) << "Memory allocation was shorter than requested. " |
| << "Number of bytes requested to allocate: " << requested |
| << ", minimal: " << minimal |
| << ", and actually allocated: " << buffer->size(); |
| } |
| } |
| |
| HeapBufferAllocator::HeapBufferAllocator() |
| : aligned_mode_(FLAGS_allocator_aligned_mode) { |
| } |
| |
| Buffer* HeapBufferAllocator::AllocateInternal( |
| const size_t requested, |
| const size_t minimal, |
| BufferAllocator* const originator) { |
| DCHECK_LE(minimal, requested); |
| void* data; |
| size_t attempted = requested; |
| while (true) { |
| data = (attempted == 0) ? &dummy_buffer[0] : Malloc(attempted); |
| if (data != nullptr) { |
| return CreateBuffer(data, attempted, originator); |
| } |
| if (attempted == minimal) return nullptr; |
| attempted = minimal + (attempted - minimal - 1) / 2; |
| } |
| } |
| |
| bool HeapBufferAllocator::ReallocateInternal( |
| const size_t requested, |
| const size_t minimal, |
| Buffer* const buffer, |
| BufferAllocator* const originator) { |
| DCHECK_LE(minimal, requested); |
| void* data; |
| size_t attempted = requested; |
| while (true) { |
| if (attempted == 0) { |
| if (buffer->size() > 0) free(buffer->data()); |
| data = &dummy_buffer[0]; |
| } else { |
| if (buffer->size() > 0) { |
| data = Realloc(buffer->data(), buffer->size(), attempted); |
| } else { |
| data = Malloc(attempted); |
| } |
| } |
| if (data != nullptr) { |
| UpdateBuffer(data, attempted, buffer); |
| return true; |
| } |
| if (attempted == minimal) return false; |
| attempted = minimal + (attempted - minimal - 1) / 2; |
| } |
| } |
| |
| void HeapBufferAllocator::FreeInternal(Buffer* buffer) { |
| if (buffer->size() > 0) free(buffer->data()); |
| } |
| |
| void* HeapBufferAllocator::Malloc(size_t size) { |
| if (aligned_mode_) { |
| void* data; |
| if (posix_memalign(&data, 16, KUDU_ALIGN_UP(size, 16))) { |
| return nullptr; |
| } |
| return data; |
| } else { |
| return malloc(size); |
| } |
| } |
| |
| void* HeapBufferAllocator::Realloc(void* previous_data, size_t previous_size, |
| size_t new_size) { |
| if (aligned_mode_) { |
| void* data = Malloc(new_size); |
| if (data) { |
| // NOTE(ptab): We should use realloc here to avoid memmory coping, |
| // but it doesn't work on memory allocated by posix_memalign(...). |
| // realloc reallocates the memory but doesn't preserve the content. |
| // TODO(ptab): reiterate after some time to check if it is fixed (tcmalloc ?) |
| memcpy(data, previous_data, min(previous_size, new_size)); |
| free(previous_data); |
| return data; |
| } else { |
| return nullptr; |
| } |
| } else { |
| return realloc(previous_data, new_size); |
| } |
| } |
| |
| Buffer* ClearingBufferAllocator::AllocateInternal(size_t requested, |
| size_t minimal, |
| BufferAllocator* originator) { |
| Buffer* buffer = DelegateAllocate(delegate_, requested, minimal, |
| originator); |
| if (buffer != nullptr) memset(buffer->data(), 0, buffer->size()); |
| return buffer; |
| } |
| |
| bool ClearingBufferAllocator::ReallocateInternal(size_t requested, |
| size_t minimal, |
| Buffer* buffer, |
| BufferAllocator* originator) { |
| size_t offset = (buffer != nullptr ? buffer->size() : 0); |
| bool success = DelegateReallocate(delegate_, requested, minimal, buffer, |
| originator); |
| if (success && buffer->size() > offset) { |
| memset(static_cast<char*>(buffer->data()) + offset, 0, |
| buffer->size() - offset); |
| } |
| return success; |
| } |
| |
| void ClearingBufferAllocator::FreeInternal(Buffer* buffer) { |
| DelegateFree(delegate_, buffer); |
| } |
| |
| Buffer* MediatingBufferAllocator::AllocateInternal( |
| const size_t requested, |
| const size_t minimal, |
| BufferAllocator* const originator) { |
| // Allow the mediator to trim the request. |
| size_t granted; |
| if (requested > 0) { |
| granted = mediator_->Allocate(requested, minimal); |
| if (granted < minimal) return nullptr; |
| } else { |
| granted = 0; |
| } |
| Buffer* buffer = DelegateAllocate(delegate_, granted, minimal, originator); |
| if (buffer == nullptr) { |
| mediator_->Free(granted); |
| } else if (buffer->size() < granted) { |
| mediator_->Free(granted - buffer->size()); |
| } |
| return buffer; |
| } |
| |
| bool MediatingBufferAllocator::ReallocateInternal( |
| const size_t requested, |
| const size_t minimal, |
| Buffer* const buffer, |
| BufferAllocator* const originator) { |
| // Allow the mediator to trim the request. Be conservative; assume that |
| // realloc may degenerate to malloc-memcpy-free. |
| size_t granted; |
| if (requested > 0) { |
| granted = mediator_->Allocate(requested, minimal); |
| if (granted < minimal) return false; |
| } else { |
| granted = 0; |
| } |
| size_t old_size = buffer->size(); |
| if (DelegateReallocate(delegate_, granted, minimal, buffer, originator)) { |
| mediator_->Free(granted - buffer->size() + old_size); |
| return true; |
| } else { |
| mediator_->Free(granted); |
| return false; |
| } |
| } |
| |
| void MediatingBufferAllocator::FreeInternal(Buffer* buffer) { |
| mediator_->Free(buffer->size()); |
| DelegateFree(delegate_, buffer); |
| } |
| |
| Buffer* MemoryStatisticsCollectingBufferAllocator::AllocateInternal( |
| const size_t requested, |
| const size_t minimal, |
| BufferAllocator* const originator) { |
| Buffer* buffer = DelegateAllocate(delegate_, requested, minimal, originator); |
| if (buffer != nullptr) { |
| memory_stats_collector_->AllocatedMemoryBytes(buffer->size()); |
| } else { |
| memory_stats_collector_->RefusedMemoryBytes(minimal); |
| } |
| return buffer; |
| } |
| |
| bool MemoryStatisticsCollectingBufferAllocator::ReallocateInternal( |
| const size_t requested, |
| const size_t minimal, |
| Buffer* const buffer, |
| BufferAllocator* const originator) { |
| const size_t old_size = buffer->size(); |
| bool outcome = DelegateReallocate(delegate_, requested, minimal, buffer, |
| originator); |
| if (buffer->size() > old_size) { |
| memory_stats_collector_->AllocatedMemoryBytes(buffer->size() - old_size); |
| } else if (buffer->size() < old_size) { |
| memory_stats_collector_->FreedMemoryBytes(old_size - buffer->size()); |
| } else if (!outcome && (minimal > buffer->size())) { |
| memory_stats_collector_->RefusedMemoryBytes(minimal - buffer->size()); |
| } |
| return outcome; |
| } |
| |
| void MemoryStatisticsCollectingBufferAllocator::FreeInternal(Buffer* buffer) { |
| DelegateFree(delegate_, buffer); |
| memory_stats_collector_->FreedMemoryBytes(buffer->size()); |
| } |
| |
| size_t MemoryTrackingBufferAllocator::Available() const { |
| return enforce_limit_ ? mem_tracker_->SpareCapacity() : std::numeric_limits<int64_t>::max(); |
| } |
| |
| bool MemoryTrackingBufferAllocator::TryConsume(int64_t bytes) { |
| // Calls TryConsume first, even if enforce_limit_ is false: this |
| // will cause mem_tracker_ to try to free up more memory by GCing. |
| if (!mem_tracker_->TryConsume(bytes)) { |
| if (enforce_limit_) { |
| return false; |
| } else { |
| // If enforce_limit_ is false, allocate memory anyway. |
| mem_tracker_->Consume(bytes); |
| } |
| } |
| return true; |
| } |
| |
| Buffer* MemoryTrackingBufferAllocator::AllocateInternal(size_t requested, |
| size_t minimal, |
| BufferAllocator* originator) { |
| if (TryConsume(requested)) { |
| Buffer* buffer = DelegateAllocate(delegate_, requested, requested, originator); |
| if (buffer == nullptr) { |
| mem_tracker_->Release(requested); |
| } else { |
| return buffer; |
| } |
| } |
| |
| if (TryConsume(minimal)) { |
| Buffer* buffer = DelegateAllocate(delegate_, minimal, minimal, originator); |
| if (buffer == nullptr) { |
| mem_tracker_->Release(minimal); |
| } |
| return buffer; |
| } |
| |
| return nullptr; |
| } |
| |
| |
| bool MemoryTrackingBufferAllocator::ReallocateInternal(size_t requested, |
| size_t minimal, |
| Buffer* buffer, |
| BufferAllocator* originator) { |
| LOG(FATAL) << "Not implemented"; |
| return false; |
| } |
| |
| void MemoryTrackingBufferAllocator::FreeInternal(Buffer* buffer) { |
| DelegateFree(delegate_, buffer); |
| mem_tracker_->Release(buffer->size()); |
| } |
| |
| } // namespace kudu |