簡體   English   中英

互通溝通時間

[英]Interthread communication time

我通過端口和接收器將15個異步操作鏈接在一起。 這讓我非常關注線程間的消息傳遞時間,特別是任務將數據發布到端口與新任務開始在不同線程上處理相同數據之間所花費的時間。 假設每個線程在啟動時空閑的最佳情況,我已經生成了一個測試,該測試使用秒表類來測量兩個不同調度程序的時間,每個調度程序以最高優先級運行一個線程。

我發現讓我感到驚訝的是,我的開發平台是運行Windows 7 x64的Q6600四核2.4 Ghz計算機,我測試的平均上下文切換時間為5.66微秒,標准偏差為5.738微秒,最大值接近1.58毫秒(系數為282!)。 秒表頻率為427.7納秒,因此我仍然遠離傳感器噪音。

我想要做的是盡可能地減少線程間的消息傳遞時間,同樣重要的是,減少上下文切換的標准偏差。 我意識到Windows不是實時操作系統,並且沒有保證,但是Windows調度程序是基於公平循環優先級的計划,並且此測試中的兩個線程都處於最高優先級(唯一的線程應該是高),所以線程上不應該有任何上下文切換(很明顯是1.58 ms最大的時間......我相信windows quanta是15.65 ms?)我唯一能想到的是OS調用時間的變化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。

當發生上下文切換時,對於任何版本的Windows,您可以期望的最佳平均值是1ms。 當您的應用程序必須屈服於內核時,您可能會看到1ms的時間。 這是針對Ring-1應用程序(用戶空間)的設計。 如果低於1ms絕對至關重要,則需要將部分應用程序切換到Ring-0,這意味着要編寫設備驅動程序。

設備驅動程序不會遇到與用戶應用程序相同的上下文切換時間,並且還可以訪問納秒級分辨率計時器和睡眠呼叫。 如果您確實需要這樣做,可以從Microsoft免費獲得DDK(設備驅動程序開發工具包),但我強烈建議您投資第三方開發工具包。 他們通常擁有非常好的樣本和許多向導來設置正確的東西,這將花費你幾個月閱讀DDK文檔來發現。 您還希望獲得類似SoftIce的東西,因為正常的Visual Studio調試器不會幫助您調試設備驅動程序。

15個異步操作必須是異步的嗎? 也就是說,您是否因某些庫的限制而被迫以這種方式操作,或者您是否可以選擇進行同步調用?

如果您可以選擇,則需要構建應用程序,以便異步性的使用由配置參數控制。 在不同線程上返回的異步操作與在同一線程上返回的同步操作之間的區別應該在代碼中是透明的。 這樣你就可以在不改變代碼結構的情況下調整它。

短語“令人尷尬地平行”描述了一種算法,其中所做的大部分工作顯然是獨立的,因此可以以任何順序完成,使得易於並行化。

但是你“通過端口和接收器將15個異步操作鏈接在一起”。 這可以被描述為“令人尷尬的順序”。 換句話說,可以在單個線程上邏輯地編寫相同的程序。 但是,對於異步操作之間發生的CPU限制工作,您將失去任何並行性(假設存在任何重要性)。

如果您編寫一個簡單的測試來刪除任何重要的CPU限制工作並只測量上下文切換時間,那么猜測一下,您將測量上下文切換時間的變化,如您所發現的那樣。

運行多個線程的唯一原因是因為您要為CPU做大量工作,因此您希望在多個CPU之間共享它。 如果單個計算塊足夠短,那么上下文切換將是任何 OS的重要開銷。 通過將計算分解為15個階段,每個階段非常短,您實際上是要求操作系統進行大量不必要的上下文切換。

ThreadPriority.Highest並不僅僅意味着線程調度程序本身具有更高的優先級。 Win32 API具有更細粒度的線程優先級( clicky ),其中幾個級別高於最高級別(IIRC最高級別通常是可以運行的最高優先級非管理代碼,管理員可以安排更高的優先級,任何硬件驅動程序/內核模式代碼都可以)所以不能保證他們不會被搶先一步。

即使線程正在以最高優先級運行,窗口也可以提升高於其基本優先級的其他線程,如果它們持有更高優先級線程所需的資源鎖,這是您可能遭受上下文切換的另一種可能性。

即便如此,正如您所說,Windows並不是一個實時操作系統,並且無論如何都無法保證支持線程優先級。

要以不同的方式解決此問題,您是否需要進行如此多的解耦異步操作? 考慮以下內容可能是有用的:垂直分區工作(異步處理numCores數據塊的端到端)而不是水平分區工作(現在,在15個解耦階段處理每個數據塊); 同步耦合15個階段中的一些以將總數減少到更小的數量。

線程間通信的開銷總是非常重要的。 如果您的15個操作中的某些操作只進行了一小部分工作,那么上下文切換會讓您感到困惑。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM