Как стать автором
Обновить

.Net: Затраты на многопоточность

Чулан
Недавно получил простую задачу: написать windows-сервис для обработки пользовательских запросов. Вопрос про то, какие эти запросы и по какому протоколу работает сервис, выходит за рамки этой статьи. Более интересным мне показался другой фактор, многопоточную ли делать обработку запросов. С одной стороны — последовательное выполнение тормозит процес обработки информации. С другой стороны могут быть не оправданы затраты на создание и запуск потока.
Итак, исходные данные: 20 простейших запросов в секунду (1200 запросов в минуту) в пиковое время. Тестовый «сервер»: Celeron, 3ГЦ, 1Гб (свободно 70%).

Однопоточная система


Сначала напишем класс-базу для однопоточного выполнения запросов.
  1. using System;
  2. using System.Diagnostics;
  3. using System.Threading;
  4.  
  5. namespace TestConsoleApplication
  6. {
  7.  
  8.   class mockClass
  9.   {
  10.     private readonly Int32 incriment_speed;
  11.     private Int32 inc;
  12.  
  13.     public mockClass(int incriment_speed)
  14.     {
  15.       this.incriment_speed = incriment_speed;
  16.       inc = 0;      
  17.     }
  18.  
  19.     public Int32 incriment()
  20.     {
  21.       Thread.Sleep(incriment_speed);
  22.       return inc++;
  23.     }
  24.  
  25.     public Int32 getIncriment()
  26.     {
  27.       return inc;
  28.     }
  29.  
  30.   }
  31.  
  32.   class TestConsoleApplication
  33.   {    
  34.  
  35.     static void Main(string[] args)
  36.     {
  37.       if (args.Length<1) return;
  38.  
  39.       Int32 mockSpeed = 0;
  40.       if (!Int32.TryParse(args[0], out mockSpeed)) return;
  41.       var mock = new mockClass(mockSpeed);
  42.  
  43.       int beginTick = Environment.TickCount;
  44.       for (int j = 0; j < 1200; j++)
  45.       {
  46.         mock.incriment();
  47.       }
  48.       int endTick = Environment.TickCount;
  49.  
  50.       var performance = new PerformanceCounter("Process", "Private Bytes", Process.GetCurrentProcess().ProcessName);
  51.       Console.WriteLine(mock.getIncriment());
  52.       Console.WriteLine("tick: {0}", endTick - beginTick);
  53.       Console.WriteLine("memory: {0:N0}K", (performance.RawValue/1024));
  54.       Console.ReadLine();
  55.     }
  56.   }
  57. }
* This source code was highlighted with Source Code Highlighter.

Запустим программу с несколькими параметрами задержки выполнения запроса: 2, 5, 10
2 5 10
tick memory tick memory tick memory
3688 10 792K 7281 10 780K 13125 10 792K

Как видим память практически не страдает, а время примерно равно (mockSpeed+1)*1200. Дополнительную миллисекунду спишем на накладные расходы.

Многопоточная система


Перепишем программу для работы с многопоточностью, оптимизируем ее и сверим результаты:
  1. using System;
  2. using System.Diagnostics;
  3. using System.Threading;
  4.  
  5. namespace TestConsoleApplication
  6. {
  7.  
  8.   class mockClass
  9.   {
  10.     private readonly Int32 incriment_speed;
  11.     private Int32 inc;
  12.  
  13.     public mockClass(int incriment_speed)
  14.     {
  15.       this.incriment_speed = incriment_speed;
  16.       inc = 0;      
  17.     }
  18.  
  19.     public Int32 incriment()
  20.     {
  21.       Thread.Sleep(incriment_speed);
  22.       return inc++;
  23.     }
  24.  
  25.     public Int32 getIncriment()
  26.     {
  27.       return inc;
  28.     }
  29.  
  30.   }
  31.  
  32.   class TestConsoleApplication
  33.   {    
  34.     private static mockClass mock = null;
  35.  
  36.     static void threadmethod()
  37.     {
  38.       lock (mock)
  39.       {
  40.         mock.incriment(); 
  41.       }      
  42.     }
  43.  
  44.     static void Main(string[] args)
  45.     {
  46.       if (args.Length<1) return;
  47.  
  48.       Int32 mockSpeed = 0;
  49.       if (!Int32.TryParse(args[0], out mockSpeed)) return;
  50.       mock = new mockClass(mockSpeed);
  51.  
  52.       var performance = new PerformanceCounter("Process", "Private Bytes", Process.GetCurrentProcess().ProcessName);
  53.       long performance_RawValue = 0;
  54.       int beginTick = Environment.TickCount;
  55.       lock (mock)
  56.       {
  57.         for (int j = 0; j < 1200; j++)
  58.         {
  59.           var trd = new Thread(threadmethod, 65536); //выделяем 1 страницу под стек
  60.           trd.Start();
  61.         }
  62.         performance_RawValue = performance.RawValue;
  63.       }
  64.       int end1Tick = Environment.TickCount;
  65.       while(mock.getIncriment()<1200)
  66.       {
  67.         Thread.Sleep(2);
  68.       }
  69.       int end2Tick = Environment.TickCount;
  70.       
  71.       Console.WriteLine("starttick: {0}", end1Tick - beginTick);
  72.       Console.WriteLine("alltick: {0}", end2Tick - beginTick);
  73.       Console.WriteLine("memory: {0:N0}K", (performance_RawValue / 1024));
  74.       Console.ReadLine();
  75.     }
  76.   }
  77. }
* This source code was highlighted with Source Code Highlighter.


- 2 5 10
- start tick all tick memory start tick all tick memory start tick all tick memory
Однопоточная - 3688 10 792K - 7281 10 780K - 13125 10 792K
Многопоточная 656 4234 323 508K 625 7719 323 508K 750 13735 323 508K

При тестировании производительности многопоточности появилась новая величина время запуска процессов. Именно на эту величину вырастает общая продолжительность выполнения программы. Примерный запуск процесса равен 0,5миллисекундам. Также мы видим, значительно выросший объем используемой памяти, которая расходуется на стек запускаемых потоков.

Итоги


Выделим все сравниваемые величины в таблицу.
- Однопоточная Многопоточная
Общее время Общее время основного потока зависит от времени выполнения всех запросов Время работы основного потока зависит только от количества запросов
Общее процессорное время Низкие паразитные нагрузки Паразитные нагрузки в 2 раза выше
Память Невысокие запросы к памяти, независящие от количества запросов На каждый запрос расходуется не менее 256Кб памяти на стек потока


Вот такая «студенческая лабораторная работа» вышла при изучении такого вопроса. Прошу не кидать камни :)
Теги:
Хабы:
Всего голосов 22: ↑16 и ↓6 +10
Просмотры 587
Комментарии Комментарии 18