Como o planejador do Windows 10 lida com o Hyper Threading desde que o Core Parking esteja desabilitado por padrão para os processadores da Intel?

6

Estou executando o Windows 10 (1607) em uma CPU Intel Xeon E3-1231v3 (Haswell, 4 núcleos físicos, 8 ).

Quando instalei o Windows 7 pela primeira vez nessa máquina, pude observar que quatro dos oito núcleos lógicos estavam estacionados até que um aplicativo precisasse de mais de quatro threads. Pode-se verificar com o monitor de recursos do Windows se os núcleos estão estacionados ou não ( exemplo ). Tanto quanto eu entendo, esta é uma técnica importante para manter os segmentos equilibrados entre os núcleos físicos, como explicado no site da Microsoft :" o algoritmo e a infra-estrutura do Core Parking também são usados para balancear o desempenho do processador entre processadores lógicos em sistemas cliente Windows 7 com processadores que incluem Intel Hyper- Tecnologia de segmentação. "

No entanto, após a atualização para o Windows 10, notei que não há estacionamento central. Todos os núcleos lógicos estão ativos o tempo todo e quando você executa um aplicativo usando menos de quatro threads, você pode ver como O programador os distribui igualmente em todos os núcleos lógicos de cpu. Os funcionários da Microsoft confirmaram que o estacionamento principal está desativado no Windows 10 .

Mas eu me pergunto por que? Qual foi o motivo disso? Existe um substituto e, se sim, como se parece? A Microsoft implementou uma nova estratégia de agendador que tornou o estacionamento do núcleo obsoleto?

Apêndice:

Aqui está um exemplo de como o estacionamento central introduzido no Windows 7 pode beneficiar o desempenho (em comparação com o Vista, que ainda não tinha o recurso central de estacionamento). O que você pode ver é que no Vista, o HT (Hyper Threading) prejudica o desempenho enquanto no Windows 7 não:

( fonte )

Eu tentei ativar o Core Parking como mencionado aqui , mas o que eu observei foi que o algoritmo do Core Parking não está mais ciente do Hyper Threading. Estacionou núcleos 4,5,6,7, enquanto deveria ter estacionado o núcleo 1,3,5,7 para evitar que os fios fossem atribuídos ao mesmo núcleo físico. O Windows enumera os núcleos de forma que dois índices sucessivos pertençam ao mesmo núcleo físico. Muito estranho. Parece que a Microsoft estragou isso fundamentalmente. E ninguém notou ...

Além disso, fiz alguns benchmarks de CPU usando exatamente 4 threads.

Afinidade de CPU definida para todos os núcleos (Windows defualt):

Average running time: 17.094498, standard deviation: 2.472625

Afinidade de CPU definida para todos os outros núcleos (para que seja executada em diferentes núcleos físicos, o melhor agendamento possível):

Average running time: 15.014045, standard deviation: 1.302473

Afinidade de CPU definida como o pior agendamento possível (quatro núcleos lógicos em dois núcleos físicos):

Average running time: 20.811493, standard deviation: 1.405621

Então é uma diferença de desempenho. E você pode ver que o agendamento defualt do Windows está entre o melhor e o pior agendamento possível, como seria de esperar que acontecesse com um agendador ciente de não-hyperthreading. No entanto, como apontado nos comentários, pode haver outras causas responsáveis por isso, como menos interrupções de contexto, inferência por aplicativos de monitoramento, etc. Portanto, ainda não temos uma resposta definitiva aqui.

Código-fonte do meu benchmark:

#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;
}
    
por manuel 15.03.2017 / 15:24

1 resposta

0

Huh, eu poderia contar a história, mas você vai odiá-la e eu vou odiar escrever: -)

Versão curta - O Win10 estragou tudo o que podia e está em estado perpétuo de núcleos famintos devido a um problema sistêmico conhecido como excesso de assinatura de CPU (muitos segmentos, ninguém pode atendê-los, algo está sufocando a qualquer momento, para sempre) . É por isso que ele precisa desesperadamente dessas falsas CPUs, encurta o temporizador do agendador básico para 1 ms e não pode deixar que você estacione nada. Seria apenas queimar o sistema. Abra o Process Explorer e adicione o número de threads, agora faça as contas: -)

A

API dos conjuntos de CPU foi introduzida para dar pelo menos alguma chance de aqueles que sabem e têm tempo para escrever o código para lutar contra a besta. Você pode de fato estacionar CPU-s colocando-os em um CPU-Set que você não vai dar a ninguém e criar um padrão para jogá-lo em piranhas. Mas você não pode fazê-lo em skus do cliente (você poderia tecnicamente, ele simplesmente não seria honrado) já que o kernel faria um estado de pânico e ou ignora totalmente os Conjuntos de CPU ou algumas outras coisas vão começar a travar. Tem que defender a integridade do sistema a qualquer custo.

Todo o estado de coisas é em grande parte um tabu, uma vez que exigiria grandes reescritas e todo mundo abatendo o não de fios frívolos e admitindo que eles estragaram tudo. Na verdade, os hyperthreads precisam ser permanentemente desativados (eles aquecem os núcleos sob carga real, degradam o desempenho e desestabilizam o HTM - a principal razão pela qual ele nunca se tornou mainstream). As grandes lojas do SQL Server estão fazendo isso como uma primeira etapa de configuração e o Azure também. O Bing não é, eles executam servidores com a configuração de cliente de fato, já que precisariam de muito mais núcleos para se atrever a mudar. O problema foi filtrado para o Server 2016.

O SQL Server é o único usuário real dos Conjuntos de CPU (como de costume :-), 99% dos recursos avançados no Win sempre foram feitos apenas para o SQL Server, começando com manipulação de arquivos mapeada de memória super eficiente que mata as pessoas que chegam do Linux, uma vez que assumem uma semântica diferente).

Para jogar com segurança, você precisaria de 16 núcleos min para uma caixa de cliente, 32 para um servidor (que realmente faz algo real :-) Você precisa colocar pelo menos 4 núcleos no conjunto padrão para que os serviços do kernel e do sistema mal consegue respirar, mas ainda é apenas um equivalente de laptop de dois núcleos (você ainda tem asfixia perpétua), o que significa 6-8 para permitir que o sistema respire corretamente.

O Win10 precisa de 4 núcleos e 16 GB para respirar mal. Laptops se safam com 2 núcleos e 2 "CPU-s" falsos, se não há nada exigente a fazer, já que a distribuição de trabalho é tão grande que sempre há coisas suficientes para esperar (fila longa no memaloc "ajuda muito" :-) .

Isto ainda não irá ajudá-lo com o OpenMP (ou qualquer paralelização automática) a menos que você tenha uma maneira de explicitamente usar o seu CPU Set (encadeamentos individuais tenham que ser atribuídos ao CPU Set) e nada mais. Você ainda precisa definir o conjunto de afinidades do processo, é pré-condição para os Conjuntos de CPU.

O servidor 2k8 foi o último bom (sim, isso significa Win7 também :-). As pessoas estavam carregando um TB em 10 minutos com ele e com o SQL Server. Agora as pessoas se gabam se podem carregá-lo em uma hora - no Linux :-) Então, as chances são de que o estado das coisas não seja muito melhor "lá" também. O Linux tinha o CPU Sets antes do Win.

    
por 10.08.2018 / 07:56