Korzystam z systemu Windows 10 (1607) na procesorze Intel Xeon E3-1231v3 (Haswell, 4 rdzenie fizyczne, 8 rdzeni logicznych ).
Kiedy po raz pierwszy zainstalowałem system Windows 7 na tym komputerze, zauważyłem, że cztery z ośmiu rdzeni logicznych były zaparkowane, dopóki aplikacja nie wymagała więcej niż 4 wątków. Za pomocą monitora zasobów Windows można sprawdzić, czy rdzenie są zaparkowane, czy nie ( przykład ). O ile rozumiem, jest to ważna technika utrzymywania równowagi wątków w rdzeniach fizycznych, jak wyjaśniono na stronie internetowej Microsoft : „ Algorytm Core Parking i infrastruktura są również wykorzystywane do równoważenia wydajności procesorów między procesorami logicznymi w systemach klienckich Windows 7 z procesory, które zawierają technologię Intel Hyper-Threading ”.
Jednak po uaktualnieniu do systemu Windows 10 zauważyłem, że nie ma parkingu podstawowego. Wszystkie rdzenie logiczne są aktywne przez cały czas, a po uruchomieniu aplikacji przy użyciu mniej niż czterech wątków można zobaczyć, jak program planujący równo rozdziela je na wszystkie logiczne rdzenie procesora. Pracownicy Microsoft potwierdzili, że Core Parking jest wyłączony w Windows 10 .
Ale zastanawiam się dlaczego? Jaki był tego powód? Czy jest zamiennik, a jeśli tak, jak to wygląda? Czy Microsoft wdrożył nową strategię planowania, która sprawiła, że parkowanie rdzeni stało się przestarzałe?
Dodatek:
Oto przykład, w jaki sposób Core Core Parking wprowadzony w Windows 7 może poprawić wydajność (w porównaniu do Visty, która nie miała jeszcze funkcji Core Parking). Widać, że w systemie Vista HT (Hyper Threading) szkodzi wydajności, podczas gdy w systemie Windows 7 nie:
( źródło )
Próbowałem włączyć Core Parking, jak wspomniano tutaj , ale zauważyłem, że algorytm Core Parking nie jest już świadomy Hyper Threading. Zaparkował rdzenie 4,5,6,7, podczas gdy powinien mieć zaparkowany rdzeń 1,3,5,7, aby uniknąć przypisania wątków do tego samego rdzenia fizycznego. Windows wylicza rdzenie w taki sposób, że dwa kolejne wskaźniki należą do tego samego rdzenia fizycznego. Bardzo dziwny. Wygląda na to, że Microsoft całkowicie to popsuł. I nikt nie zauważył ...
Ponadto wykonałem testy wydajności procesora, używając dokładnie 4 wątków.
Powinowactwo procesora ustawione na wszystkie rdzenie (Windows defualt):
Średni czas pracy: 17.094498, odchylenie standardowe: 2.472625
Powinowactwo procesora ustawione na każdym innym rdzeniu (aby działało na różnych rdzeniach fizycznych, najlepsze możliwe planowanie):
Średni czas pracy: 15.014045, odchylenie standardowe: 1.302473
Powinowactwo procesora ustawione na najgorsze możliwe planowanie (cztery rdzenie logiczne na dwóch rdzeniach fizycznych):
Średni czas pracy: 20,811493, odchylenie standardowe: 1,405621
Więc nie jest różnica wydajności. I możesz zobaczyć, że planowanie deficytu w systemie Windows plasuje się pomiędzy najlepszym i najgorszym możliwym, ponieważ spodziewalibyśmy się, że stanie się tak w przypadku harmonogramu świadomego bez hipertekstu. Jednak, jak wskazano w komentarzach, mogą być za to odpowiedzialne inne przyczyny, takie jak mniejsza liczba przełączeń kontekstu, wnioskowanie przez aplikacje monitorujące itp. Więc nadal nie mamy ostatecznej odpowiedzi.
Kod źródłowy mojego testu porównawczego:
#include <stdlib.h>
#include <Windows.h>
#include <math.h>
double runBenchmark(int num_cores) {
int size = 1000;
double** source = new double*[size];
for (int x = 0; x < size; x++) {
source[x] = new double[size];
}
double** target = new double*[size * 2];
for (int x = 0; x < size * 2; x++) {
target[x] = new double[size * 2];
}
#pragma omp parallel for num_threads(num_cores)
for (int x = 0; x < size; x++) {
for (int y = 0; y < size; y++) {
source[y][x] = rand();
}
}
#pragma omp parallel for num_threads(num_cores)
for (int x = 0; x < size-1; x++) {
for (int y = 0; y < size-1; y++) {
target[x * 2][y * 2] = 0.25 * (source[x][y] + source[x + 1][y] + source[x][y + 1] + source[x + 1][y + 1]);
}
}
double result = target[rand() % size][rand() % size];
for (int x = 0; x < size * 2; x++) delete[] target[x];
for (int x = 0; x < size; x++) delete[] source[x];
delete[] target;
delete[] source;
return result;
}
int main(int argc, char** argv)
{
int num_cores = 4;
system("pause"); // So we can set cpu affinity before the benchmark starts
const int iters = 1000;
double avgElapsedTime = 0.0;
double elapsedTimes[iters];
for (int i = 0; i < iters; i++) {
LARGE_INTEGER frequency;
LARGE_INTEGER t1, t2;
QueryPerformanceFrequency(&frequency);
QueryPerformanceCounter(&t1);
runBenchmark(num_cores);
QueryPerformanceCounter(&t2);
elapsedTimes[i] = (t2.QuadPart - t1.QuadPart) * 1000.0 / frequency.QuadPart;
avgElapsedTime += elapsedTimes[i];
}
avgElapsedTime = avgElapsedTime / iters;
double variance = 0;
for (int i = 0; i < iters; i++) {
variance += (elapsedTimes[i] - avgElapsedTime) * (elapsedTimes[i] - avgElapsedTime);
}
variance = sqrt(variance / iters);
printf("Average running time: %f, standard deviation: %f", avgElapsedTime, variance);
return 0;
}