もともと、コンテキストスイッチのオーバーヘッドはTLBがフラッシュされることだと考えていました。しかし、私はウィキペディアで見ました:
http://en.wikipedia.org/wiki/Translation_lookaside_buffer
2008年、Intel(Nehalem)[18]とAMD(SVM)[19]の両方が、TLBエントリの一部としてタグを導入し、ルックアップ中にタグをチェックする専用ハードウェアを導入しました。これらは完全に活用されていませんが、将来、これらのタグはすべてのTLBエントリが属するアドレス空間を識別すると想定されています。 したがって、コンテキストスイッチではTLBのフラッシュは行われませんが、現在のアドレススペースのタグをアドレススペースのタグに変更するだけです新しいタスク。
上記は、新しいIntel CPUの場合、TLBがコンテキストスイッチでフラッシュされないことを確認していますか?
これは、コンテキストスイッチに実際のオーバーヘッドがないことを意味しますか?
(コンテキストスイッチのパフォーマンスペナルティを理解しようとしています)
ウィキペディアが知っているように そのコンテキストスイッチで 記事、「コンテキストスイッチは、同じ状態から実行を再開できるようにプロセスの状態(コンテキスト)を保存および復元するプロセスです。後でポイントします。」。同じOSの2つのプロセス間でコンテキストを切り替えることを想定します。ユーザー/カーネルモードの移行(syscall)は、はるかに高速であり、TLBフラッシュを必要としません。
そのため、OSカーネルが現在実行中のプロセスの実行状態(すべて、実際にはすべて、レジスタ、および多くの特別な制御構造)をメモリに保存してから、他のプロセスの実行状態を読み込む(メモリから読み込む)には多くの時間が必要です。 TLBフラッシュは、必要に応じてスイッチに時間がかかりますが、総オーバーヘッドのほんの一部です。
コンテキストスイッチのレイテンシを見つけるには、lmbench
ベンチマークツールがあります http://www.bitmover.com/lmbench/ with LAT_CTX test 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 with 1-コンテキスト切り替えのための3マイクロ秒。彼の結果からTLBをフラッシュしないことの正確な効果を得ることができません。
[〜#〜] update [〜#〜]-プロセスコンテキストの切り替えではなく、仮想化について質問する必要があります。
RWTは Nehalemに関する記事 「Nehalemの内部:Intelの未来のプロセッサとシステム。TLB、ページテーブルと同期」2008年4月2日、David Kanterが、NehalemはTLBにVPIDを追加して仮想マシンを作成したと述べています/ Hostスイッチ(vmentry/vmexit)の高速化:
NehalemのTLBエントリも、「仮想プロセッサID」またはVPIDの導入により微妙に変更されました。すべてのTLBエントリは、仮想アドレスから物理アドレスへの変換をキャッシュします。この変換は、特定のプロセスおよび仮想マシンに固有です。 Intelの古いCPUは、プロセッサが仮想化ゲストとホストインスタンスを切り替えるたびにTLBをフラッシュし、プロセスがアクセスを許可されたメモリにのみアクセスするようにしました。 VPIDは、TLBの特定の変換エントリがどのVMに関連付けられているかを追跡するため、VMの終了および再入力が発生した場合、安全のためにTLBをフラッシュする必要はありません。 .... VPIDは、VM遷移のオーバーヘッドを下げることにより、仮想化のパフォーマンスに役立ちます。 Intelは、ネハレムでの往復VM遷移のレイテンシは、Merom(つまり65nm Core 2)と比較して40%で、45nm Penrynよりも約3分の1低いと推定しています。
また、質問で引用されたフラグメントの「[18]」リンクは「G.ナイガー、A。サントーニ、F。レオン、D。ロジャース、およびR. Uhligにリンクしていることを知っておく必要があります。IntelVirtualizationテクノロジ:効率的なプロセッサ仮想化のハードウェアサポート。Intel Technology Journal、10(3)。 "、これは効果的な仮想化のための機能です(高速ゲストホストスイッチ)。
キャッシュの無効化(通常、実際のコンテキスト切り替えコストに対する最大の貢献者)を考慮すると、コンテキスト切り替えによるパフォーマンスのペナルティは非常に大きくなる可能性があります。
https://www.usenix.org/legacy/events/expcs07/papers/2-li.pdf (確かに少し時代遅れですが、見つけることができた最高の範囲) 100K-1M CPUサイクルの。理論的には、64バイトのキャッシュラインで構成される32M L3ソケットごとのキャッシュ、完全ランダムアクセス、およびメインRAMのL3/100サイクルで40サイクルの典型的なアクセス時間を持つマルチソケットサーバーボックスの最悪の場合、ペナルティは最大30M + CPUサイクル(!)に達する可能性があります。
個人的な経験から、それは通常数十Kサイクルの範囲にあると思いますが、詳細によっては、桁違いに異なる場合があります。
注:ブレンダンが彼のコメントで指摘したように。この回答の目的は、詳細に回答することです(WindowsとLinuxとSolarisなどで異なるオペレーティングシステムのオーバーヘッドを含む、Windowsサーバー/デスクトップパフォーマンスに対するコンテキストスイッチの全体的な影響は何ですか?。
見つけるための最良の方法は、もちろんそれをベンチマークすることです。ここでの問題は、1秒あたりのコンテキストスイッチの数とCPU時間の関係が指数関数的であることです。つまり、O(n2) 費用。つまり、単純に超えられない最大制限があります。
次のベンチマークコードでは、いくつかの安全でない変数などを使用しています。これはポイントではないため、無視してください。
スレッドごとに行われる実際の作業は最小限です。理論的には、各スレッドは毎秒1000のコンテキストスイッチを生成する必要があります。
2560スレッドが約2%のCPUを生成するはずですが、代わりにCPUは2300スレッドで100%になります(Core i7-4790K 4コア+ 4ハイパースレッドコアデスクトップマシン上)。
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();
}
}
}