我正在尝试编写一个程序,通过将项目放在来自不同线程的集合中并在一个迭代集合并处理项目的单个线程中清理它们来安排要删除的项目。
在这样做之前,我想知道什么会产生最佳性能,所以我尝试了 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 秒的偏差。