/* * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. * * Licensed 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. */ using System; using System.Buffers; using System.Collections.Generic; using System.Linq; using Apache.Arrow.Memory; namespace QuantConnect.Python { public class PandasArrowMemoryAllocator : NativeMemoryAllocator, IDisposable { private bool _disposed; private readonly List _free = new List(); private readonly List _used = new List(); public PandasArrowMemoryAllocator() : base() { } protected override IMemoryOwner AllocateInternal(int length, out int bytesAllocated) { PandasMemoryOwner owner; var memoryResizeIndexes = new List>(); for (var i = 0; i < _free.Count; i++) { var memory = _free[i]; if (length > memory.Original.Memory.Length) { memoryResizeIndexes.Add(new KeyValuePair(i, memory.Original.Memory.Length)); continue; } owner = memory; bytesAllocated = 0; _free.Remove(owner); _used.Add(owner); owner.Reset(); if (length != memory.Original.Memory.Length) { owner.Slice(0, length); } return owner; } if (memoryResizeIndexes.Count != 0) { // Get the smallest resizable instance, and reallocate a larger buffer. var resizeIndex = memoryResizeIndexes.OrderBy(x => x.Value).First(); var resizable = _free[resizeIndex.Key]; resizable.Resize(base.AllocateInternal(length, out bytesAllocated)); _used.Add(resizable); _free.RemoveAt(resizeIndex.Key); return resizable; } // New allocation, should only be called a few times when we start using the allocator owner = new PandasMemoryOwner(base.AllocateInternal(length, out bytesAllocated)); _used.Add(owner); return owner; } /// /// Frees the underlying memory buffers so that they can be re-used /// public void Free() { foreach (var used in _used) { _free.Add(used); } _used.Clear(); } private class PandasMemoryOwner : IMemoryOwner { private bool _disposed; /// /// Original memory owner containing the full-length byte-array /// we initially allocated. /// public IMemoryOwner Original { get; private set; } /// /// Slice of the original memory owner containing the contents of /// the buffer Arrow will use. We slice the original memory so /// that Arrow doesn't panic when it receives a slice with a length /// longer than it expects when serializing its internal buffer. /// public Memory Memory { get; private set; } public PandasMemoryOwner(IMemoryOwner memory) { Original = memory; Memory = Original.Memory; } /// /// Creates a slice of the original MemoryOwner and stores the result in /// /// Index start of the slice /// Length of the slice public void Slice(int start, int length) { Memory = Original.Memory.Slice(start, length); } /// /// Restores the slice to its initial value /// public void Reset() { Memory = null; Memory = Original.Memory; } /// /// Resizes the instance to the new memory size /// /// public void Resize(IMemoryOwner newMemory) { Original.Dispose(); Original = newMemory; Memory = null; Memory = Original.Memory; } public void Free() { Original.Dispose(); Memory = null; Original = null; } /// /// no-op dispose because we want to re-use the MemoryOwner instance after we dispose of a RecordBatch. /// To dispose of the resources this class owns, use /// public void Dispose() { } } public void Dispose() { if (_disposed) { throw new ObjectDisposedException("PandasArrowMemoryAllocator has already been disposed"); } foreach (var free in _free) { free.Free(); } foreach (var used in _used) { used.Free(); } _free.Clear(); _used.Clear(); _disposed = true; } } }