Yar*_*veh 4 .net concurrency performance multithreading scalability
我正在编写一个客户端.NET应用程序,预计会使用很多线程.我被警告说,在并发性方面.NET性能非常糟糕.虽然我不是在编写实时应用程序,但我想确保我的应用程序是可伸缩的(即允许多个线程),并且在某种程度上可以与同等的C++应用程序相媲美.
你有什么经历?什么是相关基准?
Aar*_*ght 12
我使用一个素数发生器作为测试,在C#中汇总了一个快速而肮脏的基准.该测试使用简单的Eratosthenes Sieve实现生成质数达到常数限制(我选择500000)并重复测试800次,并使用.NET ThreadPool或独立线程在特定数量的线程上并行化.
测试在运行Windows Vista(x64)的四核Q6600上运行.这不是使用任务并行库,只是简单的线程.它针对以下场景运行:
ThreadPoolThreadPool(测试池本身的效率)结果是:
Test | Threads | ThreadPool | Time
-----+---------+------------+--------
1 | 1 | False | 00:00:17.9508817
2 | 4 | True | 00:00:05.1382026
3 | 40 | True | 00:00:05.3699521
4 | 4 | False | 00:00:05.2591492
5 | 40 | False | 00:00:05.0976274
Run Code Online (Sandbox Code Playgroud)
结论可以从中得出:
并行化并不完美(正如预期的那样 - 无论环境如何都是如此),但是将负载分成4个核心会导致吞吐量增加3.5倍,这几乎不值得抱怨.
使用4到40个线程之间的差异可以忽略不计ThreadPool,这意味着即使你用请求轰炸它,也不会对池产生大量费用.
ThreadPool和自由线程版本之间的差异可以忽略不计,这意味着ThreadPool它没有任何重大的"常数"费用;
4线程和40线程自由线程版本之间的差异可以忽略不计,这意味着.NET的执行速度不会超过人们对大量上下文切换的预期.
我们甚至需要一个C++基准来比较吗?结果非常清楚:.NET中的线程并不慢.除非你,程序员,编写糟糕的多线程代码并最终导致资源匮乏或锁定车队,否则你真的不必担心.
使用.NET 4.0和TPL以及对ThreadPool工作窃取队列和所有那些很酷的东西的改进,你有更多的余地来编写"有问题"的代码并且仍然可以高效运行.你根本没有从C++中获得这些功能.
供参考,这是测试代码:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Threading;
namespace ThreadingTest
{
class Program
{
private static int PrimeMax = 500000;
private static int TestRunCount = 800;
static void Main(string[] args)
{
Console.WriteLine("Test | Threads | ThreadPool | Time");
Console.WriteLine("-----+---------+------------+--------");
RunTest(1, 1, false);
RunTest(2, 4, true);
RunTest(3, 40, true);
RunTest(4, 4, false);
RunTest(5, 40, false);
Console.WriteLine("Done!");
Console.ReadLine();
}
static void RunTest(int sequence, int threadCount, bool useThreadPool)
{
TimeSpan duration = Time(() => GeneratePrimes(threadCount, useThreadPool));
Console.WriteLine("{0} | {1} | {2} | {3}",
sequence.ToString().PadRight(4),
threadCount.ToString().PadRight(7),
useThreadPool.ToString().PadRight(10),
duration);
}
static TimeSpan Time(Action action)
{
Stopwatch sw = new Stopwatch();
sw.Start();
action();
sw.Stop();
return sw.Elapsed;
}
static void GeneratePrimes(int threadCount, bool useThreadPool)
{
if (threadCount == 1)
{
TestPrimes(TestRunCount);
return;
}
int testsPerThread = TestRunCount / threadCount;
int remaining = threadCount;
using (ManualResetEvent finishedEvent = new ManualResetEvent(false))
{
for (int i = 0; i < threadCount; i++)
{
Action testAction = () =>
{
TestPrimes(testsPerThread);
if (Interlocked.Decrement(ref remaining) == 0)
{
finishedEvent.Set();
}
};
if (useThreadPool)
{
ThreadPool.QueueUserWorkItem(s => testAction());
}
else
{
ThreadStart ts = new ThreadStart(testAction);
Thread th = new Thread(ts);
th.Start();
}
}
finishedEvent.WaitOne();
}
}
[MethodImpl(MethodImplOptions.NoOptimization)]
static void IteratePrimes(IEnumerable<int> primes)
{
int count = 0;
foreach (int prime in primes) { count++; }
}
static void TestPrimes(int testRuns)
{
for (int t = 0; t < testRuns; t++)
{
var primes = Primes.GenerateUpTo(PrimeMax);
IteratePrimes(primes);
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
这里是素数发生器:
using System;
using System.Collections.Generic;
using System.Linq;
namespace ThreadingTest
{
public class Primes
{
public static IEnumerable<int> GenerateUpTo(int maxValue)
{
if (maxValue < 2)
return Enumerable.Empty<int>();
bool[] primes = new bool[maxValue + 1];
for (int i = 2; i <= maxValue; i++)
primes[i] = true;
for (int i = 2; i < Math.Sqrt(maxValue + 1) + 1; i++)
{
if (primes[i])
{
for (int j = i * i; j <= maxValue; j += i)
primes[j] = false;
}
}
return Enumerable.Range(2, maxValue - 1).Where(i => primes[i]);
}
}
}
Run Code Online (Sandbox Code Playgroud)
如果您在测试中发现任何明显的缺陷,请告诉我.除非测试本身出现任何严重问题,我认为结果不言自明,而且信息很明确:
不要聆听那些对.NET或任何其他语言/环境的性能如何在某个特定领域"糟糕"做出过于宽泛和无条件声明的人,因为他们可能正在谈论他们的......后端.
小智 9
您可能希望了解System.Threading.Tasks.NET 4 中介绍的内容.
他们介绍了一种可扩展的方式,将线程与任务结合使用,并采用了一些非常酷的作
顺便说一下,我不知道是谁告诉你.NET并不适合并发.我的所有应用程序确实在另一个应用程序的某个位置使用线程,但不要忘记在2核处理器上有10个线程会产生相反的效果(取决于你正在进行的任务的类型.如果它的任务是等待网络资源然后它可能有意义).
无论如何,不要害怕.NET的性能,它实际上非常好.
这是一个神话..NET在管理并发性方面做得非常好,并且具有很高的可扩展性.
如果可以,我建议使用.NET 4和任务并行库.它简化了许多并发问题.有关详细信息,我建议您查看带有托管代码的并行计算的MSDN中心.
如果您对实现的细节感兴趣,我还有一个关于.NET中Parallelism的非常详细的系列文章.