سؤال

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

ما وجدته فاجأني، جهاز التطوير الخاص بي هو جهاز كمبيوتر Q6600 رباعي النواة بسرعة 2.4 جيجا هرتز يعمل بنظام التشغيل Windows 7 x64، وكان متوسط ​​وقت تبديل السياق من الاختبار 5.66 ميكروثانية مع انحراف معياري قدره 5.738 ميكروثانية، وحد أقصى يبلغ 1.58 مللي ثانية تقريبًا ( عامل 282!).تردد ساعة الإيقاف هو 427.7 نانو ثانية، لذلك ما زلت بعيدًا عن ضجيج المستشعر.

ما أود القيام به هو تقليل وقت المراسلة بين مؤشرات الترابط قدر الإمكان، وبنفس القدر من الأهمية، تقليل الانحراف المعياري لتبديل السياق.أدرك أن Windows ليس نظام تشغيل في الوقت الحقيقي، ولا توجد ضمانات، ولكن برنامج جدولة Windows عبارة عن جدول زمني عادل قائم على الأولوية، والخيطان في هذا الاختبار كلاهما لهما الأولوية القصوى (الخيوط الوحيدة التي يجب أن تكون تلك عالية)، لذلك لا ينبغي أن يكون هناك أي مفاتيح تبديل للسياق على سلاسل الرسائل (كما يتضح من أكبر وقت يبلغ 1.58 مللي ثانية ...أعتقد أن windows quanta هو 15.65 مللي ثانية؟) الشيء الوحيد الذي يمكنني التفكير فيه هو الاختلاف في توقيت استدعاءات نظام التشغيل لآليات القفل التي يستخدمها CCR لتمرير الرسائل بين سلاسل الرسائل.

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

إليك الكود المصدري من اختباراتي:

using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using Microsoft.Ccr.Core;

using System.Diagnostics;

namespace Test.CCR.TestConsole
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Starting Timer");
            var sw = new Stopwatch();
            sw.Start();

            var dispatcher = new Dispatcher(1, ThreadPriority.Highest, true, "My Thread Pool");
            var dispQueue = new DispatcherQueue("Disp Queue", dispatcher);

            var sDispatcher = new Dispatcher(1, ThreadPriority.Highest, true, "Second Dispatcher");
            var sDispQueue = new DispatcherQueue("Second Queue", sDispatcher);

            var legAPort = new Port<EmptyValue>();
            var legBPort = new Port<TimeSpan>();

            var distances = new List<double>();

            long totalTicks = 0;

            while (sw.Elapsed.TotalMilliseconds < 5000) ;

            int runCnt = 100000;
            int offset = 1000;

            Arbiter.Activate(dispQueue, Arbiter.Receive(true, legAPort, i =>
                                                                            {
                                                                                TimeSpan sTime = sw.Elapsed;
                                                                                legBPort.Post(sTime);
                                                                            }));
            Arbiter.Activate(sDispQueue, Arbiter.Receive(true, legBPort, i =>
                                                                             {
                                                                                 TimeSpan eTime = sw.Elapsed;
                                                                                 TimeSpan dt = eTime.Subtract(i);
                                                                                 //if (distances.Count == 0 || Math.Abs(distances[distances.Count - 1] - dt.TotalMilliseconds) / distances[distances.Count - 1] > 0.1)
                                                                                 distances.Add(dt.TotalMilliseconds);

                                                                                 if(distances.Count > offset)
                                                                                 Interlocked.Add(ref totalTicks,
                                                                                                 dt.Ticks);
                                                                                 if(distances.Count < runCnt)
                                                                                     legAPort.Post(EmptyValue.SharedInstance);
                                                                             }));


            //Thread.Sleep(100);
            legAPort.Post(EmptyValue.SharedInstance);

            Thread.Sleep(500);

            while (distances.Count < runCnt)
                Thread.Sleep(25);

            TimeSpan exTime = TimeSpan.FromTicks(totalTicks);
            double exMS = exTime.TotalMilliseconds / (runCnt - offset);

            Console.WriteLine("Exchange Time: {0} Stopwatch Resolution: {1}", exMS, Stopwatch.Frequency);

            using(var stw = new StreamWriter("test.csv"))
            {
                for(int ix=0; ix < distances.Count; ix++)
                {
                    stw.WriteLine("{0},{1}", ix, distances[ix]);
                }
                stw.Flush();
            }

            Console.ReadKey();
        }
    }
}
هل كانت مفيدة؟

المحلول

Windows ليس نظام تشغيل في الوقت الحقيقي.لكنك عرفت ذلك بالفعل.ما يقتلك هو أوقات تبديل السياق، وليس بالضرورة أوقات الرسائل.لم تحدد حقًا كيفية عمل الاتصال بين العمليات.إذا كنت تقوم بالفعل بتشغيل عدة سلاسل رسائل، فستجد بعض المكاسب من خلال عدم استخدام رسائل Windows كبروتوكول اتصال، وبدلاً من ذلك حاول تشغيل IPC الخاص بك باستخدام قوائم انتظار الرسائل المستضافة في التطبيق بدلاً من ذلك.

