Сравнение производительности между Thread и Task с использованием c # - PullRequest
0 голосов
/ 21 октября 2018

Я пытаюсь понять преимущества между обычными Thread с и Task с.Первый находится в System.Threading пространстве имен, а второй - в System.Threading.Tasks пространстве имен.Итак, просто для того, чтобы поиграть и познакомиться с ними, я написал эту программу в C#:

class Program
{
    static void Main(string[] args)
    {
        long ticksAtStart = DateTime.UtcNow.Ticks;

        new Thread(() => { ExecuteAsyn("Thread", DateTime.UtcNow.Ticks); }).Start();
        Console.WriteLine("Using Thread: " + (DateTime.UtcNow.Ticks - ticksAtStart));

        ticksAtStart = DateTime.UtcNow.Ticks;

        Task g = Task.Factory.StartNew(() => ExecuteAsyn("TPL", DateTime.UtcNow.Ticks));
        Console.WriteLine("Using TPL: " + (DateTime.UtcNow.Ticks - ticksAtStart));

        g.Wait();
        Console.ReadKey();
    }

    private static void ExecuteAsyn(string source, long ticksAtExecutionTime)
    {
        Console.WriteLine("Hello World! Using " + source + " the difference between initialization and execution is " + (DateTime.UtcNow.Ticks - ticksAtExecutionTime));
    }
}

Так что, исходя из моего понимания, Задачи должны быть более производительными, потому что они используют Thread, доступный в ThreadPool при создании и запуске нового потока может быть очень трудоемким.Программа регистрирует два события.Число тиков, которое CRL занимает два, создает два вида объектов, и тики между потоком и фактическим выполнением предоставленного делегата в моем случае ExecuteAsync.

Произошло то, чего я не ожидал:

Использование темы: 11372 Hello World!Используя Thread, разница между инициализацией и выполнением составляет 5482. Используя TPL: 333004 Hello World!При использовании TPL разница между инициализацией и выполнением составляет 0

Таким образом, кажется, что использование классических потоков намного более производительно, чем использование задач.Но для меня здесь есть что-то странное.Кто-нибудь может рассказать мне об этой теме?Спасибо.

1 Ответ

0 голосов
/ 21 октября 2018

Выполнение метода в первый раз всегда более затратно: сборки загружаются лениво, а метод еще может не быть JITted.

Например, если мы возьмем ваш тест (заменив DateTime на Stopwatch для точности)и еще раз позвоните Task.Factory.StartNew:

static void Main(string[] args)
{
    var sw = Stopwatch.StartNew();

    new Thread(() => { ExecuteAsyn("Thread", sw); }).Start();
    Console.WriteLine("Using Thread: " + sw.Elapsed);

    sw = Stopwatch.StartNew();

    Task g = Task.Factory.StartNew(() => ExecuteAsyn("TPL", sw));
    Console.WriteLine("Using TPL: " + sw.Elapsed);

    g.Wait();

    sw = Stopwatch.StartNew();

    g = Task.Factory.StartNew(() => ExecuteAsyn("TPL", sw));
    Console.WriteLine("Using TPL: " + sw.Elapsed);

    g.Wait();

    Console.ReadKey();
}

private static void ExecuteAsyn(string source, Stopwatch sw)
{
    Console.WriteLine("Hello World! Using " + source + " the difference between initialization and execution is " + (sw.Elapsed));
}

Результат на моем компьютере:

Использование темы: 00: 00: 00.0002071

Hello World!При использовании Thread разница между инициализацией и выполнением составляет 00: 00: 00.0004732

При использовании TPL: 00: 00: 00.0046301

Hello World!При использовании TPL разница между инициализацией и выполнением составляет 00: 00: 00.0048927

При использовании TPL: 00: 00: 00.0000027

Hello World!При использовании TPL разница между инициализацией и выполнением составляет 00: 00: 00.0001215

. Мы видим, что второй вызов выполняется быстрее, чем первый, на три порядка.

Использованиереальный тестовый фреймворк (например, BenchmarkDotNet), мы можем получить гораздо более надежные результаты:

  Method |       Mean |     Error |    StdDev |
-------- |-----------:|----------:|----------:|
 Threads | 137.426 us | 1.9170 us | 1.7932 us |
   Tasks |   2.384 us | 0.0322 us | 0.0301 us |

Тем не менее, несколько дополнительных замечаний:

  1. Ваше сравнениене честно.Вы сравниваете создание потока с постановкой задачи в пул потоков через API задач.Справедливости ради, вместо этого следует использовать ThreadPool.UnsafeQueueWorkItem (что позволяет использовать пул потоков без API задачи)

  2. Независимо от того, использовать ли Thread или Task, не должно зависеть от производительности.Это действительно больше вопрос удобства.Весьма маловероятно, что разрыв в производительности будет иметь какое-либо значение в вашем приложении, если вы не имеете дело с низкой задержкой или очень высокой пропускной способностью.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...