قائمة إلحاق فقط عالية السرعة C# غير قابلة للتغيير. (التحديث: 23 سبتمبر 24؛ لا يُنصح باستخدامه. راجع الملاحظات في الأسفل لمعرفة الأساس المنطقي والبدائل.)
الدوت نت إضافة حزمة AppendOnly
أولاً، تواجه المجموعات غير القابلة للتغيير الموجودة وقتًا عصيبًا للغاية مما يسمح لك بإضافة وحذف وتحديثات تؤدي إلى إرجاع مجموعات جديدة. عليهم حرفيًا نسخ الأشياء، كثيرًا. إذا كنت تقوم بالإلحاق فقط، فإن LinkedList هو صديقك، ويمكن أن يبدو وكأنه "منسوخ" على الفور.
ثانيًا، المكتبات غير القابلة للتغيير الموجودة معقدة ويصعب استخدامها. إنها تتطلب دروسًا في البناء وفهمًا جيدًا للميكانيكا الحقيقية، وإلا فستواجه مشكلات خطيرة في الأداء.
أخيرًا: هذا الفصل آمن تمامًا للاستخدام في الحلقات الضيقة حقًا. كما هو الحال في، ضيق بقدر ما يمكنك الحصول عليه.
يستخدم AppendOnly القائمة المرتبطة داخليًا، ولا ينسخ عناصر المجموعة مطلقًا عند إنشاء نسخة جديدة "غير قابلة للتغيير". إنها ببساطة تُرجع فئة مجموعة جديدة تشترك في نفس القائمة المرتبطة الداخلية "القابلة للتغيير"، ولكنها لا تكشف عن أي طرق لتغييرها، بل تعرض فقط مُتداخلًا يتكرر عبر العناصر (n) الأولى في المجموعة.
لذلك، مع نمو المجموعة بشكل أكبر، لن تتمكن النسخ السابقة (الأقدم) من فئة المجموعة من رؤية (الكشف عبر العداد) الإدخالات الجديدة الملحقة بنهاية القائمة. بعد إضافة 1000 "معاملة" جديدة إلى مجموعة المعاملات الملحقة فقط، يكون لديك فعليًا (إذا احتفظت بالمراجع لكل مجموعة تم إنشاؤها حديثًا) 1000 مؤشر إلى مجموعات تحتوي كل منها على 1، 2، 3، 4 عناصر وما إلى ذلك، ولكن مع ما يقرب من الصفر الإضافي النفقات العامة للذاكرة.
إذا كنت تستخدم المكتبات الأخرى غير القابلة للتغيير مع المنشئين، فإن معظمهم سيمنحك ما لا يقل عن 500000 مثيل للكائنات.
قد أكون مخطئا، إنها نقطة البداية، اسمحوا لي أن أعرف إذا كنت تستخدم هذا. سأقوم بإنشاء المزيد من المجموعات على نفس المنوال.
var moves = new AppendOnlyList<Moves>();var new_moves = new AppendOnlyList<Moves>();Debug.Assert(moves.Length == 0);// أضف 10 حركات عشوائية for(int i=0; i<10 ; i++) new_moves = new_moves.Add(randomMove());// لا تزال الحركات الأصلية لا تحتوي على عناصر على الرغم من أن new_list قد أضافت عنصرًا إلى القائمة List.Debug.Assert(moves.Length == 0);// تحتوي القائمة الجديدة على 10 عناصر في مجموعتهاDebug.Assert(new_moves.Length == 10);// هذه القائمة آمنة للتكرار عدة مرات وهي آمنة للخيط// يُظهر الكود أدناه التكرار على القائمة بأكملها عشر مرات، وهو أمر لا تفعله عادةً إلا ضد // عدد لا يحصى إذا قمت بتخزينه مؤقتًا، أي إنشاء نسخة محلية.// أعلم أن هذا لا يبدو وكأنه يفعل الكثير، لكن هذا مهم حقًا. // أيضًا، من الآمن القيام بذلك بينما تكون سلاسل الرسائل الأخرى مشغولة بالإضافة إلى نفس المجموعة الأساسية، // وهو أمر يعد بمثابة "لا لا" هائل في عالم الخيوط. هنا، الأمر آمن تمامًا. for(int i = 0; i<10; i++){foreach(var move in new_moves.Items) wait DoSomethingFunkyAsync(move);}
في الكود أعلاه، تشترك new_moves
moves
الأصلية في نفس القائمة المرتبطة الأساسية. عند إنشاء نسخة جديدة من قائمة AppendOnlyList، لا يتم إجراء أي نسخ جديدة، ويتم الاحتفاظ فقط بنقطة إلى رأس القائمة ويتم الاحتفاظ بالطول.
هذا سريع للغاية، حيث لا يتم إنشاء أي نسخة، لذا فهو أسرع بشكل لا نهائي من أي مجموعة أخرى غير قابلة للتغيير تقوم بإنشاء نسخ.
ومن الآمن التكرار على المجموعة أثناء تعدادها من سلاسل رسائل أخرى! بوم! ... أو بشكل أكثر تحديدا "لا يوجد بوم!".
هذا هو الإصدار 0.1.3
مما يعني أنه غير جاهز للاستخدام الإنتاجي. إنه دليل على المفهوم، وأحتاج إلى كتابة بعض اختبارات الترابط لإثبات الادعاءات المذكورة أعلاه، ثم أطلب من زملائي مراجعتها وإخباري بذلك.
أريد أيضًا إجراء مقارنة للسرعة بين هذه المجموعات والمجموعات الأخرى بالإضافة إلى المقارنة جنبًا إلى جنب.
إذا أعجبتك هذه الحزمة، قم بإلقاء نظرة على التنفيذ الفعلي، فهي حرفيًا عبارة عن فئتين، AppendOnlyList
و LinkedListExtensions
، وهما صغيرتان.
اسمحوا لي أن أعرف ما هو رأيك؟
:د
اي استفسار تواصل معي على
Alan Hemmings, twitter : @snowcode LinkedIn : https://www.linkedin.com/in/goblinfactory www : https://goblinfactory.co.uk
...ولم أستخدمه كثيرًا، وحسنًا، نسيته. لقد تطورت الأمور (C#) كثيرًا منذ أن كتبت هذا، وعلى الرغم من وجود الكثير من التغييرات في C#، لم يقم أي منها بتبسيط الأمور كثيرًا. لسوء الحظ، أعتقد أن أفكاري أعلاه، حول متى يجب استخدام هذه الحزمة، في ضوء التغييرات اللغوية الجديدة، قد تكون هراء، وربما كانت... خاطئة تمامًا. من المؤكد أن الاختبار ضروري قبل القيام بأي شيء آخر. إليك بعض الملاحظات من محادثة سريعة مع مهندس AI C#، تقترح إعادة النظر في هذا المشروع. TBH أعتقد أن هذا المشروع قد لا يزال له ما يبرره، ولكن ربما باعتباره غلاف DSL (نوع من فئة المصنع البسيطة) حول الطرق المختلفة لإنشاء هياكل الكتابة الملحقة بشكل صحيح. قال نوف؛ وهنا ردود الفعل.
(مسودة الملاحظات تحتاج إلى اختبار وتأكيد!)
نعم، في لغة C#، يمكنك استخدام System.Collections.Generic.ArrayBuffer<T>
أو System.Buffers.ArrayPool<T>
للحصول على بنيات أكثر كفاءة للإلحاق فقط والذاكرة التي يمكن تعدادها بكفاءة. ومع ذلك، إذا كنت تريد بنية إلحاق فقط أكثر عملية وكفاءة، ففكر في هذه الخيارات:
ImmutableArray<T>.Builder
بنية إلحاق فقط توفرها الحزمة System.Collections.Immutable
.
إنه يسمح بالإلحاق بكفاءة وهو قابل للتغيير أثناء البناء، ولكنه يصبح غير قابل للتغيير بمجرد تحويله إلى ImmutableArray<T>
.
var builder = ImmutableArray.CreateBuilder<int>();builder.Add(1);builder.Add(2);var immutableArray = builder.ToImmutable();
Memory<T>
Span<T>
باستخدام ArrayPool<T>
كفاءة في التعامل مع كتل البيانات الكبيرة مع الحد الأدنى من تخصيصات الذاكرة.
يمكنك إدارة الذاكرة بنفسك، مما يتجنب عمليات إعادة التخصيص المتكررة مقارنة بـ List<T>
.
مناسب إذا كنت تريد مزيدًا من التحكم في استخدام الذاكرة ولكنك تحتاج إلى إدارة دقيقة.
varpool = ArrayPool<int>.Shared;int[] array =pool.Rent(1000); // اطلب مصفوفة من التجمع // قم بإلحاق بياناتك وإدارتها هناpool.Return(array); // العودة إلى التجمع عند الانتهاء
ValueListBuilder<T>
(من System.Buffers)
يوفر طريقة فعالة لبناء المجموعات بطريقة مكدسة مخصصة.
يكون هذا مفيدًا بشكل خاص عندما تعلم أن حجم البيانات محدود وتريد أداءً عاليًا دون تخصيص الكومة.
Span<int> الأوليSpan = stackalloc int[16]; var valueListBuilder = new ValueListBuilder<int>(initialSpan);valueListBuilder.Append(1);valueListBuilder.Append(2);ReadOnlySpan<int>span = valueListBuilder.AsSpan();
Sequence<T>
من System.IO.Pipelines
مناسب للتعامل مع سلسلة من البيانات بكفاءة بطريقة الإلحاق فقط.
مناسب للسيناريوهات التي تتطلب عمليات إدخال/إخراج وبيانات متدفقة عالية الأداء.
List<T>
: جيد للإلحاق (المطفأ O(1)
) ويوفر تعدادًا سريعًا بسبب الذاكرة المتجاورة.
ImmutableArray<T>.Builder
: فعال في الإلحاق ومن ثم جعله غير قابل للتغيير.
Memory<T>
مع ArrayPool<T>
: رائعة لتقليل التخصيصات.
ValueListBuilder<T>
: بناء فعال قائم على المكدس عندما يكون الحجم معروفًا. يعتمد على المكدس، ومناسب للمجموعات المؤقتة والفعالة.
Sequence<T>
: للسيناريوهات عالية الإنتاجية التي تحتاج إلى إلحاق فعال.
اختر بناءً على متطلبات الأداء والذاكرة المحددة لديك.
وقال ChatGPT؛
يعد LinkedList<T>
في لغة C# خيارًا لبنية ذاكرة للإلحاق فقط، ولكنه بشكل عام أقل كفاءة في الذاكرة وأبطأ في التعداد مقارنة بالبنيات الأخرى للأسباب التالية:
LinkedList<T>
: عبء الذاكرة : تحتوي كل عقدة في LinkedList<T>
على مراجع لكل من العقد التالية والسابقة، مما يقدم حملًا كبيرًا للذاكرة مقارنة بالهياكل المتجاورة مثل المصفوفات أو List<T>
.
عدم كفاءة ذاكرة التخزين المؤقت : على عكس المصفوفات أو List<T>
التي يتم تخزينها في الذاكرة المتجاورة، فإن عقد LinkedList<T>
متناثرة عبر الكومة. وهذا يجعلها أقل ملاءمة لذاكرة التخزين المؤقت، مما يؤدي إلى إبطاء التكرار وأوقات الوصول.
أداء التعداد : في حين أن LinkedList<T>
فعال في عمليات الإلحاق ( O(1)
للإضافة إلى النهاية)، فإن أداء التعداد الخاص به عادة ما يكون أبطأ من المصفوفات أو القوائم لأنه يفتقر إلى ميزة منطقة ذاكرة التخزين المؤقت.
LinkedList<T>
:عندما تحتاج إلى عمليات إدراج أو حذف متكررة من كلا الطرفين أو في المنتصف.
إذا كنت تريد تجنب تبديل العناصر كما هو الحال في List<T>
.
LinkedList<T>
لسيناريوهات الإلحاق فقط ولكنه بشكل عام ليس الخيار الأكثر كفاءة للتعداد أو استخدام الذاكرة مقارنةً List<T>
أو غيرها من بنيات البيانات الحديثة.