[डॉटनेटबुक] स्पैन, मेमोरी और रीडऑनलीमोरी

इस लेख के साथ, मैं लेखों की एक श्रृंखला प्रकाशित करना जारी रखता हूं, जिसके परिणामस्वरूप .NET सीएलआर और सामान्य रूप से .NET के काम पर एक किताब होगी। लिंक के लिए - बिल्ली में आपका स्वागत है।


मेमोरी <T> और ReadOnlyMemory <T>


Memory<T> और Span<T> बीच दो दृश्य अंतर हैं। पहला यह है कि Memory<T> में हेडर में ref बाधा नहीं होती है। यही है, दूसरे शब्दों में, Memory<T> का न केवल स्टैक पर होने का अधिकार है, बल्कि स्थानीय चर या विधि या इसके रिटर्न मान का एक पैरामीटर होने के नाते, लेकिन यह भी ढेर पर, स्मृति में कुछ डेटा का संदर्भ देता है। हालाँकि, यह छोटा अंतर Span<T> की तुलना में Memory<T> के व्यवहार और क्षमताओं में भारी अंतर करता है। Span<T> विपरीत, जो कुछ तरीकों के लिए एक निश्चित डेटा बफर का उपयोग करने का एक साधन है , Memory<T> बफर के बारे में जानकारी संग्रहीत करने के लिए डिज़ाइन किया गया है, और इसके साथ काम करने के लिए नहीं।


टिप्पणी


हैबे पर प्रकाशित अध्याय अद्यतन नहीं है और, शायद, पहले से ही थोड़ा पुराना है। और इसलिए, कृपया हाल के पाठ के लिए मूल पर जाएं:



