2009-09-02 56 views
3

我通过端口和接收器将15个异步操作链接在一起。这让我非常关心线程间消息传递时间,特别是在任务发布数据到端口之间花费的时间,并且新任务开始在不同线程上处理同一数据。假设每个线程在启动时都处于空闲状态,我已经生成了一个测试,它使用stop watch类来测量两个不同调度器的时间,每个调度器使用单个线程以最高优先级运行。线程通信时间

我发现令我吃惊的是,我的开发平台是运行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?)我唯一能想到的是操作系统调用时间的变化到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(); 
     } 
    } 
} 
+0

你为什么说“线程中不应该有任何上下文切换”?我能做到的唯一方法就是如果你能以某种方式保证线程独占使用核心。从我对CCR文档的简要介绍中,我没有发现任何可以做到这一点的功能。 – sipwiz 2009-09-02 07:04:02

+0

你是对的 - 它不是保证,但是以最高优先级运行,唯一可能中断线程的是内核中断,或者以最高优先级运行的另一个进程(本应该不是这样)... – Superman 2009-09-02 15:06:02

+0

您无法明确更改上下文切换的标准偏差。这取决于太多因素。内核也会使用CPU和其他进程。 – user224579 2009-09-30 07:55:24

回答

2

Windows不是一个实时操作系统。但是你已经知道了。什么是杀你是上下文切换时间,不一定是消息时间。你没有真正指出你的进程间通信如何工作。如果你真的只是运行多个线程,你会发现一些不使用Windows消息作为通信协议的好处,而是尝试使用应用托管消息队列来滚动你自己的IPC。

当发生上下文切换时,您希望的最佳平均值为1ms,任何版本的Windows都可以。当您的应用程序必须屈服于内核时,您可能会看到1ms的时间。这是针对Ring-1应用程序(用户空间)设计的。如果你得到1ms以下的绝对关键,你需要将你的某些应用程序切换到Ring-0,这意味着写一个设备驱动程序。

设备驱动程序不会遭受用户应用程序执行相同的上下文切换时间,并且还可以访问纳秒分辨率计时器和睡眠呼叫。如果您确实需要这样做,DDK(设备驱动程序开发工具包)可以从Microsoft免费获得,但我强烈建议您投资第三方开发工具包。他们通常有非常好的样本和许多向导来设置正确的东西,这需要你花几个月的时间阅读DDK文档。您还需要获得类似SoftIce的内容,因为普通的Visual Studio调试器不会帮助您调试设备驱动程序。

0

ThreadPriority.Highest并不仅仅意味着线程调度器本身具有更高的优先级。 Win32 API具有更细粒度的线程优先级(clicky),级别高于最高级别(IIRC)最高级别通常是可以运行的最高优先级非管理代码,管理员可以调度更高优先级,因为任何硬件驱动程序/内核模式代码),所以不能保证他们不会被抢先。

即使线程优先级最高的Windows上运行,如果他们持有较高优先级的线程需要这是另一种可能性,为什么你可能患上上下文切换资源锁可以促进他们的基本优先级以上的其他线程。

即使这样,就像你说的,Windows不是一个实时操作系统,它不能保证反正兑现线程优先级。

0

要以不同的方式攻击这个问题,您是否需要这么多的分离异步操作?考虑:垂直分区工作(异步处理numCores数据块的数据端到端),而不是水平分割工作(就像现在,每个数据块在15个分离阶段中处理);同步耦合你的15个阶段中的一些以减少总数到​​更小的数量。

线程间通信的开销将永远是不平凡的。如果你15个操作中的一部分只做了一小部分工作,上下文切换会咬你。

2

15个异步操作是否有要异步?即你是否被某种库的限制以这种方式操作,或者你有选择进行同步调用吗?

如果您有选择,则需要构建应用程序,以便异步性的使用由配置参数控制。在不同线程上返回的异步操作与在同一线程上返回的同步操作之间的区别应该在代码中是透明的。这样你就可以在不改变代码结构的情况下对其进行调整。

“尴尬平行”这个短语描述了一种算法,其中大部分工作都是独立的,因此可以按任意顺序完成,使其易于并行化。

但是你“通过端口和接收器链接15个异步操作”。这可以被描述为“令人尴尬的顺序”。换句话说,同一个程序可以在逻辑上写在一个线程上。但是,如果在异步操作之间发生CPU绑定工作(假设有任何意义),那么您将失去任何并行性。

如果您编写了一个简单的测试来删除任何重要的CPU绑定工作,并且只测量上下文切换时间,那么就猜测您将如何测量上下文切换时间的变化发现。

运行多线程的唯一原因是因为你有大量的工作要做,所以你想在多个CPU之间共享它。如果各个计算块的寿命足够短,则上下文切换对于任何OS都将是一个重要的开销。通过将计算分为15个阶段,每个阶段都非常短,您基本上要求操作系统执行大量不必要的上下文切换。