أفضل متوسط ​​يمكن أن تتمناه هو 1 مللي ثانية مع أي إصدار من Windows عند حدوث تبديل السياق.من المحتمل أن ترى الأوقات التي تبلغ 1 مللي ثانية عندما يتعين على تطبيقك أن يستسلم للنواة.وهذا حسب التصميم لتطبيقات Ring-1 (مساحة المستخدم).إذا كان من المهم للغاية أن تحصل على أقل من 1 مللي ثانية، فستحتاج إلى تحويل بعض تطبيقاتك إلى Ring-0، مما يعني كتابة برنامج تشغيل الجهاز.

لا تعاني برامج تشغيل الأجهزة من نفس أوقات تبديل السياق التي تعاني منها تطبيقات المستخدم، كما أنها تتمتع بإمكانية الوصول إلى مؤقتات بدقة نانو ثانية ومكالمات النوم أيضًا.إذا كنت بحاجة إلى القيام بذلك، فإن DDK (مجموعة تطوير برامج تشغيل الأجهزة) متاحة مجانًا من Microsoft، ولكنني أوصي بشدة بالاستثمار في مجموعة أدوات تطوير تابعة لجهة خارجية.عادةً ما يكون لديهم عينات جيدة حقًا والكثير من المعالجات لإعداد الأشياء في نصابها الصحيح والتي قد تستغرق شهورًا من قراءة مستندات DDK لاكتشافها.ستحتاج أيضًا إلى الحصول على شيء مثل SoftIce لأن مصحح أخطاء Visual Studio العادي لن يساعدك في تصحيح أخطاء برامج تشغيل الأجهزة.

نصائح أخرى

قم بإجراء 15 عملية غير متزامنة يملك أن تكون غير متزامن؟أي.هل أنت مجبر على العمل بهذه الطريقة بسبب القيود المفروضة على بعض المكتبات، أو هل لديك خيار إجراء مكالمات متزامنة؟

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

تصف عبارة "متوازي بشكل محرج" خوارزمية يكون فيها غالبية العمل الذي يتم إنجازه مستقلاً بشكل واضح وبالتالي يمكن تنفيذه بأي ترتيب، مما يجعل من السهل موازنته.

لكنك "تقوم بربط 15 عملية غير متزامنة عبر المنافذ وأجهزة الاستقبال".يمكن وصف هذا بأنه "متسلسل بشكل محرج".وبعبارة أخرى، يمكن كتابة نفس البرنامج بشكل منطقي على موضوع واحد.ولكن بعد ذلك ستفقد أي توازي للعمل المرتبط بوحدة المعالجة المركزية والذي يحدث بين العمليات غير المتزامنة (بافتراض وجود أي أهمية).

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

السبب الوحيد لتشغيل سلاسل رسائل متعددة هو أن لديك قدرًا كبيرًا من العمل الذي يتعين على وحدات المعالجة المركزية (CPUs) القيام به، ولذا ترغب في مشاركته بين عدة وحدات معالجة مركزية (CPU).إذا كانت الأجزاء الفردية من الحساب قصيرة العمر بدرجة كافية، فسيكون تبديل السياق بمثابة عبء كبير أي نظام التشغيل.من خلال تقسيم العمليات الحسابية الخاصة بك إلى 15 مرحلة، كل منها قصيرة جدًا، فإنك تطلب بشكل أساسي من نظام التشغيل إجراء الكثير من تبديل السياق غير الضروري.

لا يعني ThreadPriority.Highest أن برنامج جدولة سلسلة الرسائل نفسه فقط لديه أولوية أعلى.تتمتع Win32 API بمستوى أكثر تفصيلاً لأولوية مؤشر الترابط (نقري) مع عدة مستويات أعلى من الأعلى (IIRC Highest عادةً هو أعلى أولوية يمكن تشغيل التعليمات البرمجية غير الإدارية عليها، ويمكن للمسؤولين جدولة أولوية أعلى كما هو الحال مع أي برامج تشغيل الأجهزة/رمز وضع kernel) لذلك ليس هناك ما يضمن أنه لن يتم استباقهم .

حتى إذا كان هناك مؤشر ترابط يعمل مع النوافذ ذات الأولوية العليا، فيمكنه الترويج لسلاسل رسائل أخرى فوق أولويتها الأساسية إذا كانت تحتفظ بأقفال الموارد التي تتطلبها سلاسل الرسائل ذات الأولوية الأعلى، وهو احتمال آخر قد يجعلك تعاني من مفاتيح تبديل السياق.

وحتى مع ذلك، كما تقول، فإن Windows ليس نظام تشغيل في الوقت الفعلي وليس من المضمون احترام أولويات سلسلة المحادثات على أي حال.

لمهاجمة هذه المشكلة بطريقة مختلفة، هل تحتاج إلى العديد من العمليات غير المتزامنة المنفصلة؟قد يكون من المفيد التفكير في:تقسيم العمل عموديًا (معالجة أجزاء numCores من البيانات بشكل غير متزامن من البداية إلى النهاية) بدلاً من تقسيم العمل أفقيًا (كما هو الحال الآن، مع معالجة كل قطعة من البيانات في 15 مرحلة منفصلة)؛قم بربط بعض مراحلك الـ 15 بشكل متزامن لتقليل المجموع إلى عدد أصغر.

سيكون الحمل الزائد للاتصالات بين الخيوط دائمًا غير تافه.إذا كانت بعض عملياتك الـ 15 تؤدي جزءًا صغيرًا فقط من العمل، فإن مفاتيح تبديل السياق سوف تضايقك.

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