在这样做之前,我想知道什么会产生最佳性能,所以我尝试了ConcurrentBag,ConcurrentStack和ConcurrentQueue,并测量了添加10000000个项目所需的时间.
我使用以下程序来测试:
class Program { static List<int> list = new List<int>(); static ConcurrentBag<int> bag = new ConcurrentBag<int>(); static ConcurrentStack<int> stack = new ConcurrentStack<int>(); static ConcurrentQueue<int> queue = new ConcurrentQueue<int>(); static void Main(string[] args) { run(addList); run(addBag); run(addStack); run(addQueue); Console.ReadLine(); } private static void addList(int obj) { lock (list) { list.Add(obj); } } private static void addStack(int obj) { stack.Push(obj); } private static void addQueue(int obj) { queue.Enqueue(obj); } private static void addBag(int obj) { bag.Add(obj); } private static void run(Action<int> action) { Stopwatch stopwatch = Stopwatch.StartNew(); Parallel.For(0,10000000,new ParallelOptions() { MaxDegreeOfParallelism = # },action); stopwatch.Stop(); Console.WriteLine(action.Method.Name + " takes " + stopwatch.Elapsed); } }
其中#是使用的线程数.
但结果相当混乱:
有8个线程:
> addList需要00:00:00.8166816
> addBag需要00:00:01.0368712
> addStack需要00:00:01.0902852
> addQueue需要00:00:00.6555039
有1个线程:
> addList需要00:00:00.3880958
> addBag需要00:00:01.5850249
> addStack需要00:00:01.2764924
> addQueue需要00:00:00.4409501
所以,无论有多少线程,似乎只要锁定一个普通的旧列表就会更快,然后使用任何并发集合,除了队列,如果它需要处理大量的写入.
编辑:下面关于垃圾和调试的建议:
是的,这会影响基准.调试构建的影响将是线性的,垃圾将随着内存使用量的增加而增加.
然而,多次运行相同的测试大致相同的结果.
在运行测试之前,我将集合的初始化移动到正确的位置,并在运行后收集垃圾,如下所示:
list = new List<int>(); run(addList); list = null; GC.Collect();
与MaxDegreeOfParallelism设置为8我得到以下结果:
> addList需要00:00:7959546
> addBag需要00:00:01.08023823
> addStack需要00:00:01.1354566
> addQueue需要00:00:00.6597145
每次运行代码时给予或取0.02秒偏差.
解决方法
http://blogs.msdn.com/b/pfxteam/archive/2010/04/26/9997562.aspx
当心过早优化虽然把东西放在一起,然后进行优化.特别是因为实际工作量很重要.还有,作为一个perf瓶颈的锁是漂亮的,通常有一些io或其他算法需要更长的时间:)