上下文切换的开销是多少?
- 2024-11-01 08:41:00
- admin 原创
- 37
问题描述:
最初我认为上下文切换的开销是刷新 TLB。然而我刚刚在维基百科上看到:
http://en.wikipedia.org/wiki/Translation_lookaside_buffer
2008 年,英特尔 (Nehalem)[18] 和 AMD (SVM)[19] 都引入了标签作为 TLB 条目的一部分,并引入了在查找期间检查标签的专用硬件。尽管这些功能尚未得到充分利用,但可以预见的是,未来这些标签将标识每个 TLB 条目所属的地址空间。因此,上下文切换不会导致 TLB 刷新- 而只是将当前地址空间的标签更改为新任务地址空间的标签。
上述内容是否证实,对于较新的英特尔 CPU,TLB 不会在上下文切换时被刷新?
这是否意味着上下文切换中现在没有实际开销?
(我试图了解上下文切换的性能损失)
解决方案 1:
正如维基百科在其上下文切换文章中所述,“上下文切换是存储和恢复进程状态(上下文)的过程,以便稍后可以从同一点恢复执行。 ” 我假设上下文切换是同一操作系统的两个进程之间的切换,而不是用户/内核模式转换(系统调用),后者速度更快,不需要 TLB 刷新。
因此,OS 内核需要大量时间将当前正在运行的进程的执行状态(所有寄存器;以及许多特殊控制结构)保存到内存,然后加载其他进程的执行状态(从内存中读取)。如果需要,TLB 刷新将增加切换时间,但这只是总开销的一小部分。
如果你想找到上下文切换延迟,有lmbench
一个基准测试工具http://www.bitmover.com/lmbench/和 LAT_CTX 测试http://www.bitmover.com/lmbench/lat_ctx.8.html
我找不到 nehalem 的结果(phoronix 套件中有 lmbench 吗?),但对于core2 和现代 Linux,上下文切换可能需要 5-7 微秒。
也有低质量测试http://blog.tsunanet.net/2010/11/how-long-does-it-take-to-make-context.html的结果,上下文切换需要 1-3 微秒。从他的结果中无法得到不刷新 TLB 的确切效果。
更新-您的问题应该是关于虚拟化,而不是关于进程上下文切换。
RWT在 David Kanter 于 2008 年 4 月 2 日撰写的有关 Nehalem 的文章“Nehalem 内部:英特尔未来的处理器和系统。TLB、页表和同步”中表示,Nehalem 将 VPID 添加到 TLB 中,以使虚拟机/主机切换(vmentry/vmexit)更快:
Nehalem 的 TLB 条目也通过引入“虚拟处理器 ID”或 VPID 进行了细微更改。每个 TLB 条目都会缓存虚拟到物理地址的转换...该转换特定于给定的进程和虚拟机。每当处理器在虚拟化客户机和主机实例之间切换时,英特尔的旧 CPU 都会刷新 TLB,以确保进程仅访问允许其接触的内存。VPID 会跟踪 TLB 中给定转换条目与哪个 VM 相关联,因此当 VM 退出并重新进入时,无需为了安全而刷新 TLB。...VPID 有助于提高虚拟化性能,因为它可以降低 VM 转换的开销;英特尔估计,与 Merom(即 65nm Core 2)相比,Nehalem 中往返 VM 转换的延迟减少了 40%,比 45nm Penryn 低约三分之一。
另外,您应该知道,在问题中您引用的片段中,“[18]”链接指向“G. Neiger,A. Santoni,F. Leung,D. Rodgers 和 R. Uhlig。英特尔虚拟化技术:高效处理器虚拟化的硬件支持。英特尔技术杂志,10(3)。”,所以这是有效虚拟化的功能(快速客户机-主机切换)。
解决方案 2:
我们将任务切换的成本分为“直接成本”(任务切换代码本身的成本)和“间接成本”(TLB 未命中的成本等)。
直接成本
对于直接成本,这主要是保存上一个任务的状态(在架构上对用户空间可见)然后加载下一个任务的状态的成本。这取决于具体情况,主要是因为它可能包括也可能不包括 FPU/MMX/SSE/AVX 状态,这些状态可能加起来有几 KiB 的数据(特别是如果涉及 AVX - 例如 AVX2 本身就是 512 字节,而 AVX-512 本身就超过 2 KiB)。
请注意,存在一个“延迟状态加载”机制,以避免加载(部分或全部)FPU/MMX/SSE/AVX 状态的成本,并避免在未加载该状态时保存该状态的成本;并且可以出于性能原因禁用此功能(如果几乎所有任务都使用该状态,则“正在使用的状态需要加载”陷阱/异常的成本超过了您在任务切换期间尝试避免执行此操作所节省的成本)或出于安全原因(例如,因为 Linux 中的代码执行“如果使用则保存”而不是“如果使用则保存然后清除”,并将属于一个任务的数据留在寄存器中,而另一个任务可以通过推测执行攻击获得这些数据)。
还有一些其他成本(更新统计数据 - 例如“前一个任务使用的 CPU 时间量”),确定新任务是否使用与旧任务相同的虚拟地址空间(例如同一进程中的不同线程)等。
间接成本
间接成本本质上是 CPU 的所有“类似缓存”内容的有效性损失 - 缓存本身、TLB、更高级别的分页结构缓存、所有分支预测内容(分支方向、分支目标、返回缓冲区)等。
间接成本可分为 3 个原因。一个是间接成本,因为任务切换时该事物被完全刷新。过去,这主要限于由于在任务切换期间刷新 TLB 而导致的 TLB 未命中。请注意,即使在使用 PCID 时也可能发生这种情况 - ID 数量限制为 4096 个(当使用“崩溃缓解”时,ID 成对使用 - 对于每个虚拟地址空间,一个 ID 用于用户空间,另一个用于内核),这意味着当使用的虚拟地址空间超过 4096 个(或 2048 个)时,内核必须回收以前使用的 ID 并刷新正在重新使用的 ID 的所有 TLB。但是,现在(由于存在所有推测执行安全问题),内核可能会刷新其他内容(例如分支预测内容),以便信息不会从一个任务泄漏到另一个任务,但我真的不知道 Linux 是否支持哪些“类似缓存”的东西(我怀疑它们主要试图防止数据从内核泄漏到用户空间,并最终防止数据意外地从一个任务泄漏到另一个任务)。
造成间接成本的另一个原因是容量限制。例如,如果 L2 缓存最多只能缓存 256 KiB 的数据,而前一个任务使用了超过 256 KiB 的数据;那么 L2 缓存将充满对下一个任务无用的数据,并且下一个任务想要缓存的所有数据(以及之前已缓存的数据)将由于“最近最少使用”而被逐出。这适用于所有“类似缓存”的东西(包括 TLB 和更高级别的分页结构缓存,即使在使用 PCID 功能时也是如此)。
间接成本的另一个原因是将任务迁移到不同的 CPU。这取决于哪些 CPU - 例如,如果将任务迁移到同一核心内的不同逻辑 CPU,那么许多“类似缓存”的东西可能由两个 CPU 共享,迁移成本可能相对较小;如果将任务迁移到不同物理封装中的 CPU,那么两个 CPU 可能不共享任何“类似缓存”的东西,迁移成本可能相对较大。
请注意,间接成本的上限取决于任务的用途。例如,如果任务使用大量数据,则间接成本可能相对较高(大量缓存和 TLB 未命中),而如果任务使用少量数据,则间接成本可能微不足道(很少缓存和 TLB 未命中)。
无关
请注意,PCID 功能有其自身的成本(与任务切换本身无关)。具体来说,当在一个 CPU 上修改页面转换时,可能需要使用称为“多 CPU TLB 击落”的方法在其他 CPU 上使它们无效,这种方法相对昂贵(涉及 IPI/处理器间中断,这会中断其他 CPU,并且每个 CPU 的成本为“几百个周期”)。如果没有 PCID,您可以避免其中一些问题。例如,如果没有 PCID,对于在一个 CPU 上运行的单线程进程,您知道没有其他 CPU 可以使用相同的虚拟地址空间,因此知道您不需要执行“多 CPU TLB 击落”,并且如果多线程进程仅限于单个 NUMA 域,则只有该 NUMA 域内的 CPU 需要参与“多 CPU TLB 击落”。当使用 PCID 时,您不能依赖这些技巧,并且开销更高,因为“多 CPU TLB 击落”不会经常避免。
当然,ID 管理也有一些成本(例如,确定哪个 ID 可以免费分配给新创建的任务、在任务终止时撤销 ID、当虚拟地址空间多于 ID 时使用某种“最近最少使用”系统来重新利用 ID 等)。
由于这些成本,必然会出现一些病态情况,其中使用 PCID 的成本超过“任务切换导致的 TLB 未命中减少”的好处(使用 PCID 会使性能变差)。
解决方案 3:
如果我们计算缓存失效(我们通常应该这样做,并且这是现实世界中上下文切换成本的最大贡献者),则由于上下文切换而导致的性能损失可能是巨大的:
https://www.usenix.org/legacy/events/expcs07/papers/2-li.pdf(诚然有点过时,但这是我能找到的最好的)给出的结果是 100K-1M CPU 周期。理论上,在最坏的情况下,对于多插槽服务器盒,每插槽 32M L3 缓存由 64 字节缓存行组成,完全随机访问,典型访问时间为 L3 40 周期/主 RAM 100 周期,损失可能高达 30M+ CPU 周期(!)。
根据个人经验,我认为它通常在几十 K 周期的范围内,但根据具体情况,它可能会相差一个数量级。
解决方案 4:
注意:正如 Brendan 在其评论中指出的那样。此答案的目的是详细回答上下文切换对 Windows 服务器/桌面性能的总体影响是什么,包括 Windows、Linux 和 Solaris 等操作系统上的不同开销...。
找出答案的最佳方法当然是对其进行基准测试。这里的问题是,每秒上下文切换次数与 CPU 时间之间的关系是指数关系。换句话说,这是一个 O(n 2 ) 成本。这意味着我们有一个无法超越的最大限制。
以下基准代码使用了一些不安全的变量等...忽略这一点,因为这不是重点。
每个线程实际完成的工作很少。理论上,每个线程每秒应该产生 1000 次上下文切换。
将以下代码转储到.NET 控制台应用程序中并在 perfmon 中查看结果。
向 Perfmon 添加两个计数器:Processor->%Processor time和System->Context Switches Per Second。在 8 核机器上,128 个线程会因线程完成的工作而产生约 0.1% 的 CPU 开销。
按理说,2560 个线程应该产生大约 2% 的 CPU,但实际上,在 2300 个线程时 CPU 就达到了 100%(在我的 Core i7-4790K 4 核 + 4 超线程核台式机上)。
2048 个线程 - 每秒 200 万次上下文切换:CPU 利用率为 40%
2300 个线程 - 每秒 230 万次上下文切换:CPU 100%
static void Main(string[] args)
{
ThreadTestClass ThreadClass;
bool Wait;
int Counter;
Wait = true;
Counter = 0;
while (Wait)
{
if (Console.KeyAvailable)
{
ConsoleKey Key = Console.ReadKey().Key;
switch (Key)
{
case ConsoleKey.UpArrow:
ThreadClass = new ThreadTestClass();
break;
case ConsoleKey.DownArrow:
SignalExitThread();
break;
case ConsoleKey.PageUp:
SleepTime += 1;
break;
case ConsoleKey.PageDown:
SleepTime -= 1;
break;
case ConsoleKey.Insert:
for (int I = 0; I < 64; I++)
{
ThreadClass = new ThreadTestClass();
}
break;
case ConsoleKey.Delete:
for (int I = 0; I < 64; I++)
{
SignalExitThread();
}
break;
case ConsoleKey.Q:
Wait = false;
break;
case ConsoleKey.Spacebar:
Wait = false;
break;
case ConsoleKey.Enter:
Wait = false;
break;
}
}
Counter += 1;
if (Counter >= 10)
{
Counter = 0;
Console.WriteLine(string.Concat(@"Thread Count: ", NumThreadsActive.ToString(), @" - SleepTime: ", SleepTime.ToString(), @" - Counter: ", UnSafeCounter.ToString()));
}
System.Threading.Thread.Sleep(100);
}
IsActive = false;
}
public static object SyncRoot = new object();
public static bool IsActive = true;
public static int SleepTime = 1;
public static long UnSafeCounter = 0;
private static int m_NumThreadsActive;
public static int NumThreadsActive
{
get
{
lock(SyncRoot)
{
return m_NumThreadsActive;
}
}
}
private static void NumThreadsActive_Inc()
{
lock (SyncRoot)
{
m_NumThreadsActive += 1;
}
}
private static void NumThreadsActive_Dec()
{
lock (SyncRoot)
{
m_NumThreadsActive -= 1;
}
}
private static int ThreadsToExit = 0;
private static bool ThreadExitFlag = false;
public static void SignalExitThread()
{
lock(SyncRoot)
{
ThreadsToExit += 1;
ThreadExitFlag = (ThreadsToExit > 0);
}
}
private static bool ExitThread()
{
if (ThreadExitFlag)
{
lock (SyncRoot)
{
ThreadsToExit -= 1;
ThreadExitFlag = (ThreadsToExit > 0);
return (ThreadsToExit >= 0);
}
}
return false;
}
public class ThreadTestClass
{
public ThreadTestClass()
{
System.Threading.Thread RunThread;
RunThread = new System.Threading.Thread(new System.Threading.ThreadStart(ThreadRunMethod));
RunThread.Start();
}
public void ThreadRunMethod()
{
long Counter1;
long Counter2;
long Counter3;
Counter1 = 0;
NumThreadsActive_Inc();
try
{
while (IsActive && (!ExitThread()))
{
UnSafeCounter += 1;
System.Threading.Thread.Sleep(SleepTime);
Counter1 += 1;
Counter2 = UnSafeCounter;
Counter3 = Counter1 + Counter2;
}
}
finally
{
NumThreadsActive_Dec();
}
}
}
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件