2012-02-17 67 views
4

我有一个简单的程序,它启动n个线程并在每个线程上创建一些负载。 如果我只启动一个线程,一个内核获得大约100%的负载。 如果我用16个线程(这意味着每个内核有一个线程)启动一个进程,我只能获得大约80%的负载。 如果我用2个线程启动8个进程(这仍然意味着每个内核有一个线程),那么我得到大约99%的负载。 我没有在这个示例中使用任何锁定。为什么有两个线程的8个进程每个创建的负载都比一个具有16个线程的进程多?

这种行为的原因是什么? 我知道如果有100个线程工作,负载就会下降,因为操作系统必须安排很多工作。 但是在这种情况下,只有与内核一样多的线程。

它更糟糕(对我来说至少)。 如果我在我的循环中添加一个简单的thread.sleep(0),带有一个进程和16个线程的负载增加高达95%。

任何人都可以回答这个问题,或提供有关此特定主题的更多信息的链接?

One Process 16 threads

Eight Process 2 threads

One Process 16 threads with thread.sleep(0)

//Sample application which reads the number of threads to be started from Console.ReadLine 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      Console.WriteLine("Enter the number of threads to be started"); 
      int numberOfThreadsToStart; 

      string input = Console.ReadLine(); 

      int.TryParse(input, out numberOfThreadsToStart); 
      if(numberOfThreadsToStart < 1) 
      { 
       Console.WriteLine("No valid number of threads entered. Exit now"); 
       Thread.Sleep(1500); 
       return; 
      } 

      List<Thread> threadList = new List<Thread>(); 
      Stopwatch sw = Stopwatch.StartNew(); 
      for (int i = 0; i < numberOfThreadsToStart; i++) 
      { 
       Thread workerThread = new Thread(MakeSomeLoad); 
       workerThread.Start(); 
       threadList.Add(workerThread); 
      } 

      while (true) 
      { 
       Console.WriteLine("I'm spinning... "); 
       Thread.Sleep(2000); 
      } 
     } 

     static void MakeSomeLoad() 
     { 
      for (int i = 0; i < 100000000; i++) 
      { 

       for (int j = 0; j < i; j++) 
       { 
        //uncomment the following line to increase the load 
        //Thread.Sleep(0); 
        StringBuilder sb = new StringBuilder(); 
        sb.Append("hello world" + j); 
       } 
      } 
     } 
    } 

回答

6

您的测试看起来很沉重的GC。如果一个进程中有16个线程,GC将在该进程中运行更多,并且由于客户端GC不是并行的,这导致更低的负载。即每个GC线程有16个垃圾生成线程。

另一方面,如果您运行8个进程,每个进程有两个线程,则每个GC线程只会有两个线程产生垃圾,GC可以在这些进程之间并行工作。

如果你编写一个测试产生较少的垃圾,并直接使用更多的CPU,你可能会得到不同的结果。

(请注意,这仅仅是猜测,我没有运行测试,因为我只有一个双核CPU,这将是不同从结果反正)

+0

这似乎是原因,如果我用这样的东西替换stringbuilder:'int x = i + j; int z = x ++;',我用一个进程和16个线程获得了100%的负载。 – Manuel 2012-02-17 15:49:30

1

使用类似Thread.SpinWait(int.MaxValue)生产CPU负载,因为你的程序主要产生内存负载,这可能会导致错误共享等效果。正如CodeInChaos所述,GC活动也很可能会影响性能。

1

与其他人一样,我怀疑这与GC有关。加载示例使用巨大的内存量,在两个for循环结束时,StringBuilder对象将要求千兆字节大小的阵列存储其数据。

有几个原因可导致GC线程减缓处理。

一是当虚拟机运行内存不足时,大多数线程将不得不暂停并等待GC释放内存,然后才能继续(这是因为所有线程都会要求更多的内存在执行过程中几乎同时)。

第二个是关于线程的上下文切换(这可能是最大的原因)。如果线程A在内核X上运行时内存不足,则GC将不得不加载到内核X或将线程A的所有内存从内核X的缓存装载到其正在运行的内核上的缓存中。无论哪种方式,CPU将不得不等待其缓存从RAM中加载内存。与硬盘驱动器相比,RAM速度很快,但与CPU相比,速度很慢。当CPU等待RAM响应时,它不能进行任何处理,从而减少负载。

当你有多个虚拟机时,每个虚拟机都可以运行在自己的内核上,而不关心其他虚拟机是做什么的。当GC被调用时,不需要上下文切换,因为GC可以在与VM上的其他两个线程相同的核心上运行。

4

别的东西要考虑的是,有不同的模式,以垃圾收集器:

  • 服务器GC
  • 工作站GC - 并行(用于asp.net默认execept)
  • 工作站GC - 非并发

你可以找到每个here的一些图形细节。

由于您的进程正在使用大量线程并分配了大量内存,因此您应该尝试使用服务器GC。

服务器GC是为高吞吐量和高可扩展性优化, 服务器应用程序,其中有一个一致的负载和请求 分配和高速率释放内存。服务器GC每个处理器使用 一个堆和一个GC线程,并尝试尽可能平衡 堆。在进行垃圾回收时,GC 线程在其各自的线程上工作,并在特定的 点处进行渲染。由于他们都在自己的堆上工作,所以需要最小限度的锁定等,因此在这种情况下非常有效。

您启用服务器CG在你的App.config:

<configuration> 
<runtime> 
    <gcServer enabled="true" /> 
</runtime> 
</configuration> 

请注意,这只会多处理器(或核心)的系统上运行。如果Windows只报告一个处理器,那么您将获得Workstation GC - Non Concurrent。

相关问题