يقال أن الأخ جيانغ مين أجرى اختبار أداء صارمًا بما فيه الكفاية على أساليب Array.Sort وEnumerable.OrderBy منذ فترة. ويبدو أن الاستنتاج غير متسق مع النظرية والتوقعات، ولكن تم قياس هذا الاستنتاج في بيئة صارمة نسبيا، الأمر الذي أثار اهتمام الخبراء أيضا. لقد اختبرت ذلك أيضًا على جهازي، وبالطبع قمت بفرز بعض الرموز غير ذات الصلة أولاً:
باستخدام النظام؛
باستخدام System.Collections.Generic؛
باستخدام System.Diagnostics؛
باستخدام System.Linq؛
باستخدام System.Runtime.InteropServices؛
باستخدام System.Threading؛
مساحة الاسم Exam11
{
فئة CodeTimer العامة الثابتة
{
تهيئة الفراغ الثابت العام ()
{
Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;
Thread.CurrentThread.Priority = ThreadPriority.Highest;
الوقت( ""، 1, () => { } );
}
وقت الفراغ الثابت العام (اسم السلسلة، التكرار int، الإجراء الإجراء)
{
إذا عاد ( String.IsNullOrEmpty( name ) ) ؛
// تسخين
فعل()؛
// 1.
ConsoleColor currentForeColor = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine(اسم);
// 2.
GC.Collect( GC.MaxGeneration,GCcollectionMode.Forced );
int[] gcCounts = new int[GC.MaxGeneration + 1];
من أجل ( int i = 0; i <= GC.MaxGeneration; i++ )
{
gcCounts[i] = GC.CollectionCount(i);
}
// 3.
ساعة توقيت = ساعة توقيت جديدة ()؛
watch.Start();
ulong CycleCount = GetCycleCount();
من أجل (int i = 0; i < iteration; i++ ) action();
ulong cpuCycles = GetCycleCount() -cycleCount;
watch.Stop();
// 4.
Console.ForegroundColor = currentForeColor;
Console.WriteLine( "tالوقت المنقضي:t" + watch.ElapsedMillithans.ToString( "N0") + "ms" );
Console.WriteLine( "tدورات وحدة المعالجة المركزية:t" + cpuCycles.ToString( "N0" ) );
// 5.
من أجل ( int i = 0; i <= GC.MaxGeneration; i++ )
{
int count = GC.CollectionCount(i) - gcCounts[i];
Console.WriteLine( "tGen " + i + ": tt" + count );
}
Console.WriteLine();
}
ulong الخاص الثابت GetCycleCount()
{
دورة ulongCount = 0;
QueryThreadCycleTime( GetCurrentThread(), ref CycleCount );
دورة العودةCount؛
}
[DllImport("kernel32.dll" )]
[العودة: MarshalAs(UnmanagedType.Bool)]
static extern bool QueryThreadCycleTime(IntPtr threadHandle, ref ulong CycleTime);
[DllImport("kernel32.dll" )]
static extern IntPtr GetCurrentThread();
}
برنامج الصف
{
الفراغ الثابت الرئيسي (سلسلة [] وسيطات)
{
فار عشوائي = جديد Random(DateTime.Now.Millisec);
var array = Enumerable.Repeat( 0, 50000 ).Select( _ => new Person { ID = Random.Next() } ).ToArray();
// برنامج لاو تشاو
CodeTimer.Initialize();
CodeTimer.Time( "SortWithCustomComparer", 500, () => SortWithCustomComparer( CloneArray( array ) ));
CodeTimer.Time( "SortWithLinq", 500, () => SortWithLinq( CloneArray( array ) ));
Console.ReadLine();
}
فراغ ثابت خاص SortWithCustomComparer (صفيف الشخص [])
{
Array.Sort( array, new PersonComparer() );
}
SortWithLinq (Person[] array) باطل ثابت خاص
{
فار مرتبة =
(من شخص في الصفيف
الطلب حسب الشخص.ID
حدد الشخص ).ToList();
}
خاص ثابت T[] CloneArray<T>( مصدر T[] )
{
var dest = new T[source.Length];
Array.Copy( source, dest, source.Length );
وجهة العودة؛
}
}
شخص من الطبقة العامة
{
السلسلة العامة الاسم الأول
{
يحصل؛
تعيين؛
}
السلسلة العامة اسم العائلة
{
يحصل؛
تعيين؛
}
معرف int العام
{
يحصل؛
تعيين؛
}
}
الفئة العامة PersonComparer: IComparer<Person>
{
مقارنة int العامة (الشخص x، الشخص y)
{
إرجاع x.ID - y.ID؛
}
}
}
تظهر نتائج الاختبار بالفعل المزايا الواضحة لـ Enumerable.OrderBy، حتى في وضع التحويل البرمجي للإصدار.
من حيث المبدأ، هذا مستحيل، لذلك يجب أن تكون هناك منافسة غير عادلة في مكان ما.
لقد قمت بإعادة النظر في الكود بأكمله وقمت بتعديل موضع رمزي الاختبار:
CodeTimer.Time( "SortWithLinq", 100, () => SortWithLinq( CloneArray( array ) ));
CodeTimer.Time( "SortWithCustomComparer", 100, () => SortWithCustomComparer( CloneArray( array ) ));
وبطبيعة الحال، لن يكون لهذا أي تأثير. ثانيًا، وجدت أن مثيل المقارنة هذا قد تم إنشاؤه عدة مرات (في الواقع 100 مرة فقط)، لذا قمت بتحسينه على النحو التالي:
مقارن PersonComparer الثابت الخاص = new PersonComparer();
فراغ ثابت خاص SortWithCustomComparer (صفيف الشخص [])
{
Array.Sort(array, Comparer);
}
لا يوجد تحسن في النتائج.
لذلك يمكنني التركيز فقط على تنفيذ المقارنة:
الفئة العامة PersonComparer: IComparer<Person>
{
مقارنة int العامة (الشخص x، الشخص y)
{
إرجاع x.ID - y.ID؛
}
}
هذا ليس نهجا عادلا، ولكن لا ينبغي أن يسبب مشاكل في الأداء. قم بتغييره إلى النموذج التالي:
مقارنة int العامة (الشخص x، الشخص y)
{
إرجاع x.ID.CompareTo(y.ID);
}
لا يوجد حتى الآن أي تحسن في الأداء، بل انخفاض طفيف (وهذا أمر طبيعي).
يبدو أننا وصلنا إلى طريق مسدود؟ في الواقع، نحن قريبون جدًا من الحقيقة، في الواقع، لقد أعلن العنوان بالفعل عن الإجابة. نظرًا لأن المشكلة تكمن في أن person.ID هو سمة، يتم استدعاء الطريقة بالفعل ثلاث مرات هنا.
لذلك، قم ببساطة بتغيير المعرف إلى حقل، وستكون نتائج الأداء قريبة من المتوقع.
فلماذا تتمتع LINQ بميزة هنا؟ آه، إذا فهمت مبدأ الفرز في LINQ، فليس من الصعب الاعتقاد بأن LINQ يمكنه حفظ العديد من المكالمات إلى Person.get_ID. وأعتقد أن هذا التحليل سيتم تفصيله في مقال دانيال القادم، لذلك لن أعرضه هنا.
لذلك يمكن استخلاص الاستنتاج، ولكن لا يزال من المفاجئ بعض الشيء أن تتمتع LINQ بمزايا الأداء في بيئات معينة. لكن ما أريد قوله هو أن هناك بالفعل العديد من العوامل التي ستؤثر على هذا الاستنتاج، ولا يمكننا حقًا أن نقول بشكل عام أن LINQ يجب أن يتمتع بمزايا الأداء أو أن Array.Sort يجب أن يتمتع بمزايا الأداء.