لماذا يعد كود System/mscorlib أسرع بكثير؟وخاصة بالنسبة للحلقات؟

StackOverflow https://stackoverflow.com/questions/891835

سؤال

هذا مجرد مشروع شخصي كنت أحفر فيه.في الأساس، أقوم بتحليل ملف نصي (على سبيل المثال، من 20 ميجابايت إلى حوالي 1 جيجابايت) باستخدام StreamReader.الأداء قوي جدًا، لكن لا يزال...لقد كنت متشوقًا لمعرفة ما سيحدث إذا قمت بتحليله بطريقة ثنائية.لا تسيئوا الفهم، أنا لا أقوم بالتحسين قبل الأوان.أنا بالتأكيد أقوم بالتحسين الدقيق عن قصد فقط "لأرى".

لذلك، أنا أقرأ في الملف النصي باستخدام صفائف البايت.تعال لتكتشف أن الخطوط الجديدة يمكن أن تكون CR/LF القياسي (Windows) أو CR أو LF...فوضوي جدا.كنت أتمنى أن أتمكن من استخدام Array.IndexOf على CR ثم تخطي LF.بدلاً من ذلك أجد نفسي أكتب تعليمات برمجية مشابهة جدًا لـ IndexOf ولكني أتحقق من أي منهما وأعيد مصفوفة حسب الحاجة.

لذا فإن الجوهر:باستخدام رمز مشابه جدًا لـ IndexOf، لا يزال الكود الخاص بي أبطأ بجنون.لوضعها في منظورها الصحيح باستخدام ملف بحجم 800 ميجابايت:

  • استخدام IndexOf والبحث عن CR:~320 ميجابايت/ثانية
  • باستخدام StreamReader وReadLine:~180 ميجابايت/ثانية
  • لحلقة تكرار IndexOf:~150 ميجابايت/ثانية

إليك الكود الذي يحتوي على حلقة for (~150 ميجابايت/ثانية):

IEnumerator<byte[]> IEnumerable<byte[]>.GetEnumerator() {
    using(FileStream fs = new FileStream(_path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, _bufferSize)) {
        byte[] buffer = new byte[_bufferSize];
        int bytesRead;
        int overflowCount = 0;
        while((bytesRead = fs.Read(buffer, overflowCount, buffer.Length - overflowCount)) > 0) {
            int bufferLength = bytesRead + overflowCount;
            int lastPos = 0;
            for(int i = 0; i < bufferLength; i++) {
                if(buffer[i] == 13 || buffer[i] == 10) {
                    int length = i - lastPos;
                    if(length > 0) {
                        byte[] line = new byte[length];
                        Array.Copy(buffer, lastPos, line, 0, length);
                        yield return line;
                    }
                    lastPos = i + 1;
                }
            }
            if(lastPos > 0) {
                overflowCount = bufferLength - lastPos;
                Array.Copy(buffer, lastPos, buffer, 0, overflowCount);
            }
        }
    }
}

هذه هي كتلة التعليمات البرمجية الأسرع (~ 320 ميجابايت/ثانية):

while((bytesRead = fs.Read(buffer, overflowCount, buffer.Length - overflowCount)) > 0) {
    int bufferLength = bytesRead + overflowCount;
    int pos = 0;
    int lastPos = 0;
    while(pos < bufferLength && (pos = Array.IndexOf<byte>(buffer, 13, pos)) != -1) {
        int length = pos - lastPos;
        if(length > 0) {
            byte[] line = new byte[length];
            Array.Copy(buffer, lastPos, line, 0, length);
            yield return line;
        }
        if(pos < bufferLength - 1 && buffer[pos + 1] == 10)
            pos++;
        lastPos = ++pos;

    }
    if(lastPos > 0) {
        overflowCount = bufferLength - lastPos;
        Array.Copy(buffer, lastPos, buffer, 0, overflowCount);
    }
}

(لا، ​​إنه ليس جاهزًا للإنتاج، فبعض الحالات ستجعله ينفجر؛أستخدم مخزنًا مؤقتًا بحجم 128 كيلو بايت لتجاهل معظم هذه الأشياء.)

