由于 Intel CPU 默认禁用 Core Parking,Windows 10 调度程序如何处理超线程?

man*_*uel 7 cpu performance hyper-threading windows-10

我在 Intel Xeon E3-1231v3 CPU(Haswell,4 个物理内核,8 个逻辑内核)上运行 Windows 10 (1607 )。

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

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

但我想知道为什么?这是什么原因?是否有替代品,如果是,它看起来如何?Microsoft 是否实施了新的调度程序策略,使核心停车位过时了?


附录:

下面是一个示例,说明 Windows 7 中引入的核心停车如何提高性能(与尚没有核心停车功能的 Vista 相比)。您可以看到,在 Vista 上,HT(超线程)会损害性能,而在 Windows 7 上则不会:

在此处输入图片说明

在此处输入图片说明

来源

我尝试启用此处提到的 Core Parking ,但我观察到 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;
}
Run Code Online (Sandbox Code Playgroud)

ZXX*_*ZXX -3

呵呵,我可以告诉你这个故事,但你会讨厌它,我也会讨厌写它:-)

简短版本 - Win10 搞砸了一切可能的事情,并且由于称为 cpu 超额订阅的系统性问题(线程太多,没有人可以为它们提供服务,任何时候都有东西永远窒息)而处于核心饥饿的永久状态。这就是为什么它迫切需要这些假 CPU,将基本调度程序计时器缩短到 1 毫秒,并且不能让您停放任何东西。它只会烧毁系统。打开 Process Explorer 并添加线程数,现在进行数学计算:-)

CPU Sets API 的引入至少给那些了解并有时间编写代码来与野兽搏斗的人提供了一些战斗的机会。事实上,您可以通过将假 CPU 放入一个您不会提供给任何人的 CPU 集中来停放它们,并创建默认集以将其扔给食人鱼。但是你不能在客户端 sku-s 上执行此操作(从技术上讲,你可以这样做,只是不会受到尊重),因为内核会进入恐慌状态,并且要么完全忽略 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 分钟内批量加载 TB。现在,人们吹嘘他们是否可以在一小时内加载它 - 在 Linux 下:-) 所以很可能“那边”的情况也好不到哪儿去。Linux 的 CPU 集早于 Win。