Почему такая разница в производительности между Thread и Task?
Windows 7, Intel CORE i3, 64 бит, оперативная память 4 ГБ, 2,27 ГГц
.NET Framework 4.0
У меня есть следующий код:
static void Main(string[] args)
{
var timer = new Stopwatch();
timer.Start();
for (int i = 0; i < 0xFFF; ++i)
{
// I use one of the following line at time
Task.Factory.StartNew(() => { });
new Thread(() => { }).Start();
}
timer.Stop();
Console.WriteLine(timer.Elapsed.TotalSeconds);
Console.ReadLine();
}
Если я использую Task, вывод всегда меньше 0.01 секунд, но если я использую Thread, вывод всегда будет больше 40 секунд!
Как это возможно? Почему так много разницы?
Ответы
Ответ 1
Оба не совпадают.
Когда вы используете Task.Factory.StartNew
, вы планируете выполнение задачи на ThreadPool
. Когда вы создаете новый Thread
, вам нужно создать и запустить новый поток.
В первом случае потоки уже созданы и повторно используются. Это приводит к тому, что накладные расходы на планирование задач намного ниже, так как нити не должны создаваться каждой итерацией.
Обратите внимание, что поведение не одно и то же. При создании отдельного потока каждая задача получает собственный поток. Они сразу начнут работать. При использовании Task.Factory.StartNew
они помещаются в планировщик для запуска в ThreadPool
, который (потенциально) ограничивает количество запущенных параллельных потоков. Это, как правило, хорошо, поскольку это предотвращает чрезмерное возникновение.
Ответ 2
Каждый раз, когда вы запускаете Task
, он переходит в пул, который должен обслуживаться несколькими потоками, многие из которых могут быть предварительно созданы. В пуле есть отношение M:N
задач к потокам.
Каждый раз, когда вы запускаете Thread
, он создает новый поток и все служебные данные, связанные с созданием потоков. Поскольку вы явно создаете поток, существует соотношение потоков 1:1.
Чем ближе отношение задач к потокам достигает 1, тем медленнее запускается задача. В действительности, ThreadPool
обеспечивает, чтобы соотношение оставалось намного выше 1.
Ответ 3
Task.Factory.StartNew()
не запускает задачу сразу, она просто планирует ее, поэтому TaskScheduled сможет запустить ее чуть позже (зависит от количества доступных потоков/задач).
MSDN заявляет, что после Thread.Start()
операционная система может запланировать ее выполнение, взаимодействие с ОС намного медленнее, чем с .NET Framework TaskScheduler, но не в такой степени.
И вернемся к вашему примеру, 0xFFF == 4095, поэтому вы планируете 4095 потоков, и это занимает 40 секунд. 102 нити в секунду - довольно неплохое время!:)
Ответ 4
Вызов Task.Factory.StartNew
не обязательно создает новый поток, они управляются с помощью TaskScheduler
на основе количества ядер и т.д. на компьютере работает код.
Если вы планируете (вызывая Task.Factory.StartNew
) больше задач, чем могут быть одновременно запущены, они будут поставлены в очередь и запущены по мере поступления большего количества ресурсов.
Ответ 5
У вас есть проблема с вашим тестом, поскольку вы не ждете завершения каждого потока/задачи.
Задача использует очередь, поэтому ее гораздо быстрее создать задачу, чем поток.
Готов поспорить, что даже если вы ждали завершения задач/потоков, то использование задачи выполняется быстрее. Накладные расходы на создание и уничтожение Thread очень высоки. Вот почему была создана Task.Factory!
Ответ 6
Создание новых потоков происходит медленно, но не так медленно.
Ник сообщил ~ 10 мс/нить.
Скорее всего, это произошло в отладчике Visual Studio.
Я получаю ~ 3.9ms за новый поток в отладчике Visual Studio.
Я получаю ~ 0.15ms за новый поток без отладчика.
http://dennisgorelik.livejournal.com/125269.html?thread=2238805#t2238805