यहाँ से एपीआई में अंतर आता है:


  • Memory<T> में डेटा एक्सेस के तरीके शामिल नहीं हैं जो इसे प्रबंधित करता है। इसके बजाय, इसमें Span प्रॉपर्टी और Slice विधि है, जो वर्कहॉर्स को लौटाती है - Span प्रकार का एक उदाहरण।
  • Memory<T> इसके अतिरिक्त Pin() विधि शामिल है, जिसे स्क्रिप्ट के लिए डिज़ाइन किया गया है जब संग्रहीत बफर को unsafe कोड में पारित किया जाना चाहिए। जब यह उन मामलों के लिए कहा जाता है जब .NET में मेमोरी आवंटित की गई थी, तो बफर को पिन किया जाएगा और जब जीसी ट्रिगर हो जाएगा, तब नहीं MemoryHandle , MemoryHandle संरचना के एक उदाहरण के लिए उपयोगकर्ता को वापस MemoryHandle , जो एक GCHandle जीवन GCHandle की अवधारणा को GCHandle करता GCHandle जिसने मेमोरी में बफर को ठीक किया है:

 public unsafe struct MemoryHandle : IDisposable { private void* _pointer; private GCHandle _handle; private IPinnable _pinnable; /// <summary> ///  MemoryHandle    /// </summary> public MemoryHandle(void* pointer, GCHandle handle = default, IPinnable pinnable = default) { _pointer = pointer; _handle = handle; _pinnable = pinnable; } /// <summary> ///     ,   ,       /// </summary> [CLSCompliant(false)] public void* Pointer => _pointer; /// <summary> ///  _handle  _pinnable,      /// </summary> public void Dispose() { if (_handle.IsAllocated) { _handle.Free(); } if (_pinnable != null) { _pinnable.Unpin(); _pinnable = null; } _pointer = null; } } 

हालांकि, के साथ शुरू करने के लिए, मैं कक्षाओं के पूरे सेट से परिचित होने का प्रस्ताव करता हूं। और उनमें से पहले के रूप में, Memory<T> की संरचना पर एक नज़र डालें Memory<T> (सभी प्रकार के सदस्यों को नहीं दिखाया गया है, लेकिन वे जो सबसे महत्वपूर्ण लगते हैं):


  public readonly struct Memory<T> { private readonly object _object; private readonly int _index, _length; public Memory(T[] array) { ... } public Memory(T[] array, int start, int length) { ... } internal Memory(MemoryManager<T> manager, int length) { ... } internal Memory(MemoryManager<T> manager, int start, int length) { ... } public int Length => _length & RemoveFlagsBitMask; public bool IsEmpty => (_length & RemoveFlagsBitMask) == 0; public Memory<T> Slice(int start, int length); public void CopyTo(Memory<T> destination) => Span.CopyTo(destination.Span); public bool TryCopyTo(Memory<T> destination) => Span.TryCopyTo(destination.Span); } 

संरचना क्षेत्रों को निर्दिष्ट करने के अलावा, मैंने अतिरिक्त रूप से इंगित करने का निर्णय लिया कि दो और internal प्रकार के निर्माता हैं जो अभी तक एक अन्य इकाई के आधार पर काम करते हैं - MemoryManager , जिस पर थोड़ा आगे चर्चा की जाएगी और यह कुछ ऐसा नहीं है जिसके बारे में आपने अभी कुछ बताया हो सोचा: शास्त्रीय अर्थ में एक स्मृति प्रबंधक। हालांकि, Span तरह, Memory में उस ऑब्जेक्ट का संदर्भ भी होता है जिसे नेविगेट किया जाएगा, साथ ही आंतरिक बफर की ऑफसेट और आकार भी। इसके अलावा, यह ध्यान देने योग्य है कि Memory को new ऑपरेटर के साथ केवल सरणी प्लस एक्सटेंशन विधियों के आधार पर बनाया जा सकता है - स्ट्रिंग, सरणी और सरणी के आधार पर। यानी मैन्युअल रूप से अप्रबंधित मेमोरी के आधार पर इसका निर्माण निहित नहीं है। हालाँकि, जैसा कि हम देखते हैं, MemoryManager पर आधारित इस संरचना को बनाने के लिए कुछ आंतरिक विधि है:


फ़ाइल MemoryManager.cs


 public abstract class MemoryManager<T> : IMemoryOwner<T>, IPinnable { public abstract MemoryHandle Pin(int elementIndex = 0); public abstract void Unpin(); public virtual Memory<T> Memory => new Memory<T>(this, GetSpan().Length); public abstract Span<T> GetSpan(); protected Memory<T> CreateMemory(int length) => new Memory<T>(this, length); protected Memory<T> CreateMemory(int start, int length) => new Memory<T>(this, start, length); void IDisposable.Dispose() protected abstract void Dispose(bool disposing); } 

मैं खुद को CLR कमांड में पेश की गई शब्दावली के साथ थोड़ा बहस करने की अनुमति दूंगा, जिसका नाम MemoryManager नाम से टाइप है। जब मैंने उसे देखा, तो मैंने पहली बार फैसला किया कि यह एक मेमोरी मैनेजमेंट जैसा कुछ होगा, लेकिन मैनुअल, LOH / SOH के अलावा। लेकिन वास्तविकता देखकर वह बहुत निराश हुआ। शायद आपको इसे इंटरफ़ेस के साथ सादृश्य द्वारा कॉल करना चाहिए: मेमोरीऑनर।

जो स्मृति के एक टुकड़े के मालिक की अवधारणा को कूटबद्ध करता है। दूसरे शब्दों में, यदि Span Memory साथ काम करने का एक साधन है, Memory किसी विशेष साइट के बारे में जानकारी संग्रहीत करने का एक साधन है, तो MemoryManager अपने जीवन, उसके मालिक को नियंत्रित करने का एक साधन है। उदाहरण के लिए, आप NativeMemoryManager<T> प्रकार ले सकते हैं, जो कि परीक्षणों के लिए लिखा गया है, लेकिन "स्वामित्व" की अवधारणा का सार खराब नहीं दर्शाता है:


NativeMemoryManager.cs फ़ाइल


 internal sealed class NativeMemoryManager : MemoryManager<byte> { private readonly int _length; private IntPtr _ptr; private int _retainedCount; private bool _disposed; public NativeMemoryManager(int length) { _length = length; _ptr = Marshal.AllocHGlobal(length); } public override void Pin() { ... } public override void Unpin() { lock (this) { if (_retainedCount > 0) { _retainedCount--; if (_retainedCount == 0) { if (_disposed) { Marshal.FreeHGlobal(_ptr); _ptr = IntPtr.Zero; } } } } } //   } 

यही है, दूसरे शब्दों में, वर्ग Pin() विधि से नेस्टेड कॉल की संभावना प्रदान करता है, जिससे unsafe दुनिया से परिणामी लिंक की गिनती होती है।


Memory से संबंधित एक अन्य इकाई MemoryPool , जो Memory MemoryPool (और वास्तव में, IMemoryOwner ) के उदाहरणों की पूलिंग प्रदान करती है:


फ़ाइल MemoryPool.cs


 public abstract class MemoryPool<T> : IDisposable { public static MemoryPool<T> Shared => s_shared; public abstract IMemoryOwner<T> Rent(int minBufferSize = -1); public void Dispose() { ... } } 

जिसे अस्थायी उपयोग के लिए आवश्यक आकार के बफर जारी करने के लिए डिज़ाइन किया गया है। ऐसे उदाहरण हैं जो IMemoryOwner<T> लागू करते हैं IMemoryOwner<T> इंटरफ़ेस में एक IMemoryOwner<T> Dispose() विधि है जो पट्टे पर दिए गए सरणी को सरणी पूल में वापस लौटाता है। और डिफ़ॉल्ट रूप से, आप साझा बफर पूल का उपयोग कर सकते हैं, जो कि ArrayMemoryPool के आधार पर बनाया गया है:


फ़ाइल ArrayMemoryPool.cs


 internal sealed partial class ArrayMemoryPool<T> : MemoryPool<T> { private const int MaximumBufferSize = int.MaxValue; public sealed override int MaxBufferSize => MaximumBufferSize; public sealed override IMemoryOwner<T> Rent(int minimumBufferSize = -1) { if (minimumBufferSize == -1) minimumBufferSize = 1 + (4095 / Unsafe.SizeOf<T>()); else if (((uint)minimumBufferSize) > MaximumBufferSize) ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.minimumBufferSize); return new ArrayMemoryPoolBuffer(minimumBufferSize); } protected sealed override void Dispose(bool disposing) { } } 

और उसने जो देखा उसके आधार पर, दुनिया की निम्नलिखित तस्वीर सामने आती है:


  • यदि आप या तो डेटा ( ReadOnlySpan ) या लेखन ( Span ) पढ़ रहे हैं, तो Span डेटा प्रकार का उपयोग विधि मापदंडों में किया जाना चाहिए। लेकिन भविष्य के उपयोग के लिए इसे कक्षा क्षेत्र में संग्रहीत करने का कार्य नहीं है
  • यदि आपको क्लास फ़ील्ड से डेटा बफ़र के लिए एक लिंक स्टोर करने की आवश्यकता है, तो आपको Memory<T> या ReadOnlyMemory<T> उपयोग करना होगा - उद्देश्य के आधार पर
  • MemoryManager<T> डेटा बफ़र का स्वामी है (आप इसका उपयोग नहीं कर सकते हैं: यदि आवश्यक हो)। यह आवश्यक है जब, उदाहरण के लिए, Pin() को कॉल की गणना करने की आवश्यकता होती है। या जब आपको मेमोरी खाली करने के तरीके के बारे में ज्ञान होना चाहिए
  • यदि Memory एक अप्रबंधित स्मृति क्षेत्र के आसपास बनाई गई है, तो Pin() कुछ भी नहीं Pin() । हालांकि, यह विभिन्न प्रकार के बफ़र्स के साथ काम को एकीकृत करता है: प्रबंधित के मामले में और अप्रबंधित कोड के मामले में, इंटरैक्शन इंटरफ़ेस समान होगा
  • प्रत्येक प्रकार के सार्वजनिक निर्माता हैं। इसका मतलब है कि आप दोनों Span सीधे उपयोग कर सकते हैं और Memory से इसकी एक प्रति प्राप्त कर सकते हैं। आप स्वयं Memory को अलग से बना सकते हैं या इसके लिए IMemoryOwner प्रकार की व्यवस्था कर सकते हैं जो Memory उस हिस्से का मालिक होगा जिसे Memory संदर्भित करेगा। मेमोरी MemoryManager आधार पर एक विशेष मामला किसी भी प्रकार का हो सकता है: स्मृति के टुकड़े का कुछ स्थानीय स्वामित्व (उदाहरण के लिए, unsafe दुनिया से संदर्भ की गिनती के साथ)। यदि एक ही समय में आपको ऐसे बफ़र्स खींचने की ज़रूरत है (लगभग बराबर आकार के बफ़र्स के लगातार ट्रैफ़िक की अपेक्षा करें), तो आप MemoryPool प्रकार का उपयोग कर सकते हैं।
  • यदि यह निहित है कि आपको unsafe कोड के साथ काम करने की आवश्यकता है, तो वहां एक निश्चित डेटा बफर पास करके, आपको Memory प्रकार का उपयोग करना चाहिए: इसमें एक Pin विधि है जो .NET हीप में बफर फिक्सिंग को स्वचालित करता है, अगर कोई वहां बनाया गया था।
  • यदि आपके पास कुछ बफर ट्रैफ़िक है (उदाहरण के लिए, आप पार्सिंग प्रोग्राम टेक्स्ट या कुछ डीएसएल) की समस्या को हल करते हैं, तो यह MemoryPool प्रकार का उपयोग करने के लायक है, जिसे बहुत ही सही तरीके से व्यवस्थित किया जा सकता है, पूल से उपयुक्त आकार के MemoryPool का MemoryPool किया जाता है (उदाहरण के लिए, यदि कोई उपयुक्त हो तो थोड़ा बड़ा) लेकिन pruning originalMemory.Slice(requiredSize) ताकि पूल को खंडित न किया जाए।

पूरी किताब का लिंक



Source: https://habr.com/ru/post/hi420051/


All Articles