2015-04-30 4 views
34

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

Итак, я сделал небольшую консольную программу для собственного понимания объекта потоковой передачи в Visual Studio 2013. Мой процессор - это Intel Core i7, который может использовать несколько потоков.

Мой код:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks; 
using System.Threading; 
using System.Diagnostics; 

namespace ConsoleApplication1 
{ 
    class Program 
    { 

     static TimeSpan MTTime; 
     static TimeSpan STTime; 

     static void Main(string[] args) 
     { 
      Stopwatch stopwatch = new Stopwatch(); 
      stopwatch.Start(); 


      Console.WriteLine(Environment.NewLine + "---------------Multi Process-------------" + Environment.NewLine); 

      Thread th1 = new Thread(new ParameterizedThreadStart(Process)); 
      Thread th2 = new Thread(new ParameterizedThreadStart(Process)); 
      Thread th3 = new Thread(new ParameterizedThreadStart(Process)); 
      Thread th4 = new Thread(new ParameterizedThreadStart(Process)); 

      th1.Start("A"); 
      th2.Start("B"); 
      th3.Start("C"); 
      th4.Start("D"); 

      th1.Join(); 
      th2.Join(); 
      th3.Join(); 
      th4.Join(); 

      stopwatch.Stop(); 
      MTTime = stopwatch.Elapsed ; 

      Console.WriteLine(Environment.NewLine + "---------------Single Process-------------" + Environment.NewLine); 


      stopwatch.Reset(); 
      stopwatch.Start(); 

      Process("A"); 
      Process("B"); 
      Process("C"); 
      Process("D"); 

      stopwatch.Stop(); 
      STTime = stopwatch.Elapsed; 

      Console.Write(Environment.NewLine + Environment.NewLine + "Multi : "+ MTTime + Environment.NewLine + "Single : " + STTime); 


      Console.ReadKey(); 
     } 

     static void Process(object procName) 
     { 
      for (int i = 0; i < 100; i++) 
      { 
       Console.Write(procName); 
      } 
     } 
    } 
} 

изображение Результат:

enter image description here

Мы ясно видим, что многоступенчатый процесс протекторов тотально случайны и единственный один раз сделать все давит на после другие, но я не думаю, что это влияет на скорость.

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

+31

Письмо на консоль - ужасный тест. Попробуйте вычислить пару тысяч криптографических хэшей в цикле «Процесс» (с одной записью на консоль в конце), и вы увидите ускорение. – bstenzel

+3

просто для того, чтобы добавить к действительно хорошему ответу: вам редко удастся использовать Threads - скорее, используйте API более высокого уровня из theTPL, например Tasks, или еще лучше PLinq и co. (или если вы действительно знаете, что вы делаете, продолжайте, но не обижайтесь, но в этом случае вам может понадобиться вначале немного поработать) – Carsten

+0

Запустите Visual Studio Profiler, и вы увидите, где приложение неэффективно. (Подсказка: 'Console') – Anders

ответ

45

Из официальной документации Console

операций ввода/вывода, которые используют эти потоки синхронизированы, что означает, что несколько потоков могут читать из или записывать в, потоки. Это означает, что методы, которые обычно асинхронные, такие как TextReader.ReadLineAsync, выполнять синхронно, если объект представляет собой поток консоли

Это означает, что класс консоли обрабатывает синхронизацию потоков так что если нить А и нить B пытается написать на консоль, консоль будет обрабатывать их, и только один для времени сможет писать. Логика обработка за ней причина, почему это занимает больше времени


UPDATE
Я предлагаю вам взглянуть на Parallel.ForEach

+0

Хорошо, так что, если я понимаю, это причина того, что консоль обрабатывает резьбу? поэтому, я думаю, если бы я сделал что-то еще, а потом писал или читал вещи в консоли, это сработало бы? правильно ? позвольте мне попробовать это, но я не знаю, что может быть хорошим испытанием thx много ура, который меня продвигает! : D – Kazimar

+0

@ Kazimar Вы должны использовать многопоточность, когда у вас много данных для обработки с некоторыми alghorthim или тому подобное, я также предлагаю вам взглянуть на Parallel.ForEach, я обновлю свой ответ. Посмотрите, и если вы считаете мой ответ тем, что вы искали метку, как ответили! – Sid

+0

Другим способом описать это было бы сказать, что, поскольку консольная запись должна быть потокобезопасной, это сериализованный ресурс, и потоки тратят много времени, ожидая своей очереди. Если бы это были записи на диске, вы могли бы буферизировать их шесть способов с воскресенья. – mckenzm

73

Обратите внимание, что Process записывает на консоль (и в основном ничего не делает), и вывод на консоль (который здесь действует как своего рода общий ресурс) медленный и нуждается в синхронизации с другими потоками.

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

+29

Точно. Для OP: если вы хотите иметь достоверное сравнение однопоточных и многопоточных реализаций алгоритма, вы должны убедиться, что эти реализации могут выполняться полностью независимо друг от друга. И, в частности, они не могут использовать общие ресурсы, поскольку это заставляет отдельные потоки ждать друг друга. –

5

