://blog. ypro.tech
Użycie procesorów logicznych przez uruchomione zadania
2021-10-16

Postanowiłem zbadać jak wykorzystywane są poszczególne rdzenie wielordzeniowego i wielowątkowego procesora przez aplikację napisaną w C# z użyciem różnej liczby uruchomionych przez nią zadań (Task). Do testów postanowiłem użyć bardzo prostej konsolowej aplikacji (źródło poniżej). Nie badałem wydajności poszczególnych procesorów. Moim celem było jedynie zaobserwowanie jak system przydziela zadania do poszczególnych rdzeni i w jakim stopniu są one obciążane.

using System;
using System.Threading;
using System.Threading.Tasks;

namespace MultiTaskTest
{
    class Program
    {
        static void Main(string[] args)
        {
            int n = (args.Length > 0 && Int32.TryParse(args[0], out int v)) ? v : 8;

            bool useConsole = args.Length > 1 && "C".Equals(args[1].ToUpper());

            Console.WriteLine($"Threads:{n}, use console:{useConsole}");

            for (int i = 0; i < n; i++)
            {
                var task = new Task(() => (new Tester()).Do(useConsole));
                task.Start();
            }

            Thread.Sleep(20000);
        }
    }

    internal class Tester
    {
        internal void Do(bool useConsole)
        {
            int a = 3;
            int b = 5;

            while (true)
            {
                a = a + b;
                b = b + a;

                if (a > 1000)
                {
                    a = 5;
                    b = 3;
                }

                if (useConsole)
                    Console.Write($"{a}-{b}|");
            }
        }
    }
}

Testy przeprowadziłem na trzech procesorach:

  • i5 10-tej generacji – 6 rdzeni, 12 wątków
  • i3 10-tej generacji – 2 rdzenie, 4 wątki
  • i5 7-mej generacji – 2 rdzenie, 4 wątki

Pierwszy test to wykonywanie zadań bez wyprowadzania wyników na konsolę (useConsole: false). Wyniki przedstawia poniższa tabela. Wyniki procentowe to ogólne wykorzystanie procesora.

liczba tasków i5 (10) i3 (10) i5 (7))
1 12% 73% 33%
2 24% 100% 62%
3 36% 100% 89%
4 47% 100% 100%
5 63%    
6 68%    
7 79%    
8 91%    
9 100%    
10 100%    
11 100%    
12 100%    

Drugi test to wykonywanie obliczeń i wyprowadzanie ich rezultatów na konsolę.

liczba tasków i5 (10) i3 (10) i5 (7))
1 15% 85% 44%
2 27% 100% 72%
3 38% 100% 96%
4 49% 100% 100%
5 60%    
6 71%    
7 82%    
8 94%    
9 100%    
10 100%    
11 100%    
12 100%    

Jak widać procentowe wykorzystanie procesora w obu przypadkach daje dość zbliżone wyniki. Jednak różnice pojawiają się gdy w rozkładzie obciążenia poszczególnych rdzeni. Pierwsze zestawienie: procesor sześciordzeniowy i program testowy z uruchomionym jednym taskiem. Wykres dla trybu bez wyprowadzania danych na konsolę:

oraz z ich wypisywaniem:

Drugie zestawienie to ten sam procesor i program testowy z sześcioma uruchomionymi taskami. Dla lepszego zobrazowania program testowy uruchomiony kilka razy z rzędu. Wykres w trybie bez wyprowadzania danych na konsolę:

oraz z ich wypisywaniem:

W przypadku wcześniejszej generacji procesora i5 z tylko dwoma rdzeniami wartości procentowe obciążenia kształtują się inaczej, ale ich charakter wygląda podobnie do powyżej przedstawionych. Wykres bez wykorzystania konsoli wygląda następująco:

a z ich wypisywaniem:

Ja widać użycie w toku wykonywanego algorytmu takich operacji jak np. operacje wejścia-wyjścia, znacznie zmienia rozkład zadań na poszczególne procesory logiczne. W jednolitym algorytmie, który działa w nieskończonej pętli i nie ma w nim naturalnych miejsc przełączenia zadań (jak operacje we/wy) system przerzuca wątki pomiędzy różnymi procesorami logicznymi. Natomiast w drugim przypadku przydział poszczególnych zadań jest bardziej jednolity, mniej jest przeskoków, a jednocześnie przydzielony procesor logiczny nie jest w pełni obciążony.

Na koniec jeszcze jedna refleksja. Poniżej wykres dla dwurdzeniowego i3 z uruchomionym programem testowym z dwoma taskami z wykorzystaniem konsoli.

W tym teście menedżer zadań pokazuje sumaryczne wykorzystanie procesora na poziomie 100%. Jednak widać, że ostatni procesor logiczny nie jest w pełni wykorzystany. Wynika z tego, że całkowite obciążenie procesora jest wyliczane w bardziej złożony sposób niż tylko średnia arytmetyczna wykorzystania poszczególnych rdzeni.