【问题标题】:Why does eight processes with 2 threads each create more load than one process with 16 threads?为什么每个有 2 个线程的八个进程比一个有 16 个线程的进程产生更多的负载?
【发布时间】:2012-02-17 14:53:06
【问题描述】:

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

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

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

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

//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);
                }
            }
        }
    }

【问题讨论】:

    标签: c# multithreading


    【解决方案1】:

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

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

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

    (请注意,这只是猜测,我没有运行你的测试,因为我只有一个双核 CPU,无论如何都会与你的结果不同)

    【讨论】:

    • 这似乎是原因,如果我用这样的东西替换字符串生成器:' int x = i + j; int z = x++;',我得到了 100% 的负载,一个进程和 16 个线程。
    【解决方案2】:

    另外需要考虑的是垃圾收集器有不同的模式:

    • 服务器垃圾回收
    • 工作站 GC - 并发(asp.net 除外)
    • 工作站 GC - 非并发

    您可以找到每个here 的一些图形详细信息。

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

    服务器 GC 针对高吞吐量和高可扩展性进行了优化 负载和请求一致的服务器应用程序 高速分配和释放内存。服务器 GC 使用 每个处理器一个堆和一个 GC 线程,并尝试平衡 尽可能堆。垃圾回收时,GC 线程在它们各自的线程上工作,并在某些时候会合 点。由于它们都在自己的堆上工作,因此锁定最少等。 需要,这使得它在这种情况下非常有效。

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

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

    请注意,这仅适用于多处理器(或内核)系统。如果 windows 只报告一个处理器,那么您将获得 Workstation GC – Non Concurrent。

    【讨论】:

      【解决方案3】:

      使用Thread.SpinWait(int.MaxValue)之类的东西来产生CPU负载,因为你的程序主要产生内存负载,这可能会导致虚假共享等效果。正如 CodeInChaos 已经说过的,GC 活动也很可能会影响性能。

      【讨论】:

        【解决方案4】:

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

        GC 线程可能会减慢处理速度有几个原因。

        其中一个原因是,一旦 VM 内存不足,大多数线程将不得不暂停并等待 GC 释放内存,然后才能继续(这是因为所有线程都会在执行期间大致相同的时间)。

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

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

        【讨论】:

          猜你喜欢
          • 2011-08-14
          • 1970-01-01
          • 2021-05-07
          • 2016-10-29
          • 2017-01-23
          • 1970-01-01
          • 2017-09-24
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多