dis*_*ame 23 .net c# performance multithreading hyperthreading
鉴于:
8,16和28个线程可能比4个线程表现更好吗?我的理解是,4个线程将执行较少的上下文切换,并且在任何意义上将比4个物理核心机器上的8,16或28个线程具有更少的开销.但是,时间是 -
Threads Time Taken (in seconds)
4 78.82
8 48.58
16 51.35
28 52.10
Run Code Online (Sandbox Code Playgroud)
用于测试获取时间的代码在下面的原始问题部分中提到.CPU规格也在底部给出.
在阅读了各个用户提供的答案以及评论中给出的信息后,我终于可以将问题归结为我上面写的内容.如果上述问题为您提供完整的上下文,则可以跳过下面的原始问题.
我们说的是什么意思
超线程的工作方式是复制处理器的某些部分 - 存储体系结构状态的部分 - 但不复制主要执行资源.这允许超线程处理器作为通常的"物理"处理器和主机操作系统的额外"逻辑"处理器出现
?
今天在SO上询问了这个问题,它基本上测试了多个线程执行相同工作的性能.它具有以下代码:
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);
}
}
Run Code Online (Sandbox Code Playgroud)
(我已经删除了一些大括号,将代码放在一个页面中以便快速阅读.)
我在我的机器上运行此代码以复制问题.我的机器有4个物理核心和8个逻辑核心.DoWork()上面代码中的方法完全受CPU限制.我觉得超线程可能会导致30%的加速(因为这里我们拥有与物理内核一样多的CPU绑定线程(即4)).但它几乎可以获得64%的性能提升.当我为4个线程运行此代码时,花了大约82秒,当我为8,16和28个线程运行此代码时,它在所有情况下运行大约50秒.
总结时间:
Threads Time Taken (in seconds)
4 78.82
8 48.58
16 51.35
28 52.10
Run Code Online (Sandbox Code Playgroud)
我可以看到4个线程的CPU使用率约为50%.不应该是~100%?毕竟我的处理器只有4个物理内核.8线和16线的CPU使用率约为100%.
如果有人能够在开始时解释引用的文本,我希望能更好地理解超线程,并希望得到答案为什么完全CPU绑定的进程能够更好地处理超线程?.
为了完成,
我可以看到4个线程的CPU使用率约为50%.不应该是~100%?
不,它不应该.
在4物理核心机器上运行4个CPU绑定线程时,50%CPU使用率的理由是什么?
这就是在Windows中报告CPU利用率的方式(顺便说一下,至少在某些其他操作系统上也是如此).HT CPU显示为操作系统的两个核心,并按此报告.
因此,当你有四个HT CPU时,Windows会看到一台八核机器.如果查看任务管理器中的"性能"选项卡,您将看到八个不同的CPU图,并且计算总CPU利用率,100%利用率是这八个核的完全利用率.
如果您只使用四个线程,则这些线程无法充分利用可用的CPU资源并解释了时序.它们最多可以使用八个可用核心中的四个,因此当然您的利用率最高可达50%.一旦超过逻辑核心数(8),运行时间再次增加; 在这种情况下,您正在添加调度开销而不添加任何新的计算资源.
顺便说说…
从过去的共享缓存和其他限制开始,HyperThreading已经有了很大的改进,但它仍然永远不会提供与完整CPU相同的吞吐量优势,因为CPU内部仍存在一些争用.所以即使忽略操作系统开销,你的速度提高35%对我来说也是相当不错的.我经常看到不超过20%的速度将额外的HT内核添加到计算瓶颈的过程中.
每条指令都必须经过管道中的几个步骤才能完全执行.至少,它必须被解码,发送到执行单元,然后在那里实际执行.现代CPU上有几个执行单元,它们可以完全并行执行指令.顺便说一句,执行单元不可互换:某些操作只能在单个执行单元上完成.例如,内存加载通常专用于一个或两个单元,内存存储专门发送到另一个单元,所有计算都由其他一些单元完成.
了解了管道,我们可能想知道:如果我们编写纯粹的后续代码并且每条指令都要经历如此多的流水线阶段,那么CPU如何工作得如此之快?答案就是:处理器以无序方式执行指令.它有一个大的重新排序缓冲区(例如200条指令),它并行地通过其管道推送许多指令.如果在任何时候由于任何原因无法执行某些指令(等待来自慢速存储器的数据,取决于尚未完成的其他指令,则无论如何),它会延迟一些周期.在此期间,处理器执行一些新指令,这些指令位于我们代码中的延迟指令之后,因为它们不以任何方式依赖于延迟指令.
现在我们可以看到延迟问题.即使指令被解码并且其所有输入都已经可用,也需要几个周期才能完全执行.此延迟称为指令延迟.但是,我们知道此时处理器可以执行许多其他独立指令(如果有的话).
如果指令从L2高速缓存加载数据,则必须等待大约10个周期才能加载数据.如果数据仅位于RAM中,则需要数百个周期才能将其加载到处理器.在这种情况下,我们可以说该指令具有高延迟.此时必须执行一些其他独立操作,以获得最大性能.这有时称为延迟隐藏.
最后,我们不得不承认,大多数真正的代码本质上是后续的.它有一些独立的指令可以并行执行,但不能太多.没有执行指令会导致管道气泡,并导致处理器晶体管的低效使用.另一方面,几乎在所有情况下,两个不同线程的指令自动独立.这直接引导我们超线程的想法.
PS您可能希望阅读Agner Fog的手册,以更好地了解现代CPU的内部结构.
当在单核上以超线程模式执行两个线程时,处理器可以交错其指令,允许使用第二线程的指令从第一线程填充气泡.这允许更好地利用处理器的资源,尤其是在普通程序的情况下.请注意,HT不仅可以帮助您进行大量的内存访问,还可以帮助您处理大量的内存代码.经过充分优化的计算代码可以充分利用CPU的所有资源,在这种情况下,您将看不到 HT的利润(例如dgemm来自优化良好的BLAS的例程).
PS您可能希望阅读英特尔对超线程的详细说明,包括有关哪些资源是重复或共享的信息,以及有关性能的讨论.
上下文是CPU的内部状态,至少包括所有寄存器.当执行线程改变时,OS必须进行上下文切换(这里有详细描述).根据这个答案,上下文切换大约需要10微秒,而调度程序的时间量是10毫秒或更长(见这里).因此,上下文切换不会对总时间产生太大影响,因为它们很少完成.请注意,在某些情况下,线程之间CPU缓存的竞争会增加交换机的有效成本.
但是,在超线程的情况下,每个内核在内部有两个状态:两组寄存器,共享高速缓存,一组执行单元.因此,当您在4个物理内核上运行8个线程时,操作系统无需执行任何上下文切换.当您在四核上运行16个线程时,将执行上下文切换,但它们占用总时间的一小部分,如上所述.
说到您在流程管理器中看到的CPU利用率,它不会测量CPU管道的内部.Windows只能注意到线程何时将执行返回到OS以便:休眠,等待互斥,等待硬盘,以及执行其他慢速操作.因此,它认为如果有一个线程正在处理它就会完全使用一个核心,它不会休眠或等待任何事情.例如,您可以检查运行无限循环是否可以while (true) {}充分利用CPU.