由于英特尔 CPU 默认禁用核心停车,Windows 10 调度程序如何处理超线程?

由于英特尔 CPU 默认禁用核心停车,Windows 10 调度程序如何处理超线程?

我在 Intel Xeon E3-1231v3 CPU(Haswell,4 个物理核心,8逻辑核心)。

当我第一次在这台机器上安装 Windows 7 时,我观察到八个逻辑核心中有四个处于停顿状态,直到应用程序需要超过 4 个线程。可以使用 Windows 资源监视器检查核心是否处于停顿状态(例子)。据我所知,这是保持物理核心间线程平衡的重要技术,如Microsoft 网站:”核心停车算法和基础架构还用于平衡包含英特尔超线程技术的处理器的 Windows 7 客户端系统上的逻辑处理器之间的处理器性能。

然而升级到 Windows 10 后,我注意到没有核心停车。所有逻辑核心始终处于活动状态,当您使用少于四个线程运行应用程序时,您可以看到调度程序如何在所有逻辑 CPU 核心上均匀分配它们。微软员工已确认 Windows 10 中已禁用 Core Parking

但我想知道为什么?这是什么原因?有替代品吗?如果有,它是什么样子的?微软是否实施了新的调度程序策略,使核心停放过时了?


附录:

以下是 Windows 7 中引入的核心停放如何有益于性能的示例(与尚未具有核心停放功能的 Vista 相比)。您可以看到,在 Vista 上,HT(超线程)会损害性能,而在 Windows 7 上则不会:

在此处输入图片描述

在此处输入图片描述

来源

我尝试按照所述启用 Core Parking这里,但我观察到核心停放算法不再支持超线程。它停放了核心 4、5、6、7,而它应该停放核心 1、3、5、7,以避免将线程分配给同一个物理核心。Windows 以这样的方式枚举核心,即两个连续的索引属于同一个物理核心。很奇怪。微软似乎从根本上搞砸了。而且没有人注意到……

此外,我使用 4 个线程进行了一些 CPU 基准测试。

CPU 亲和性设置为所有核心(Windows 默认):

平均运行时间:17.094498,标准差:2.472625

CPU 亲和性设置为每个其他核心(以便它在不同的物理核心上运行,实现最佳调度):

平均运行时间:15.014045,标准差:1.302473

CPU 亲和性设置为最差的调度(两个物理核心上有四个逻辑核心):

平均运行时间:20.811493,标准差:1.405621

因此性能差异。您可以看到 Windows 默认调度排名介于最佳和最差调度之间,正如我们预期的那样,它会发生在一个非超线程感知调度程序中。但是,正如评论中指出的那样,可能还有其他原因导致这种情况,例如较少的上下文切换、通过监控应用程序进行推断等。所以我们仍然没有明确的答案。

我的基准测试的源代码:

#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;
}

答案1

嗯,我可以给你讲这个故事,但你会讨厌它,我也会讨厌写它:-)

简短版本 - Win10 搞砸了所有可能的事情,由于系统性问题(称为 CPU 超额认购,线程太多,没有人能够为它们提供服务,某些东西在任何时候都处于阻塞状态,永远如此),因此处于核心饥饿状态。这就是为什么它迫切需要这些假 CPU,将基本调度程序计时器缩短到 1 毫秒,并且不能让你停放任何东西。它只会烧坏系统。打开进程资源管理器并添加线程数,现在进行计算 :-)

CPU 设置 API引入该功能是为了给那些了解并有时间编写代码来与野兽搏斗的人至少提供一些机会。你可以通过将假 CPU 放入你不会给任何人的 CPU 集来事实上停放假 CPU,并创建默认集将其扔给食人鱼。但你不能在客户端 sku 上这样做(从技术上讲你可以,只是不会被遵守),因为内核会进入恐慌状态,要么完全忽略 CPU 集,要么其他一些东西会开始崩溃。它必须不惜一切代价捍卫系统的完整性。

整个情况总体上是一个禁忌,因为它需要进行大规模重写,每个人都要剔除无用的线程,并承认他们搞砸了。超线程实际上必须被永久禁用(它们在实际负载下会使内核升温,降低性能并使 HTM 不稳定 - 这是它从未成为主流的主要原因)。大型 SQL Server 商店正在将其作为第一步设置,Azure 也是如此。Bing 不是,他们使用事实上的客户端设置运行服务器,因为他们需要更多内核才能敢于切换。问题渗透到了 Server 2016 中。

SQL Server 是 CPU 集的唯一真正用户(像往常一样 :-),Win 中 99% 的性能高级事物一直都是为 SQL Server 完成的,从超高效的内存映射文件处理开始,这会杀死来自 Linux 的人,因为他们假设不同的语义)。

为了安全地使用这个,您需要为客户端机箱配备至少 16 个核心,为服务器配备 32 个核心(这实际上会做一些实际的事情 :-) 您必须在默认设置中放置至少 4 个核心,以便内核和系统服务几乎可以呼吸,但这仍然只是双核笔记本电脑的等效产品(您仍然会永久窒息),这意味着需要 6-8 个核心才能让系统正常呼吸。

Win10 需要 4 个内核和 16 GB 才能勉强维持运行。如果没有繁重的工作要做,笔记本电脑可以使用 2 个内核和 2 个假“CPU”,因为它们通常的工作分布是这样的,总有足够多的事情需要等待(memaloc 上的长队列“很有帮助” :-)。

这仍然不会帮助您使用 OpenMP(或任何自动并行化),除非您有办法明确告诉它使用您的 CPU 集(必须将各个线程分配给 CPU 集)而不做其他任何事情。您仍然需要进程亲和性集,这是 CPU 集的先决条件。

Server 2k8 是最后一个好版本(是的,这意味着 Win7 也是如此 :-)。人们使用它和 SQL Server 在 10 分钟内批量加载 1 TB。现在人们吹嘘他们能在一小时内加载它 - 在 Linux 下 :-) 所以很可能“那边”的情况也好不到哪里去。Linux 早在 Win 之前就有 CPU 集了。

相关内容