Я пытаюсь написать программу, в которой я планирую элементы для удаления, помещая их в коллекцию из разных потоков и очищая их в одном потоке, который выполняет итерацию коллекции и размещает элементы.
Прежде чем делать это, я задавался вопросом, что даст оптимальную производительность, поэтому я пробовал 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
поэтому, независимо от того, сколько потоков, кажется, что просто блокировка простого старого списка быстрее, чем использование любой из параллельных коллекций, за исключением, может быть, очереди, если ему нужно обрабатывать много записей.
EDIT: после комментариев ниже о сборке мусора и отладки: Да, это влияет на бенчмарк. Влияние отладки сборки будет линейным, Garbage будет увеличиваться с увеличением использования памяти.
Однако выполнение одного и того же теста несколько раз дает примерно одинаковые результаты.
Я перенес инициализацию коллекции прямо перед тестовым запуском и собираю мусор после запуска сейчас, например:
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 секунды каждый раз, когда я запускаю код.