C# 为什么完全受CPU限制的进程在超线程中工作得更好?

C# 为什么完全受CPU限制的进程在超线程中工作得更好?,c#,.net,multithreading,performance,hyperthreading,C#,.net,Multithreading,Performance,Hyperthreading,鉴于: 完全受CPU限制的非常大(即超过几个CPU周期)的作业,以及 一个CPU,具有4个物理核和8个逻辑核 8、16和28线程的性能是否可能优于4线程?我的理解是,与4物理核心机器上的8、16或28个线程相比,4个线程执行的上下文切换更少,开销也更小。然而,时间安排是有限的- Threads Time Taken (in seconds) 4 78.82 8 48.58 16 51.35 28 52.

鉴于:

  • 完全受CPU限制的非常大(即超过几个CPU周期)的作业,以及
  • 一个CPU,具有4个物理核和8个逻辑核
8、16和28线程的性能是否可能优于4线程?我的理解是,与4物理核心机器上的8、16或28个线程相比,4个线程执行的上下文切换更少,开销也更小。然而,时间安排是有限的-

Threads    Time Taken (in seconds)
   4         78.82
   8         48.58
   16        51.35
   28        52.10
下面的原始问题部分提到了用于测试获取计时的代码。CPU规格也在底部给出


在阅读了不同用户提供的答案和评论中给出的信息后,我终于能够将问题归结为我上面写的内容。如果以上问题为您提供了完整的上下文,您可以跳过下面的原始问题

原始问题 我们说的是什么意思

“超线程”的工作原理是复制 处理器存储体系结构状态但不复制 主要执行资源。这允许使用超线程处理器 显示为通常的“物理”处理器和额外的“逻辑” 将处理器连接到主机操作系统

?

今天,它基本上测试了多个线程执行相同工作的性能。其代码如下:

private static void Main(string[] args)
{
    int threadCount;
    if (args == null || args.Length < 1 || !int.TryParse(args[0], out threadCount))
        threadCount = Environment.ProcessorCount;

    int load;
    if (args == null || args.Length < 2 || !int.TryParse(args[1], out load))
        load = 1;

    Console.WriteLine("ThreadCount:{0} Load:{1}", threadCount, load);
    List<Thread> threads = new List<Thread>();
    for (int i = 0; i < threadCount; i++)
    {
        int i1 = i;
        threads.Add(new Thread(() => DoWork(i1, threadCount, load)));
    }

    var timer = Stopwatch.StartNew();
    foreach (var thread in threads) thread.Start();
    foreach (var thread in threads) thread.Join();
    timer.Stop();

    Console.WriteLine("Time:{0} seconds", timer.ElapsedMilliseconds/1000.0);
}

static void DoWork(int seed, int threadCount, int load)
{
    var mtx = new double[3,3];
    for (var i = 0; i < ((10000000 * load)/threadCount); i++)
    {
         mtx = new double[3,3];
         for (int k = 0; k < 3; k++)
            for (int l = 0; l < 3; l++)
              mtx[k, l] = Math.Sin(j + (k*3) + l + seed);
     }
}
我可以看到4个线程的CPU使用率约为50%。它不应该是100%左右吗?毕竟,我的处理器只有4个物理内核。8和16个线程的CPU使用率约为100%

如果有人能在一开始就解释引用的文本,我希望能更好地理解hyperreading,并反过来希望得到答案,为什么一个完全受CPU约束的进程在hyperreading中工作得更好


为了完成,

  • 我有Intel Core i7-4770 CPU@3.40 GHz,3401 MHz,4核,8逻辑处理器
  • 我在发布模式下运行代码
  • 我知道计时的方式很糟糕。这只会为最慢的线程提供时间。我接受了另一个问题的代码。但是,在4物理核心机器上运行4个CPU绑定线程时,50%的CPU使用率的理由是什么
我可以看到4个线程的CPU使用率约为50%。它不应该是100%左右吗

不,不应该

在4物理核心机器上运行4个CPU绑定线程时,50%的CPU使用率的理由是什么

这就是Windows中CPU利用率的报告方式(顺便说一句,至少在其他一些操作系统上也是如此)。HT CPU在操作系统中显示为两个内核,并按此报告

因此,当您有四个HT CPU时,Windows会看到一台八核机器。如果查看Task Manager中的“Performance”选项卡,您将看到八个不同的CPU图,总CPU利用率是以100%利用率作为这八个核心的全部利用率来计算的

如果您只使用四个线程,那么这些线程就不能充分利用可用的CPU资源,这就是计时的原因。他们最多可以使用8个可用内核中的4个,因此您的利用率当然会达到50%一旦超过逻辑核数(8),运行时将再次增加;在这种情况下,您会增加调度开销,而不会添加任何新的计算资源。


顺便说一下


与以前的共享缓存和其他限制相比,超线程技术有了很大的改进,但它仍然无法提供与完整CPU相同的吞吐量优势,因为CPU中仍然存在一些争用。因此,即使忽略操作系统开销,您35%的速度提升对我来说也相当不错。我经常看到,在计算瓶颈的进程中添加额外的HT内核的速度不会超过20%。

超线程通过在处理器执行管道中交错指令来工作。当处理器在一个“线程”上执行读写操作时,它在另一个“线程”上执行逻辑求值,将它们分开,并使您感觉到性能加倍

获得如此大的加速的原因是
DoWork
方法中没有分支逻辑。这都是一个大循环,具有非常可预测的执行序列

处理器执行管道必须经过几个时钟周期才能执行单个计算。处理器试图通过使用接下来的几条指令预加载执行缓冲区来优化性能。如果加载的指令实际上是一个条件跳转(例如
If
语句),这是一个坏消息,因为处理器必须刷新整个管道并从内存的不同部分获取指令


你可能会发现,如果你在你的
DoWork
方法中加入
if
语句,你将不会得到100%的加速…

我无法解释你观察到的加速量:100%对于超读来说似乎太大了。但我可以解释这些原则

超线程的主要好处是处理器必须在线程之间切换。每当线程数超过CPU核数(99.9997%的情况下为真)且操作系统决定切换到其他线程时,它必须执行(大部分)以下步骤:

  • 保存当前线程的状态:这包括堆栈、寄存器的状态和程序计数器。它们保存的位置取决于体系结构,但一般来说,它们要么保存在缓存中,要么保存在内存中。无论哪种方式,此步骤都需要时间
  • 将线程置于“就绪”状态(与“运行”状态相反)
  • 加载下一个线程的状态:再次加载,包括堆栈、寄存器和程序
    Threads    Time Taken (in seconds)
       4         78.82
       8         48.58
       16        51.35
       28        52.10