C # задачи (с или без TaskContinuationOptions.ExecuteSynchronously) производительность по сравнению с циклом потока. Поток .net хуже? - PullRequest
0 голосов
/ 07 марта 2012

Я думал о лучшем методе работы при обработке очереди действий (порядок операций был важен, поэтому каждая операция должна следовать после предыдущей)

Я довольно разочарован результатами производительностизапуск задачи с параметрами продолжения, которые предполагают выполнение в том же потоке (я ожидал получить результаты, аналогичные тем, что выполняются в цикле) ... я был бы признателен за любые комментарии об этих результатах производительности, кстати - кажется, что, возможно, JITулучшена производительность, так что в некоторых случаях выполнение без какой-либо многопоточности лучше, чем с многопоточностью, в моем примере ниже вы можете видеть, что работа с размером массива 450 КБ в основном потоке была быстрее, чем в другом потоке, выполняющем цикл


Рассмотренные методы:

  • в простом цикле for для каждого действия (последовательный подход)
  • запуск метода Task.ContinueWith .этот метод выполняется в потоке пула потоков, и каждое другое действие также выполняется после его предыдущего (пул потоков решает, в каком потоке он работает).
  • запуск метода Task.ContinueWith с TaskContinuationOptions установлен в TaskContinuationOptions.ExecuteSynchronously, это вызывает выполнение всех задач в одном потоке
  • , работающих в отдельном потоке - новый System.Threading.Thread, работающий с циклом forв действии

Код метода:

Я создал простое приложение-тестер, которое запускает и массив, и устанавливает arr [i] = i* i где i: от 100 000 до 450 000 (скачки 50 000 между каждым тестом)

Результаты:

------------------------- Запуск теста с 100000 элементов и итераций ---------------------------

Простой для результатов: 24.0013 MS

Task.ContinueWith () результат: 691.0395 MS

Task.ContinueWith (TaskContinuationOptions.ExecuteSynchronously) результат: 91,0052 MS

Thread.Start результат: 16.0009 MS

{пропустить ... пропустить... пропустить - это слишком долго ...}

------------------------- Запуск теста с450000 пунктов и итераций ---------------------------

Простой для результатов: 16.0009 MS

Task.ContinueWith () результат: 3686.2108 MS

Task.ContinueWith (TaskContinuationOptions.ExecuteSynchronously) результат: 415.0238 MS

Thread.Start результат: 35,002 MS

Нажмите любую клавишу для выхода

Исходный код

static int max = 100000;
    static int[] array;
    static DateTime start;
    static int valueOfMax = 0;
    static void Main(string[] args)
    {

        for (valueOfMax = max; valueOfMax < max * 5; valueOfMax += (max/2))
        {
            Console.WriteLine(string.Format("------------------------- Running test with {0} items & iterations---------------------------", valueOfMax));
            array = new int[valueOfMax];
            start = DateTime.Now;
            Console.Write("Simple for results :                                                                 ");
            for (int i = 0; i < valueOfMax; i++)
            {
                doSomething(i);
            }

            start = DateTime.Now;
            Action<int> action = doSomething;
            Task lastTask = Task.Factory.StartNew(() => { int p = 4; });
            Console.Write("Task.ContinueWith() result :                                                         ");
            for (int i = 0; i < valueOfMax; i++)
            {
                var valueOfI = i;
                lastTask = lastTask.ContinueWith((task) => doSomething(valueOfI));
            }
            lastTask.Wait();

            start = DateTime.Now;
            lastTask = Task.Factory.StartNew(() => { int p = 4; });
            Console.Write("Task.ContinueWith(TaskContinuationOptions.ExecuteSynchronously) result :             ");
            for (int i = 0; i < valueOfMax; i++)
            {
                var valueOfI = i;
                lastTask = lastTask.ContinueWith((task) => doSomething(valueOfI), TaskContinuationOptions.ExecuteSynchronously);
            }
            lastTask.Wait();

            start = DateTime.Now;
            Thread t = new Thread(delegate()
            {
                for (int i = 0; i < valueOfMax; i++)
                {
                    doSomething(i);
                }
            });
            Console.Write("Thread.Start result :                                                                ");
            t.Start();
            t.Join();
        }

        Console.WriteLine("Press any key to exit");
        Console.ReadKey();
    }

    static void doSomething(int i)
    {
        array[i] = i * i;

        if ((i+1) == valueOfMax)
        {
            DateTime end = DateTime.Now;
            var diff = end - start;
            Console.WriteLine(string.Format("{0} MS", diff.TotalMilliseconds));
        }
    }

Ответы [ 2 ]

1 голос
/ 07 марта 2012

Во-первых, вы не должны использовать DateTime.Now для измерения производительности, это слишком неточно для этого.Вы должны использовать StopWatch вместо этого.В этом случае измерения сильно отличаются.

Во-вторых, когда вы вызываете метод в первый раз, он должен быть скомпилирован JIT, так что вы, вероятно, должны игнорировать результаты первого раунда.

В-третьих, вы должны запустить его в Release more без отладчика (Ctrl + F5, а не F5), если вы этого еще не сделали.

В-четвертых, не забывайте оGC, это может изменить ваши измерения непредсказуемым образом.

Теперь давайте подумаем о том, что вы собираетесь делать: если вы хотите запустить некоторый код в цикле, то просто запустите его в цикле.Простые циклы чрезвычайно эффективны, будет очень трудно получить что-либо с производительностью, близкой к этой.

А как насчет Task с?Я думаю, что нереально использовать их для таких простых операций.Если вы хотите многократно выполнять простую операцию быстро, вы должны сделать свой код как можно более простым, не включать в себя замыкания, выделения кучи, синхронизацию потоков и, кто знает, что еще, все это необходимо, если вы используете Task как вы..

В заключение, если у вас есть простая операция, которую вы хотите выполнять много раз, просто используйте простой цикл.Я не вижу причин использовать что-либо еще.С помощью цикла вы знаете, что компьютер будет выполнять ваш код и (почти) больше ничего.

Task s и ContinueWith() действительно имеют свое место, особенно если у вас есть более сложные потоки управления (например,имея какую-то задачу, которая что-то делает, затем две разные задачи, которые начинаются после того, как одна завершена, и затем другая задача, которая запускается после того, как они обе завершены).Или, если вы хотите сделать ваше приложение компонуемым.Но если вы попытаетесь использовать их вместо for цикла, не удивляйтесь, что результаты будут менее звездными.

0 голосов
/ 08 марта 2012

Вы говорите, что все предметы должны выполняться последовательно.Это означает, что максимум один процессор может быть занят.Таким образом, вы выполняете тот же объем работы, все еще на одном процессоре, но с дополнительными накладными расходами. Конечно , это медленнее.Я не уверен, чего вы ожидали.

Я думаю, что вы действительно хотите, чтобы выделенный поток обрабатывал ваши рабочие элементы и извлекал их из BlockingCollection.Вот очень хороший учебник: http://blogs.msdn.com/b/csharpfaq/archive/2010/08/12/blocking-collection-and-the-producer-consumer-problem.aspx

...