إن Spanned عبارة عن مكتبة .NET ذات الأداء العالي ، والتي تقدم بدائل متوافقة مع أنواع BCL الشهيرة وتوفر حلولًا متجهة للعمليات المشتركة على المسافات.
للبدء ، أضف أولاً الحزمة الممتدة إلى مشروعك. يمكنك القيام بذلك عن طريق تشغيل الأمر التالي:
dotnet add package Spannedبدلاً من ذلك ، يمكنك تثبيته عبر وحدة تحكم Package Manager مع هذا الأمر:
Install-Package Spannedلاحظ أن .NET قد جمع العديد من طرق التحسين مع مرور الوقت ، سواء تعتمد على الإطار أو وقت التشغيل. وبالتالي ، فقد أصبح من الصعب للغاية على الجوار رمزًا محسّنًا للغاية لأطر مختلفة في قاعدة كود واحدة. لذلك ، اتخذت قرارًا صعبًا بدعم إطار عمل واحد فقط لكل إصدار من هذه المكتبة. يمكنك العثور على قائمة بإصدارات المكتبة والإصدارات الإطارية المدعومة المقابلة في الجدول أدناه:
| .NET Standard 2.0 | .NET Standard 2.1 | .NET 8+ | |
|---|---|---|---|
على الرغم من أنه قد يكون من المغري استخدام v0.0.1 لجميع احتياجاتك ، نظرًا لدعمها لـ .NET Standard 2.0 والاعتماد الواسع النطاق لـ .NET Standard 2.0 في الوقت الحاضر ، أوصي بشدة بعدم القيام بذلك. لا توجد تحسينات تقريبًا يمكن للمرء أن يؤديها باستخدام هذا الإطار القديم. حتى أن Span الحبيب لدينا ، التي يمكن الوصول إليها في .NET Standard 2.0 عبر حزمة System.Memory Nuget ، تُعرف باسم "Slow Span" ، لأن هذه Span ليست أكثر من مجرد ArraySegment المعادلة ، والتي تفتقر إلى الدعم المناسب على جانب وقت التشغيل/JIT. لذلك ، يرجى اختيار أفضل إصدار حزمة لبيئتك ، وليس الإصدار الذي يبدو أنه يناسبهم جميعًا.
قبل أن ندخل في التفاصيل ، دعنا نناقش بعض حالات الحافة الشائعة التي قد تواجهها أثناء استخدام هذه المكتبة ، والإجابة على السؤال: "لماذا ليس جزء X من .NET؟" باختصار ، كل ما يمكنك العثور عليه هنا سهل الاستخدام وسهل الاستخدام.
لنبدأ بنقطة واضحة. تم تصميم هذه المكتبة خصيصًا للسيناريوهات التي تقاتل فيها الأسنان والأظافر لكل بايت مخصص وكل نانو ثانية من وقت التنفيذ في مسارات حرجة للغاية. ليس من المفترض أن تكون حلًا يناسب الجميع لقاعدة الكود بأكملها. إن دمج الأنواع من هذه المكتبة في كل سيناريو يمكن تصوره قد يؤدي إلى تدهور الأداء العام لتطبيقك ، بدلاً من تعزيزه.
تذكر ، لا تغوص أولاً في محيط التحسين النانوي حتى تكون متأكدًا من ضرورة ذلك.
من الخطأ الشائع الذي يجب تجنبه هو تمرير أي من الأنواع التي توفرها هذه المكتبة حسب القيمة (نعم ، يجب أن يساعدك هذا وحده على فهم سبب عدم وجود شيء مثل هذا ولن يكون جزءًا من BCL) . على سبيل المثال ، على الرغم من أن الكود التالي قد يبدو جيدًا ، إلا أنه في الواقع كارثي:
ValueStringBuilder sb = new ValueStringBuilder ( 16 ) ;
sb . AppendUserName ( user ) ;
// ...
public static void AppendUserName ( this ValueStringBuilder sb , User user )
{
sb . Append ( user . FirstName ) ;
sb . Append ( ' ' ) ;
sb . Append ( user . LastName ) ;
} قد يؤدي إلحاق منشئ السلسلة بتكبير المخزن المؤقت الداخلي. ومع ذلك ، نظرًا لأننا مررنا بـ ValueStringBuilder حسب القيمة (أي نسخها) ، فإن ValueStringBuilder الأصلي لن يكون على دراية به وسيستمر في استخدام مخزن مؤقت تم التخلص منه بالفعل.
على الرغم من أن هذا النهج قد يبدو أنه يعمل مع المدخلات المعقدة أثناء الاختبار ، إلا أنه سيفشل أحيانًا ، ليس فقط في الكود ، ولكن أيضًا بعض الأجزاء العشوائية من وقت تشغيل التطبيق الخاص بك من خلال التدخل مع المخزن المؤقت الذي عادت إليه نسخة من ValueStringBuilder بالفعل إلى التجمع ، لذلك يمكن إعادة استخدامها بواسطة شيء آخر.
قد تحاول أن تكون ذكيًا حول هذا الموضوع ومعالجة المشكلة من خلال إعادة كتابة طريقة التمديد الإشكالية على النحو التالي:
public static void AppendUserName ( this in ValueStringBuilder sb , User user )
{
sb . Append ( user . FirstName ) ;
sb . Append ( ' ' ) ;
sb . Append ( user . LastName ) ;
} الآن ، يتم تمرير ValueStringBuilder بالرجوع إليه ، لذلك لا ينبغي أن تكون هناك مشاكل ، أليس كذلك؟ حسنا ، لا. يوجد in في تقليل تكاليف نسخ مثيل نوع القيمة بأكمله عن طريق تمرير مرجعه إلى طريقة مع الحفاظ على الدلالات كما لو تم تمريره بالقيمة. هذا يعني أن أي تعديلات دولة في ValueStringBuilder المقدمة لن يتم نشرها على المثيل الأصلي. لذلك ، لا يزال لدينا نفس المشكلة على أيدينا. الطريقة الصحيحة الوحيدة لتنفيذ طريقة قد تعدل الحالة الداخلية لمثيل نوع القيمة هي تمريرها فعليًا بالرجوع إليها:
ValueStringBuilder sb = new ValueStringBuilder ( 16 ) ;
AppendUserName ( ref sb , user ) ;
// ...
public static void AppendUserName ( ref ValueStringBuilder sb , User user )
{
sb . Append ( user . FirstName ) ;
sb . Append ( ' ' ) ;
sb . Append ( user . LastName ) ;
}على الرغم من أن هذا الحل لا يتوهم ، إلا أن هذا الحل له فائدة من العمل فعليًا.
تحدد معظم الأنواع التي توفرها هذه المكتبة طريقة Dispose() ، مما يتيح استخدامها مع الكلمة الرئيسية using الكلمة الرئيسية ، كما هو موضح أدناه:
using ValueStringBuilder sb = new ValueStringBuilder ( stackalloc char [ 16 ] ) ;
Foo ( ref sb ) ;
return sb . ToString ( ) ; ومع ذلك ، هذا لا يعني أنه ينبغي استخدامها مع الكلمة الرئيسية using . من المهم جدًا أن تتذكر كيف يتم خفض الكود أعلاه بالفعل:
ValueStringBuilder sb = new ValueStringBuilder ( stackalloc char [ 16 ] ) ;
try
{
Foo ( ref sb ) ;
return sb . ToString ( ) ;
}
finally
{
sb . Dispose ( ) ;
} إنشاء وإدارة المناطق المحمية ليس مجانيًا. بالنظر إلى تركيزنا على التحسين النانوي ، فإن التأثير هنا ملحوظ. لذلك ، من الأفضل الاتصال يدويًا Dispose() :
ValueStringBuilder sb = new ValueStringBuilder ( stackalloc char [ 16 ] ) ;
Foo ( ref sb ) ;
string result = sb . ToString ( ) ;
sb . Dispose ( ) ;
return result ;بدلاً من ذلك ، تحقق مما إذا كانت الطريقة الأخيرة التي تتصل بها على نوع معين تحتوي على تحميل زائد يؤدي التنظيف تلقائيًا:
ValueStringBuilder sb = new ValueStringBuilder ( stackalloc char [ 16 ] ) ;
Foo ( ref sb ) ;
return sb . ToString ( dispose : true ) ;في Modern .NET ، من الشائع مواجهة النمط التالي:
أو التعبير عن نفس المفهوم في الكود:
const int StackAllocByteLimit = 1024 ;
T [ ] ? spanSource ;
scoped Span < T > span ;
if ( sizeof ( T ) * length > StackAllocByteLimit )
{
spanSource = ArrayPool < T > . Shared . Rent ( length ) ;
span = spanSource . AsSpan ( 0 , length ) ;
}
else
{
spanSource = null ;
span = stackalloc T [ length ] ;
}
DoSomeWorkWithSpan ( span ) ;
if ( spanSource is not null )
{
ArrayPool < T > . Shared . Return ( spanSource ) ;
} ليست أجمل قطعة من الغلاية ، أليس كذلك؟ غالبًا ما ينتهي المنطق الفعلي بالدفن به ، وهو بعيد عن المثالي. هذه هي المشكلة الدقيقة التي يهدف SpanOwner إلى حلها. إليكم نفس المنطق ، ولكن تم إخفاء جميع الغلاية خلف SpanOwner :
SpanOwner < T > owner = SpanOwner < T > . ShouldRent ( length ) ? SpanOwner < T > . Rent ( length ) : stackalloc T [ length ] ;
Span < T > span = owner . Span ;
DoSomeWorkWithSpan ( span ) ;
owner . Dispose ( ) ; أسهل بكثير للكتابة ، وأسهل بكثير للقراءة ، والأهم من ذلك ، أن هذا النهج يوفر نفس الأداء بالضبط لأن SpanOwner مصمم ليكون غير قابل للعلاج بالكامل. يمكن التخلص منها تمامًا من الرمز الخاص بك بواسطة JIT:
| طريقة | يقصد | Stddev | نسبة | حجم الرمز |
|---|---|---|---|---|
| بدون _spanowner_int32 | 5.134 ns | 0.0425 ns | 1.00 | 315 ب |
| with_spanowner_int32 | 4.908 NS | 0.0168 ns | 0.96 | 310 ب |
ValueStringBuilder هو إعادة تنفيذ من StringBuilder مصممة لدعم المخازن المؤقتة المخصصة المكدس. إنه قادر على استخدام تجمع صفيف مشترك لتوسيع المخزن المؤقت الداخلي عند الضرورة. يعد ValueStringBuilder مثاليًا لبناء سلاسل مضغوطة يمكن أن تتناسب مع المكدس ؛ ومع ذلك ، لا ينبغي استخدامه لأي شيء آخر ، لأن العمليات على الأوتار الكبيرة قد تخدع الأداء العام لتطبيقك. يظهر التألق الحقيقي لـ ValueStringBuilder عندما تحتاج إلى إنشاء تسلسل أحرف قصير لا يحتاج إلى تحقيقه كسلسلة على الإطلاق.
يعكس ValueStringBuilder جميع ميزات StringBuilder وتجتاز بنجاح نفس مجموعة اختبارات الوحدة ، مما يسمح لها بالاستبدال بسلاسة كبديل في معظم السيناريوهات.
// Note that providing a capacity instead of a buffer will force
// the builder to rent an array from `ArrayPool<char>.Shared`.
ValueStringBuilder sb = new ( stackalloc char [ 256 ] ) ;
// `ValueStringBuilder` provides a custom interpolated string handler,
// ensuring such operations do not allocate any new strings.
sb . Append ( $ "Hello, { user . Name } ! Your ID is: { user . id } " ) ;
// Unlike `StringBuilder`, `ValueStringBuilder` can be represented
// as a readonly span. Thus, you don't need to actually materialize
// the string you've built in lots of cases.
DisplayWelcome ( ( ReadOnlySpan < char > ) sb ) ;
// Remember to dispose of the builder to return
// a rented buffer, if any, back to the pool.
sb . Dispose ( ) ; ValueList<T> عبارة عن إعادة تنفيذ List<T> مصممة لدعم المخازن المؤقتة المخصصة للمكدس. إنه قادر على استخدام تجمع صفيف مشترك لتوسيع المخزن المؤقت الداخلي عند الضرورة. ValueList<T> مثالي لمعالجة كميات صغيرة من البيانات التي يمكن أن تتناسب مع المكدس ؛ ومع ذلك ، لا ينبغي استخدامه لأي شيء آخر ، لأن العمليات على مجموعات البيانات الكبيرة قد تخدع الأداء العام لتطبيقك.
يعكس ValueList<T> جميع ميزات List<T> وتجتاز بنجاح نفس مجموعة اختبارات الوحدة ، مما يسمح لها بالاستبدال بسلاسة كبديل في معظم السيناريوهات.
// Note that providing a capacity instead of a buffer will force
// the list to rent an array from `ArrayPool<T>.Shared`.
ValueList < int > list = new ( stackalloc int [ 10 ] ) ;
list . Add ( 0 ) ;
list . Add ( 1 ) ;
list . Add ( 2 ) ;
DoSomethingWithIntegers ( ( ReadOnlySpan < int > ) list ) ;
// Remember to dispose of the list to return
// a rented buffer, if any, back to the pool.
list . Dispose ( ) ; ValueStack<T> عبارة عن إعادة تنفيذ من Stack<T> مصمم لدعم المخازن المؤقتة المخصصة للمكدس. إنه قادر على استخدام تجمع صفيف مشترك لتوسيع المخزن المؤقت الداخلي عند الضرورة. ValueStack<T> مثالية لمعالجة كميات صغيرة من البيانات التي يمكن أن تتناسب مع المكدس ؛ ومع ذلك ، لا ينبغي استخدامه لأي شيء آخر ، لأن العمليات على مجموعات البيانات الكبيرة قد تخدع الأداء العام لتطبيقك.
يعكس ValueStack<T> جميع ميزات Stack<T> وتجتاز بنجاح نفس مجموعة اختبارات الوحدة ، مما يتيح لها أن تعمل بسلاسة كبديل في معظم السيناريوهات.
// Note that providing a capacity instead of a buffer will force
// the stack to rent an array from `ArrayPool<T>.Shared`.
ValueStack < int > stack = new ( stackalloc int [ 10 ] ) ;
stack . Push ( 0 ) ;
stack . Push ( 1 ) ;
stack . Push ( 2 ) ;
DoSomethingWithIntegers ( ( ReadOnlySpan < int > ) stack ) ;
// Remember to dispose of the stack to return
// a rented buffer, if any, back to the pool.
stack . Dispose ( ) ; ValueQueue<T> عبارة عن إعادة تنفيذ Queue<T> مصممة لدعم المخازن المؤقتة المخصصة للمكدس. إنه قادر على استخدام تجمع صفيف مشترك لتوسيع المخزن المؤقت الداخلي عند الضرورة. ValueQueue<T> مثالية لمعالجة كميات صغيرة من البيانات التي يمكن أن تتناسب مع المكدس ؛ ومع ذلك ، لا ينبغي استخدامه لأي شيء آخر ، لأن العمليات على مجموعات البيانات الكبيرة قد تخدع الأداء العام لتطبيقك.
تعكس ValueQueue<T> جميع ميزات قائمة Queue<T> ونجاحًا تمرر نفس مجموعة اختبارات الوحدة ، مما يتيح لها أن تعمل بسلاسة كبديل في معظم السيناريوهات.
// Note that providing a capacity instead of a buffer will force
// the queue to rent an array from `ArrayPool<T>.Shared`.
ValueQueue < int > queue = new ( stackalloc int [ 10 ] ) ;
queue . Enqueue ( 0 ) ;
queue . Enqueue ( 1 ) ;
queue . Enqueue ( 2 ) ;
DoSomethingWithIntegers ( ( ReadOnlySpan < int > ) queue ) ;
// Remember to dispose of the queue to return
// a rented buffer, if any, back to the pool.
queue . Dispose ( ) ; ValueSet<T> عبارة عن إعادة تنفيذ لـ HashSet<T> مصممة لدعم المخازن المؤقتة المخصصة للمكدس. إنه قادر على استخدام تجمع صفيف مشترك لتوسيع المخزن المؤقت الداخلي عند الضرورة. تعتبر ValueSet<T> مثالية لمعالجة كميات صغيرة من البيانات التي يمكن أن تتناسب مع المكدس ؛ ومع ذلك ، لا ينبغي استخدامه لأي شيء آخر ، لأن العمليات على مجموعات البيانات الكبيرة قد تخدع الأداء العام لتطبيقك.
تعكس ValueSet<T> جميع ميزات HashSet<T> ونجاح اجتياز نفس مجموعة اختبارات الوحدة ، مما يتيح لها أن تعمل بسلاسة كبديل في معظم السيناريوهات.
// Note that providing a capacity instead of a buffer will force
// the set to rent an array from `ArrayPool<T>.Shared`.
ValueSet < int > set = new ( stackalloc int [ 10 ] ) ;
set . Add ( 0 ) ;
set . Add ( 1 ) ;
set . Add ( 2 ) ;
DoSomethingWithIntegers ( ( ReadOnlySpan < int > ) set ) ;
// Remember to dispose of the set to return
// a rented buffer, if any, back to the pool.
set . Dispose ( ) ; ValueDictionary<TKey, TValue> هو إعادة تنفيذ Dictionary<TKey, TValue> مصمم لدعم المخازن المؤقتة المخصصة للمكدس. إنه قادر على استخدام تجمع صفيف مشترك لتوسيع المخزن المؤقت الداخلي عند الضرورة. ValueDictionary<TKey, TValue> مثالية لمعالجة كميات صغيرة من البيانات التي يمكن أن تتناسب مع المكدس ؛ ومع ذلك ، لا ينبغي استخدامه لأي شيء آخر ، لأن العمليات على مجموعات البيانات الكبيرة قد تخدع الأداء العام لتطبيقك.
يعكس ValueDictionary<TKey, TValue> جميع ميزات Dictionary<TKey, TValue> وتجتاز بنجاح نفس مجموعة اختبارات الوحدة ، مما يسمح لها بالاستبدال بديل بسلاسة في معظم السيناريوهات.
// Note that providing a capacity instead of a buffer will force
// the dictionary to rent an array from `ArrayPool<T>.Shared`.
ValueDictionary < int , string > dictionary = new ( 10 ) ;
dictionary . Add ( 0 , "zero" ) ;
dictionary . Add ( 1 , "one" ) ;
dictionary . Add ( 2 , "two" ) ;
DoSomethingWithPairs ( ( ReadOnlySpan < KeyValuePair < int , string > > ) dictionary ) ;
// Remember to dispose of the dictionary to return
// a rented buffer, if any, back to the pool.
dictionary . Dispose ( ) ; .Min() و .Max() هي طرق تمديد يمكن أن تساعدك في العثور على القيمة الدنيا/الحد الأقصى في المدى. وهي متجهة لجميع الأنواع المدعومة ، على عكس Enumerable.Min() و Enumerable.Max() ، والتي لا توفر أي تحسينات لأرقام الفاصلة العائمة.
ومع ذلك ، هناك مشكلة بسيطة مع أرقام الفاصلة العائمة (أي ، float double ) ، واسم هذه المشكلة هو NaN . كما تعلم ، فإن NaN ليس أكبر من أي رقم أو أقل ، ولا يساوي أي رقم ، حتى لنفسه. وبالتالي ، إذا كان NaN موجودًا في التسلسل المقدم ، فيمكنه تعطيل التنفيذ الساذج الذي يعتمد فقط على نتيجة عمليات المقارنة العادية. وبالتالي ، إذا كان NaN موجودًا في التسلسل المقدم ، فيمكنه تعطيل التنفيذ الساذج الذي يعتمد فقط على نتيجة عمليات المقارنة العادية. لذلك ، فإن عدم حساب مقارنات النقطة العائمة هذا ليس خيارًا.
تمكنت Spanned من توظيف جميع الشيكات المرتبطة بـ NaN بطريقة عالية الكفاءة ، مما يوفر زيادة كبيرة في الأداء على الحلول غير المحسنة. ومع ذلك ، يمكن أن يكون الأداء أفضل إذا لم نكن بحاجة إلى حساب NaN S. هذا هو .UnsafeMax() .UnsafeMin() هذه الطرق خاصة بالمسافات التي تحتوي على أرقام فاصلة عائمة ، وتؤدي عمليات المقارنة دون الاعتراف بوجود NaN ، مما يلغي جميع الشيكات ذات الصلة. لذلك ، إذا كنت متأكدًا تمامًا من تم تطهير نطاق أرقام النقطة العائمة ولا يمكن أن يحتوي على أي NaN S ، فيمكنك الضغط على المزيد من الأداء من عمليات .Min() و .Max() .
في حين أن الفرق بين .Min() و .UnsafeMin() قد لا يكون ملحوظًا للغاية:
| طريقة | يقصد | Stddev | نسبة | حجم الرمز |
|---|---|---|---|---|
| min_loop_single | 3،919.5 نانو ثانية | 15.75 ns | 1.00 | 207 ب |
| min_linq_single | 4،030.3 ns | 37.38 NS | 1.03 | 570 ب |
| min_span_single | 611.1 ns | 8.55 ns | 0.16 | 534 ب |
| unsafemin_span_single | 569.0 ns | 1.82 ns | 0.15 | 319 ب |
تصبح فجوة الأداء كبيرة جدًا بين .Max() و .UnsafeMax() :
| طريقة | يقصد | Stddev | نسبة | حجم الرمز |
|---|---|---|---|---|
| max_loop_single | 3،849.2 ns | 36.97 ns | 1.00 | 215 ب |
| max_linq_single | 3،936.4 NS | 53.51 NS | 1.02 | 643 ب |
| max_span_single | 901.7 NS | 7.12 ns | 0.23 | 606 ب |
| unsafemax_span_single | 551.8 NS | 3.06 ns | 0.14 | 321 ب |
.Sum() هي طريقة تمديد يمكن أن تساعدك في حساب مجموع جميع القيم في فترة. إنه متجه لجميع الأنواع المدعومة ، على عكس Enumerable.Sum() ، والتي لا تفتقر فقط إلى التخصيص ، ولكنها لا توفر عمليات تحميل زائدة لمعظم الأنواع الرقمية خارج الصندوق على الإطلاق.
على غرار .Min() و .Max() ، .Sum() لديه التوأم الشرير الذي يحمل الاسم .UnsafeSum() . ستقوم الطريقة الأساسية بإلقاء OverflowException إذا كان حساب SUM يؤدي إلى زيادة في التدفق/تدفق عدد صحيح. يحضر حراس الفائض ، بطبيعة الحال ، بتكلفة ، وليست حرسًا ضئيلًا. لذلك ، إذا تم تطهير المدخلات الخاصة بك ولا يمكن أن يتسبب في تجاوز الفائض ، أو إذا كان الفائض الصحيح هو السلوك المتوقع في سياق العمل الخاص بك ، فلا تتردد في استخدام .UnsafeSum() . يسرع ضعف .Sum() ، 34 مرة أسرع من حساب المبلغ داخل الحلقة ، وأسرع 130 مرة أسرع من حساب المبلغ عبر LINQ:
| طريقة | يقصد | Stddev | نسبة | حجم الرمز |
|---|---|---|---|---|
| SUM_LOOP_INT16 | 3،820.0 ns | 7.04 ns | 1.00 | 128 ب |
| sum_linq_int16 | 14،472.6 ns | 281.83 NS | 3.80 | 732 ب |
| sum_span_int16 | 214.6 ns | 2.43 ns | 0.06 | 413 ب |
| Unsafesum_span_int16 | 111.8 NS | 1.00 ns | 0.03 | 200 ب |
.LongSum() هي طريقة تمديد يمكن أن تساعدك على حساب مجموع جميع القيم في فترة باستخدام مجمع 64 بت (أي ، منذ long للأعداد الصحيحة الموقعة ، و ulong للأعداد الصحيحة غير الموقعة ، double float ) ، قادرة على تخزين نتيجة أكبر من الحد الأقصى/القيمة الأصلية int.MaxValue + int.MaxValue EG ، لا يمكنك تخزينها للرياضيات. int ) . إنه متجه لجميع الأنواع المدعومة وليس له بدائل مناسبة في LINQ (وبالتالي ، فإن المعيار أدناه غير عادل بعض الشيء) .
.LongSum() لا يحتوي على نظير "غير آمن" ، لأنه حتى أكبر فترة ممكنة تخزن عناصر int.MaxValue من النوع int لا يمكن أن تتسبب في تجاوز تراكم 64 بت ( (long)int.MaxValue * (long)int.MaxValue < long.MaxValue ).
| طريقة | يقصد | Stddev | نسبة | حجم الرمز |
|---|---|---|---|---|
| longsum_loop_int16 | 2،537.1 ns | 21.30 ns | 1.00 | 98 ب |
| longsum_linq_int16 | 14،372.0 ns | 130.00 ns | 5.67 | 734 ب |
| longsum_span_int16 | 251.0 ns | 2.38 ns | 0.10 | 390 ب |
.Average() هي طريقة تمديد يمكن أن تساعدك في حساب متوسط جميع القيم في فترة واحدة. إنه متجه لجميع الأنواع المدعومة ، على عكس Enumerable.Average() ، والذي يوفر فقط مستوى من التحسين للأعداد الصحيحة الموقعة 32 بت (أي ، int s).
تحت غطاء محرك السيارة ، يستخدم .Average() .LongSum() لحساب مجموع جميع العناصر مع تجنب الفائض الصحيح. ومع ذلك ، إذا تم تطهير المدخلات الخاصة بك ولا يمكن أن تسبب واحدة ، فيمكنك التبديل إلى .UnsafeAverage() ، والذي يستخدم .UnsafeSum() ولا يقضي وقت التنفيذ الثمين على حراس الفائض.
| طريقة | يقصد | Stddev | نسبة | حجم الرمز |
|---|---|---|---|---|
| MEANTER_LOOP_INT16 | 2،482.1 ns | 20.04 ns | 1.00 | 241 ب |
| MEANTER_LINQ_INT16 | 13،198.2 ns | 97.67 NS | 5.31 | 1،016 ب |
| MEANTER_SPAN_INT16 | 257.8 NS | 3.61 ns | 0.10 | 593 ب |
| Unsafeaveriver_span_int16 | 116.7 NS | 1.27 ns | 0.05 | 128 ب |
.FillSequential() هي طريقة تمديد يمكن أن تساعدك على ملء فترة معينة بقيم رقمية متتابعة. إنه متجه لجميع الأنواع المدعومة وليس لديه بدائل في LINQ.
| طريقة | يقصد | Stddev | نسبة | حجم الرمز |
|---|---|---|---|---|
| fillAsealient_loop_int16 | 2،499.4 ns | 28.47 NS | 1.00 | 118 ب |
| fillAsealient_span_int16 | 169.2 ns | 0.18 ns | 0.07 | 660 ب |
.IndexOf() ، .LastIndexOf() ، و .Contains() قد يبدو مألوفًا لك ، لأن هذه الطرق يتم توفيرها بواسطة MemoryExtensions . ومع ذلك ، هناك مشكلتان معهما:
IEquatable<T> ، مما يجعلها لا يمكن الوصول إليها في السياق العام غير المحدود.IEqualityComparer<T> .تطبيقات هذه الأساليب التي توفرها هذه المكتبة تتناول كل من هذه المسألة.
مرخصة بموجب شروط ترخيص معهد ماساتشوستس للتكنولوجيا.