| // |
| // Copyright 2022 The ANGLE Project Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| // |
| // RingBufferAllocator.cpp: |
| // Implements classes used for ring buffer allocators. |
| // |
| |
| #include "common/RingBufferAllocator.h" |
| |
| namespace angle |
| { |
| |
| // RingBufferAllocator implementation. |
| RingBufferAllocator::RingBufferAllocator(RingBufferAllocator &&other) |
| { |
| *this = std::move(other); |
| } |
| |
| RingBufferAllocator &RingBufferAllocator::operator=(RingBufferAllocator &&other) |
| { |
| mOldBuffers = std::move(other.mOldBuffers); |
| mBuffer = std::move(other.mBuffer); |
| mDataBegin = other.mDataBegin; |
| mDataEnd = other.mDataEnd; |
| mFragmentEnd = other.mFragmentEnd; |
| mFragmentEndR = other.mFragmentEndR; |
| mFragmentReserve = other.mFragmentReserve; |
| |
| mMinCapacity = other.mMinCapacity; |
| mCurrentCapacity = other.mCurrentCapacity; |
| mAllocationMargin = other.mAllocationMargin; |
| mDecaySpeedFactor = other.mDecaySpeedFactor; |
| |
| ASSERT(other.mOldBuffers.size() == 0); |
| ASSERT(other.mBuffer.isEmpty()); |
| other.mBuffer.resetId(); |
| other.mDataBegin = nullptr; |
| other.mDataEnd = nullptr; |
| other.mFragmentEnd = nullptr; |
| other.mFragmentEndR = nullptr; |
| other.mFragmentReserve = 0; |
| |
| other.mMinCapacity = 0; |
| other.mCurrentCapacity = 0; |
| other.mAllocationMargin = 0; |
| other.mDecaySpeedFactor = 0; |
| |
| return *this; |
| } |
| |
| void RingBufferAllocator::reset() |
| { |
| mListener = nullptr; |
| mFragmentReserve = 0; |
| mDecaySpeedFactor = kDefaultDecaySpeedFactor; |
| mMinCapacity = kMinRingBufferAllocationCapacity; |
| resize(mMinCapacity); |
| mOldBuffers.clear(); |
| } |
| |
| void RingBufferAllocator::setListener(RingBufferAllocateListener *listener) |
| { |
| ASSERT(!mListener || !listener); |
| mListener = listener; |
| } |
| |
| void RingBufferAllocator::setDecaySpeedFactor(uint32_t decaySpeedFactor) |
| { |
| ASSERT(valid()); |
| mDecaySpeedFactor = std::max(decaySpeedFactor, 1u); |
| } |
| |
| RingBufferAllocatorCheckPoint RingBufferAllocator::getReleaseCheckPoint() const |
| { |
| ASSERT(valid()); |
| RingBufferAllocatorCheckPoint result; |
| result.mBufferId = mBuffer.getId(); |
| result.mReleasePtr = mDataEnd; |
| return result; |
| } |
| |
| void RingBufferAllocator::release(const RingBufferAllocatorCheckPoint &checkPoint) |
| { |
| ASSERT(valid()); |
| ASSERT(checkPoint.valid()); |
| |
| if (mOldBuffers.size() > 0) |
| { |
| // mOldBuffers are sorted by id |
| int removeCount = 0; |
| for (uint32_t i = 0; |
| (i < mOldBuffers.size()) && (mOldBuffers[i].getId() < checkPoint.mBufferId); ++i) |
| { |
| ++removeCount; |
| } |
| mOldBuffers.erase(mOldBuffers.begin(), mOldBuffers.begin() + removeCount); |
| } |
| |
| if (checkPoint.mBufferId == mBuffer.getId()) |
| { |
| const uint32_t allocatedBefore = getNumAllocatedInBuffer(); |
| |
| release(checkPoint.mReleasePtr); |
| |
| if (allocatedBefore >= mAllocationMargin) |
| { |
| if ((mCurrentCapacity > mMinCapacity) && (allocatedBefore * 6 <= mCurrentCapacity)) |
| { |
| resize(std::max(allocatedBefore * 3, mMinCapacity)); |
| } |
| else |
| { |
| mAllocationMargin = mCurrentCapacity; |
| } |
| } |
| else |
| { |
| const uint64_t numReleased = (allocatedBefore - getNumAllocatedInBuffer()); |
| const uint64_t distanceToMargin = (mAllocationMargin - allocatedBefore); |
| mAllocationMargin -= |
| std::max(static_cast<uint32_t>(numReleased * distanceToMargin / mAllocationMargin / |
| mDecaySpeedFactor), |
| 1u); |
| } |
| } |
| } |
| |
| void RingBufferAllocator::setFragmentReserve(uint32_t reserve) |
| { |
| ASSERT(valid()); |
| mFragmentReserve = reserve; |
| mFragmentEndR = mBuffer.decClamped(mFragmentEnd, mFragmentReserve); |
| } |
| |
| uint8_t *RingBufferAllocator::allocateInNewFragment(uint32_t size) |
| { |
| if (mListener) |
| { |
| mListener->onRingBufferFragmentEnd(); |
| } |
| |
| uint8_t *newFragmentBegin = nullptr; |
| if (mFragmentEnd != mDataBegin) |
| { |
| newFragmentBegin = mBuffer.data(); |
| uint8_t *const newFragmentEnd = mDataBegin; |
| uint8_t *const newFragmentEndR = mBuffer.decClamped(newFragmentEnd, mFragmentReserve); |
| |
| // It should wrap around only if it can allocate. |
| if (newFragmentEndR - newFragmentBegin >= static_cast<ptrdiff_t>(size)) |
| { |
| mDataEnd = newFragmentBegin; |
| mFragmentEnd = newFragmentEnd; |
| mFragmentEndR = newFragmentEndR; |
| |
| if (mListener) |
| { |
| mListener->onRingBufferNewFragment(); |
| } |
| |
| mDataEnd = newFragmentBegin + size; |
| return newFragmentBegin; |
| } |
| } |
| |
| resize(std::max(mCurrentCapacity + mCurrentCapacity / 2, size + mFragmentReserve)); |
| |
| if (mListener) |
| { |
| mListener->onRingBufferNewFragment(); |
| } |
| |
| ASSERT(mFragmentEndR - mDataEnd >= static_cast<ptrdiff_t>(size)); |
| newFragmentBegin = mDataEnd; |
| mDataEnd = newFragmentBegin + size; |
| return newFragmentBegin; |
| } |
| |
| void RingBufferAllocator::resize(uint32_t newCapacity) |
| { |
| ASSERT(newCapacity >= mMinCapacity); |
| |
| if (mBuffer.getId() != 0) |
| { |
| mOldBuffers.emplace_back(std::move(mBuffer)); |
| } |
| |
| mCurrentCapacity = newCapacity; |
| mBuffer.incrementId(); |
| mBuffer.resize(mCurrentCapacity); |
| resetPointers(); |
| |
| mAllocationMargin = mCurrentCapacity; |
| } |
| |
| void RingBufferAllocator::release(uint8_t *releasePtr) |
| { |
| if (releasePtr == mDataEnd) |
| { |
| // Ensures "mDataEnd == mBuffer.data()" with 0 allocations. |
| resetPointers(); |
| return; |
| } |
| |
| if (mDataBegin == mFragmentEnd) |
| { |
| ASSERT((releasePtr >= mBuffer.data() && releasePtr < mDataEnd) || |
| (releasePtr >= mDataBegin && releasePtr <= mBuffer.data() + mCurrentCapacity)); |
| if (releasePtr < mDataBegin) |
| { |
| mFragmentEnd = mBuffer.data() + mCurrentCapacity; |
| } |
| else |
| { |
| mFragmentEnd = releasePtr; |
| } |
| mFragmentEndR = mBuffer.decClamped(mFragmentEnd, mFragmentReserve); |
| } |
| else |
| { |
| ASSERT(releasePtr >= mDataBegin && releasePtr < mDataEnd); |
| } |
| mDataBegin = releasePtr; |
| } |
| |
| uint32_t RingBufferAllocator::getNumAllocatedInBuffer() const |
| { |
| // 2 free fragments: [mBuffer.begin, mDataBegin) [mDataEnd, mBuffer.end); |
| // 1 used fragment: [DataBegin, DataEnd) |
| if (mFragmentEnd != mDataBegin) |
| { |
| ASSERT(mDataEnd >= mDataBegin); |
| return static_cast<uint32_t>(mDataEnd - mDataBegin); |
| } |
| |
| // 1 free fragment: [mDataEnd, mDataBegin) |
| // 2 used fragments: [mBuffer.begin, mDataEnd) [mDataBegin, mBuffer.end) |
| ASSERT(mDataBegin >= mDataEnd); |
| return (mCurrentCapacity - static_cast<uint32_t>(mDataBegin - mDataEnd)); |
| } |
| |
| void RingBufferAllocator::resetPointers() |
| { |
| mDataBegin = mBuffer.data(); |
| mDataEnd = mDataBegin; |
| mFragmentEnd = mDataEnd + mCurrentCapacity; |
| mFragmentEndR = mBuffer.decClamped(mFragmentEnd, mFragmentReserve); |
| } |
| |
| // SharedRingBufferAllocator implementation. |
| SharedRingBufferAllocator::SharedRingBufferAllocator() |
| { |
| mAllocator.reset(); |
| } |
| |
| SharedRingBufferAllocator::~SharedRingBufferAllocator() |
| { |
| #if defined(ANGLE_ENABLE_ASSERTS) |
| ASSERT(!mSharedCP || mSharedCP->mRefCount == 1); |
| #endif |
| SafeDelete(mSharedCP); |
| } |
| |
| SharedRingBufferAllocatorCheckPoint *SharedRingBufferAllocator::acquireSharedCP() |
| { |
| if (!mSharedCP) |
| { |
| mSharedCP = new SharedRingBufferAllocatorCheckPoint(); |
| } |
| #if defined(ANGLE_ENABLE_ASSERTS) |
| ASSERT(++mSharedCP->mRefCount > 1); |
| // Must always be 1 ref before |
| #endif |
| return mSharedCP; |
| } |
| |
| void SharedRingBufferAllocator::releaseToSharedCP() |
| { |
| ASSERT(mSharedCP); |
| const RingBufferAllocatorCheckPoint releaseCP = mSharedCP->pop(); |
| if (releaseCP.valid()) |
| { |
| mAllocator.release(releaseCP); |
| } |
| } |
| |
| void SharedRingBufferAllocatorCheckPoint::releaseAndUpdate(RingBufferAllocatorCheckPoint *newValue) |
| { |
| ASSERT(newValue && newValue->valid()); |
| #if defined(ANGLE_ENABLE_ASSERTS) |
| ASSERT(--mRefCount >= 1); |
| // Must always remain 1 ref |
| #endif |
| { |
| std::lock_guard<std::mutex> lock(mMutex); |
| mValue = *newValue; |
| } |
| newValue->reset(); |
| } |
| |
| RingBufferAllocatorCheckPoint SharedRingBufferAllocatorCheckPoint::pop() |
| { |
| std::lock_guard<std::mutex> lock(mMutex); |
| RingBufferAllocatorCheckPoint value = mValue; |
| mValue.reset(); |
| return value; |
| } |
| |
| } // namespace angle |