Еще одна вещь, чтобы принять во внимание: вы страдают от накладных расходов для создания потоков и объединения. Кроме того, вы можете получить небольшой прирост производительности с помощью ThreadPool, посмотрите здесь:

https://msdn.microsoft.com/en-us/library/system.threading.threadpool.queueuserworkitem%28v=vs.110%29.aspx

5

OK! thx в Assa и Codor, чтобы поместить мой разум в нужное место!Я, наконец, создаю небольшую консольную программу, которая показывает, что все очень ясно. Заключительная многозадачность намного быстрее, когда используется тяжелая обработка. Просто прочитайте мой код, и вы легко поймете.

Результат:

enter image description here

Код:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks; 
using System.Threading; 
using System.Diagnostics; 

namespace ConsoleApplication1 
{ 
    class Program 
    { 
     //Timer for speed guidance 
     static Stopwatch stopwatch; 
     //Data i use for generate time 
     static List<int> timeData; 
     static void Main(string[] args) 
     { 
      stopwatch = new Stopwatch(); 
      timeData = new List<int> { 1000, 800, 200, 700, 600, 300, 800, 100, 200, 300, 655, 856, 695, 425 }; 

      ////-------------------------- SINGLE THREAD ------------------------------///// 
      Console.WriteLine("-------------------------------------------------"); 
      Console.WriteLine("    Single Threading Process   "); 
      Console.WriteLine("-------------------------------------------------"); 
      Console.WriteLine(" Process Time  Thread ID     "); 
      Console.WriteLine("-------------------------------------------------"); 
      stopwatch.Reset(); 
      stopwatch.Start(); 
      //For each normal that use only 1 thread 
      foreach(int i in timeData) 
      { 
       Process(i); 
      } 

      stopwatch.Stop(); 
      //Total time that the program take for making the process happen 
      Console.WriteLine("*Total : " + stopwatch.Elapsed); 

      ////-------------------------- Mulit Multiple ------------------------------///// 

      Console.WriteLine("-------------------------------------------------"); 
      Console.WriteLine("-------------------------------------------------"); 
      Console.WriteLine("    Multi Threading Process   "); 
      Console.WriteLine("-------------------------------------------------"); 
      Console.WriteLine(" Process Time  Thread ID     "); 
      Console.WriteLine("-------------------------------------------------"); 
      stopwatch.Reset(); 
      stopwatch.Start(); 
      //for each thats use Multiple thread fr the process (can be made with parallel.invoke or Task Library or Thread Library) 
      Parallel.ForEach(timeData, (i) => Process(i)); 
      //Total time that the program take for making the process happen 
      Console.WriteLine("*Total : " + stopwatch.Elapsed); 
      Console.WriteLine("-------------------------------------------------"); 
      Console.ReadKey(); 
     } 

     // Methode for sumulating long processing 
     static void Process(int time) 
     { 
      stopwatch.Reset(); 
      stopwatch.Start(); 
      //sleep time simulate the IO portion of the process 
      Thread.Sleep(time); 
      // The loop simulate de algoritme type of precessing 
      for (int i = 0; i < time*1000000; i++){} 
      stopwatch.Stop(); 
      Console.WriteLine(stopwatch.Elapsed + "   " + Thread.CurrentThread.ManagedThreadId.ToString());   
     } 


    } 
} 
+0

Это лучшее «демо» многопоточности. Однако обратите внимание, что все ваши потоки используют один и тот же объект секундомера. Если вы хотите, чтобы каждый поток замерял свое собственное время выполнения, вы должны объявить новую переменную StopWatch в Process (int). –

+0

Ваш секундомер показывает только разницу между последним стартом и первой остановкой. Вот почему у вас много одинаковых чисел. При поточной обработке каждый отдельный процесс не будет быстрее. Это комбинация нескольких процессов. –

+0

hummm im not shure, что означает u?может у вас развиться больше плз :) ты говоришь, что мой старт/стоп-часы не в том месте в моей петле? потому что моя мультитрещина еще быстрее, если вы видите комбинацию. Или ур просто сказал факт о том, что я хочу, и мое кодирование в порядке? – Kazimar

1

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

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

Если вы хотите, чтобы ваш код работал быстрее, вы должны сначала проверить, что заставляет его работать медленно. Профилируйте свой код. Найдите узкое место.

И помните, что Premature optimization is the root of all evil

В вашем случае узкое место - единственный наиболее доступен ресурс, который блокирует все действия - это консольный вывод потока. Он имеет один экземпляр и работает относительно медленно. Даже одно ядро ​​не будет использоваться на 100% для печати на нем как можно быстрее.

+0

Да, я окончательно заметил со всем моим тестом! и если я запустил какой-то небольшой процесс, то многопоточный материал будет действовать медленнее только из-за процесса, который он генерирует сам для создания объекта с несколькими проходами и каждого. В большинстве случаев это бесполезно .... если ур не делает вещи, которые используют действительно много многопоточность ... как сеть и т. д. или большой алгоритмический материал. – Kazimar

+0

Ну, не совсем ответ на вопрос, но я ищу причины, почему мое приложение работает медленно, и это заставило меня подумать, так что +1. –

Смежные вопросы