لذلك سؤالي الكبير هو ...لماذا يعمل Array.IndexOf بشكل أسرع؟إنه في الأساس نفس الشيء، حلقة for تتجول في مصفوفة.هل هناك شيء يتعلق بطريقة تنفيذ كود mscorlib؟حتى تغيير الكود أعلاه لتكرار IndexOf حقًا والبحث عن CR فقط ثم تخطي LF كما أفعل إذا كان استخدام IndexOf لا يساعد.يخطئ...لقد مررت بالعديد من التباديل وكان الوقت متأخرًا بدرجة كافية لدرجة أنه ربما يكون هناك بعض الأخطاء الصارخة التي أفتقدها؟

راجع للشغل، لقد بحثت في ReadLine ولاحظت أنه يستخدم كتلة التبديل بدلاً من كتلة if...عندما أفعل شيئًا مشابهًا، فمن الغريب أنه يزيد الأداء بحوالي 15 ميجابايت/ثانية.هذه أسئلة أخرى لوقت آخر (لماذا يتم التبديل بشكل أسرع مما لو؟) لكنني اعتقدت أنني أود أن أشير إلى أنني ألقيت نظرة عليه.

كما أنني أقوم باختبار إصدار إصدار خارج VS، لذلك لا يحدث أي تصحيح للأخطاء.

هل كانت مفيدة؟

المحلول

هذا سؤال جيد.النسخة القصيرة هي أن الأمر كله يتلخص في تطبيق IEqualityComparer الذي سيستخدمه IndexOf.دعونا نرى الجزء التالي من التعليمات البرمجية:

using System;
using System.Collections.Generic;
using System.Diagnostics;

class Program {

    static int [] buffer = new int [1024];
    const byte mark = 42;
    const int iterations = 10000;

    static void Main ()
    {
        buffer [buffer.Length -1] = mark;

        Console.WriteLine (EqualityComparer<int>.Default.GetType ());

        Console.WriteLine ("Custom:  {0}", Time (CustomIndexOf));
        Console.WriteLine ("Builtin: {0}", Time (ArrayIndexOf));
    }

    static TimeSpan Time (Action action)
    {
        var watch = new Stopwatch ();
        watch.Start ();
        for (int i = 0; i < iterations; i++)
            action ();
        watch.Stop ();
        return watch.Elapsed;
    }

    static void CustomIndexOf ()
    {
        for (int i = 0; i < buffer.Length; i++)
            if (buffer [i] == mark)
                break;
    }

    static void ArrayIndexOf ()
    {
        Array.IndexOf (buffer, mark);
    }
}

سوف تحتاج إلى تجميعها مع دي جي / تحسين +.

هذه هي النتيجة التي لدي:

C:\Tmp>test
System.Collections.Generic.GenericEqualityComparer`1[System.Int32]
Custom:  00:00:00.0386403
Builtin: 00:00:00.0427903

الآن، قم بتغيير نوع المصفوفة وEqualityComparer إلى بايت، وهذه هي النتيجة التي حصلت عليها:

C:\Tmp>test
System.Collections.Generic.ByteEqualityComparer
Custom:  00:00:00.0387158
Builtin: 00:00:00.0165881

كما ترون، فإن مصفوفة البايت عبارة عن غلاف خاص، والذي ربما تم تحسينه للعثور على بايت في مصفوفة بايت.نظرًا لأنني لا أستطيع فك ترجمة إطار عمل .net، فقد أوقفت التحليل هنا، ولكن أعتقد أن هذا دليل جيد جدًا.

نصائح أخرى

يتم إنشاء ملفات mscorlib أثناء التثبيت.حاول إنشاء ملفك باستخدام الأداة المساعدة Ngen.exe (المقدمة مع إطار عمل .NET على ما أعتقد)...ومن ثم التحقق من المعايير.يمكن أن يكون أسرع قليلا.

لتشغيل تعليمات NET البرمجية الخاصة بك بسرعات قريبة من السرعة الأصلية، توصي Microsoft بـ "إنشاء" التعليمات البرمجية الخاصة بك أثناء تثبيت التطبيق...

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top