我有一个简单的程序,它启动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);
}
}
}
}
答案 0 :(得分:6)
你的测试看起来很重。如果在一个进程中有16个线程,则GC将在该进程中运行更多,并且由于客户端GC不是并行的,因此会导致负载降低。即每个GC线程有16个垃圾生成线程。
另一方面,如果你运行8个进程,每个进程有两个线程,你只得到两个线程为每个GC线程产生垃圾,GC可以在这些进程之间并行工作。
如果您编写的测试产生的垃圾较少,并且直接使用更多CPU,则可能会得到不同的结果。
(请注意,这只是推测,我没有运行您的测试,因为我只有一个与您的结果不同的双核CPU)
答案 1 :(得分:4)
要考虑的其他事情是垃圾收集器有不同的模式:
您可以找到每个here的一些图形细节。
由于您的进程使用大量线程并分配了大量内存,因此您应该尝试使用服务器GC。
服务器GC针对高吞吐量和高可扩展性进行了优化 服务器应用程序,其中存在一致的负载和请求 以高速率分配和释放内存。服务器GC使用 每个处理器一个堆和一个GC线程,并尝试平衡 尽可能地堆积。在垃圾收集时,GC 线程在它们各自的线程上工作,并且肯定会更新 点。因为它们都在自己的堆上工作,最小的锁定等。 需要这使得它在这种情况下非常有效。
在App.config中启用Server CG:
<configuration>
<runtime>
<gcServer enabled="true" />
</runtime>
</configuration>
请注意,这仅适用于多处理器(或核心)系统。如果Windows只报告一个处理器,那么您将获得Workstation GC - 非并发。
答案 2 :(得分:1)
使用像Thread.SpinWait(int.MaxValue)
之类的东西来产生CPU负载,因为你的程序主要产生内存负载,这可能会导致错误共享等效果。正如CodeInChaos已经指出的那样,GC活动也很可能会影响性能。
答案 3 :(得分:1)
与其他人一样,我怀疑这与GC有关。加载示例使用巨大内存量,在两个for循环结束时,StringBuilder对象将要求使用千兆字节大小的数组来存储其数据。
GC线程可能会降低处理速度,这有几个原因。
一个是,一旦VM耗尽内存,大多数线程将不得不暂停并等待GC释放内存,然后才能继续(这是因为所有线程都会要求更多内存)执行期间大致相同的时间。)
第二个是线程的上下文切换(这可能是最大的原因)。如果在核心X上运行的线程A用完了内存,则GC必须被加载到核心X或者将核心X的高速缓存中的所有线程A的内存加载到它正在运行的核心上的高速缓存中。无论哪种方式,CPU都必须等待其缓存从RAM加载内存。 RAM与硬盘驱动器相比速度很快,但与CPU相比,它的速度非常慢。当CPU等待RAM响应时,它无法进行任何处理,从而减少了负载。
当您有多个虚拟机时,每个虚拟机都可以在自己的核心上运行,而不关心其他虚拟机的用途。当调用GC时,不需要上下文切换,因为GC可以在与VM上其他两个线程相同的内核上运行。