// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. using Microsoft.Research.SEAL.Tools; using System; using System.Runtime.InteropServices; namespace Microsoft.Research.SEAL { /// /// Manages a shared pointer to a memory pool. Microsoft SEAL uses memory pools for /// improved performance due to the large number of memory allocations needed /// by the homomorphic encryption operations, and the underlying polynomial /// arithmetic. The library automatically creates a shared global memory pool /// that is used for all dynamic allocations by default, and the user can /// optionally create any number of custom memory pools to be used instead. /// /// /// /// Uses in Multi-Threaded Applications /// Sometimes the user might want to use specific memory pools for dynamic /// allocations in certain functions. For example, in heavily multi-threaded /// applications allocating concurrently from a shared memory pool might lead /// to significant performance issues due to thread contention. For these cases /// Microsoft SEAL provides overloads of the functions that take a MemoryPoolHandle as an /// additional argument, and uses the associated memory pool for all dynamic /// allocations inside the function. Whenever this functions is called, the /// user can then simply pass a thread-local MemoryPoolHandle to be used. /// /// /// Thread-Unsafe Memory Pools /// While memory pools are by default thread-safe, in some cases it suffices /// to have a memory pool be thread-unsafe. To get a little extra performance, /// the user can optionally create such thread-unsafe memory pools and use them /// just as they would use thread-safe memory pools. /// /// /// Initialized and Uninitialized Handles /// A MemoryPoolHandle has to be set to point either to the global memory pool, /// or to a new memory pool. If this is not done, the MemoryPoolHandle is /// said to be uninitialized, and cannot be used. Initialization simple means /// assigning MemoryPoolHandle::Global() or MemoryPoolHandle::New() to it. /// /// /// Managing Lifetime /// Internally, the MemoryPoolHandle wraps a C++ shared pointer pointing to /// a memory pool class. Thus, as long as a MemoryPoolHandle pointing to /// a particular memory pool exists, the pool stays alive. Classes such as /// Evaluator and Ciphertext store their own local copies of a MemoryPoolHandle /// to guarantee that the pool stays alive as long as the managing object /// itself stays alive. The global memory pool is implemented as a global /// C++ std::shared_ptr to a memory pool class, and is thus expected to stay /// alive for the entire duration of the program execution. Note that it can /// be problematic to create other global objects that use the memory pool /// e.g. in their constructor, as one would have to ensure the initialization /// order of these global variables to be correct (i.e. global memory pool /// first). /// /// public class MemoryPoolHandle : NativeObject { /// /// Creates a new uninitialized MemoryPoolHandle. /// public MemoryPoolHandle() { NativeMethods.MemoryPoolHandle_Create(out IntPtr handlePtr); NativePtr = handlePtr; } /// /// Creates a copy of a given MemoryPoolHandle. As a result, the created /// MemoryPoolHandle will point to the same underlying memory pool as the /// copied instance. /// /// The MemoryPoolHandle to copy from. /// if copy is null. public MemoryPoolHandle(MemoryPoolHandle copy) { if (null == copy) throw new ArgumentNullException(nameof(copy)); NativeMethods.MemoryPoolHandle_Create(copy.NativePtr, out IntPtr handlePtr); NativePtr = handlePtr; } /// /// Create a MemoryPoolHandle through a native object pointer. /// /// Pointer to native MemoryPoolHandle /// Whether this instance owns the native pointer internal MemoryPoolHandle(IntPtr ptr, bool owned = true) : base(ptr, owned) { } /// /// Overwrites the MemoryPoolHandle instance with the specified instance. As /// a result, the current MemoryPoolHandle will point to the same underlying /// memory pool as the assigned instance. /// /// The MemoryPoolHandle instance to assign to the current /// instance /// if assign is null. public void Set(MemoryPoolHandle assign) { if (null == assign) throw new ArgumentNullException(nameof(assign)); NativeMethods.MemoryPoolHandle_Set(NativePtr, assign.NativePtr); } /// /// Returns a MemoryPoolHandle pointing to the global memory pool. /// public static MemoryPoolHandle Global() { NativeMethods.MemoryPoolHandle_Global(out IntPtr handlePtr); MemoryPoolHandle handle = new MemoryPoolHandle(handlePtr); return handle; } /// /// Returns a MemoryPoolHandle pointing to the thread-local memory pool. /// public static MemoryPoolHandle ThreadLocal() { NativeMethods.MemoryPoolHandle_ThreadLocal(out IntPtr handlePtr); MemoryPoolHandle handle = new MemoryPoolHandle(handlePtr); return handle; } /// /// Returns a MemoryPoolHandle pointing to a new thread-safe memory pool. /// /// Indicates whether the memory pool data /// should be cleared when destroyed.This can be important when memory pools /// are used to store private data. public static MemoryPoolHandle New(bool clearOnDestruction = false) { NativeMethods.MemoryPoolHandle_New(clearOnDestruction, out IntPtr handlePtr); MemoryPoolHandle handle = new MemoryPoolHandle(handlePtr); return handle; } /// /// Returns the number of different allocation sizes. /// /// /// This function returns the number of different allocation sizes the memory /// pool pointed to by the current MemoryPoolHandle has made. For example, /// if the memory pool has only allocated two allocations of sizes 128 KB, /// this function returns 1. If it has instead allocated one allocation of /// size 64 KB and one of 128 KB, this functions returns 2. /// public ulong PoolCount { get { NativeMethods.MemoryPoolHandle_PoolCount(NativePtr, out ulong count); return count; } } /// /// Returns the size of allocated memory. /// /// /// This functions returns the total amount of memory (in bytes) allocated /// by the memory pool pointed to by the current MemoryPoolHandle. /// public ulong AllocByteCount { get { NativeMethods.MemoryPoolHandle_AllocByteCount(NativePtr, out ulong count); return count; } } /// /// Returns the number of MemoryPoolHandle objects sharing this memory pool. /// public long UseCount { get { NativeMethods.MemoryPoolHandle_UseCount(NativePtr, out long count); return count; } } /// /// Returns whether the MemoryPoolHandle is initialized. /// public bool IsInitialized { get { NativeMethods.MemoryPoolHandle_IsInitialized(NativePtr, out bool result); return result; } } /// /// Compares MemoryPoolHandles. This function returns whether the current /// MemoryPoolHandle points to the same memory pool as a given MemoryPoolHandle. /// /// Object to compare to. public override bool Equals(object obj) { MemoryPoolHandle other = obj as MemoryPoolHandle; if (null == other) return false; NativeMethods.MemoryPoolHandle_Equals(NativePtr, other.NativePtr, out bool result); return result; } /// /// Get hash code for this MemoryPoolHandle /// public override int GetHashCode() { return base.GetHashCode(); } /// /// Destroy native object. /// protected override void DestroyNativeObject() { NativeMethods.MemoryPoolHandle_Destroy(NativePtr); } } }