Linuxのコンテキストスイッチに費やされた時間を調べるためのcプログラムを書くことはできますか?コードがあれば教えてください。ありがとう
スイッチング時間のプロファイリングは非常に困難ですが、カーネル内のレイテンシプロファイリングツールと、oprofile(カーネル自体をプロファイリングできる)が役立ちます。
インタラクティブなアプリケーションのパフォーマンスのベンチマークとして、予期しないレイテンシスパイクを測定するレイテンシベンチと呼ばれる小さなツールを作成しました。
// Compile with g++ latencybench.cc -o latencybench -lboost_thread-mt
// Should also work on MSVC and other platforms supported by Boost.
#include <boost/format.hpp>
#include <boost/thread/thread.hpp>
#include <boost/date_time.hpp>
#include <algorithm>
#include <cstdlib>
#include <csignal>
volatile bool m_quit = false;
extern "C" void sighandler(int) {
m_quit = true;
}
std::string num(unsigned val) {
if (val == 1) return "one occurrence";
return boost::lexical_cast<std::string>(val) + " occurrences";
}
int main(int argc, char** argv) {
using namespace boost::posix_time;
std::signal(SIGINT, sighandler);
std::signal(SIGTERM, sighandler);
time_duration duration = milliseconds(10);
if (argc > 1) {
try {
if (argc != 2) throw 1;
unsigned ms = boost::lexical_cast<unsigned>(argv[1]);
if (ms > 1000) throw 2;
duration = milliseconds(ms);
} catch (...) {
std::cerr << "Usage: " << argv[0] << " milliseconds" << std::endl;
return EXIT_FAILURE;
}
}
typedef std::map<long, unsigned> Durations;
Durations durations;
unsigned samples = 0, wrongsamples = 0;
unsigned max = 0;
long last = -1;
std::cout << "Measuring actual sleep delays when requesting " << duration.total_milliseconds() << " ms: (Ctrl+C when done)" << std::endl;
ptime begin = boost::get_system_time();
while (!m_quit) {
ptime start = boost::get_system_time();
boost::this_thread::sleep(start + duration);
long actual = (boost::get_system_time() - start).total_milliseconds();
++samples;
unsigned num = ++durations[actual];
if (actual != last) {
std::cout << "\r " << actual << " ms " << std::flush;
last = actual;
}
if (actual != duration.total_milliseconds()) {
++wrongsamples;
if (num > max) max = num;
std::cout << "spike at " << start - begin << std::endl;
last = -1;
}
}
if (samples == 0) return 0;
std::cout << "\rTotal measurement duration: " << boost::get_system_time() - begin << "\n";
std::cout << "Number of samples collected: " << samples << "\n";
std::cout << "Incorrect delay count: " << wrongsamples << boost::format(" (%.2f %%)") % (100.0 * wrongsamples / samples) << "\n\n";
std::cout << "Histogram of actual delays:\n\n";
unsigned correctsamples = samples - wrongsamples;
const unsigned line = 60;
double scale = 1.0;
char ch = '+';
if (max > line) {
scale = double(line) / max;
ch = '*';
}
double correctscale = 1.0;
if (correctsamples > line) correctscale = double(line) / correctsamples;
for (Durations::const_iterator it = durations.begin(); it != durations.end(); ++it) {
std::string bar;
if (it->first == duration.total_milliseconds()) bar = std::string(correctscale * it->second, '>');
else bar = std::string(scale * it->second, ch);
std::cout << boost::format("%5d ms | %s %d") % it->first % bar % it->second << std::endl;
}
std::cout << "\n";
std::string indent(30, ' ');
std::cout << indent << "+-- Legend ----------------------------------\n";
std::cout << indent << "| > " << num(1.0 / correctscale) << " (of " << duration.total_milliseconds() << " ms delay)\n";
if (wrongsamples > 0) std::cout << indent << "| " << ch << " " << num(1.0 / scale) << " (of any other delay)\n";
}
Ubuntu 2.6.32-14-genericカーネルでの結果。測定中、4つのコアでC++コードをコンパイルし、同時にOpenGLグラフィックスでゲームをプレイしていました(より興味深いものにするため)。
Total measurement duration: 00:01:45.191465
Number of samples collected: 10383
Incorrect delay count: 196 (1.89 %)
Histogram of actual delays:
10 ms | >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 10187
11 ms | *************************************************** 70
12 ms | ************************************************************ 82
13 ms | ********* 13
14 ms | ********* 13
15 ms | ** 4
17 ms | *** 5
18 ms | * 2
19 ms | **** 6
20 ms | 1
+-- Legend ----------------------------------
| > 169 occurrences (of 10 ms delay)
| * one occurrence (of any other delay)
Rtパッチが適用されたカーネルを使用すると、かなり良い結果が得られます。
プリントアウトの凡例は、丸め誤差または何かに苦しんでいるように見えます(そして、貼り付けられたソースコードは完全に同じバージョンではありません)。このアプリケーションをリリース用に本当に磨いたことはありません...
スーパーユーザー権限がある場合は、コンテキストスイッチのプローブポイントを使用してSystemTapプログラムを実行し、それぞれの現在時刻を出力できます。
probe scheduler.ctxswitch {
printf("Switch from %d to %d at %d\n", prev_pid, next_pid, gettimeofday_us())
}
出力データの信頼性はわかりませんが、いくつかの数値をすばやく簡単に取得する方法です。
コンテキストの切り替えを秒またはミリ秒、さらにはマイクロ秒で測定してどう思いますか。すべてはナノ秒未満で発生します。測定可能なコンテキストの切り替えに膨大な時間を費やしたい場合は、アセンブリに記述されたリアルモードのカーネルタイプのコードを試してみてください。
短い答え-いいえ。長い答えは以下です。
コンテキストの切り替えは、おおよそ次のいずれかの場合に発生します。
スイッチ自体は一方向であるため、ユーザーランドで実行できる最善の方法(それはあなたが求めていることだと思います)は、RTTの種類をプロセスからプロセスへ、そしてプロセスからプロセスへと測定することです。他のプロセスも、その作業を行うのに時間がかかります。もちろん、これで2つ以上のプロセスを連携させることもできますが、カーネルは、プロセスの1つが次に選択されることを保証しないということです。おそらくRTスケジューラを使用して特定のプロセスに予測どおりに切り替えることは可能ですが、ここではアドバイスがありません。提案は歓迎します。
コンテキストスイッチのコストを測定するのは少し難しいです。 1つのCPUで2つのプロセスを実行し、それらの間に3つのLinuxパイプを設定することで、コンテキストスイッチに費やした時間を計算できます。
次に、最初のプロセスが最初のパイプに書き込みを発行し、2番目のパイプで読み取りを待ちます。 OSは、2番目のパイプから何かが読み取られるのを待っている最初のプロセスを確認すると、最初のプロセスをブロック状態にし、最初のパイプから読み取って2番目のパイプに書き込む他のプロセスに切り替えます。 2番目のプロセスが最初のパイプから再度読み取ろうとすると、ブロックされるため、通信の往復サイクルが続行されます。このような通信のコストを繰り返し測定することで、コンテキストスイッチのコストを適切に見積もることができます。
複数のCPUを搭載したシステムでは、コンテキストスイッチのコストを測定する際に1つの問題が発生します。このようなシステムで必要なことは、コンテキスト切り替えプロセスが同じプロセッサ上にあることを確認することです。幸い、ほとんどのオペレーティングシステムには、プロセスを特定のプロセッサにバインドする呼び出しがあります。たとえば、Linuxでは、sched_setaffinity()呼び出しが探しているものです。両方のプロセスが同じプロセッサ上にあることを確認することにより、OSが1つのプロセスを停止し、同じCPUで別のプロセスを復元するコストを確実に測定できます。
ここでは、プロセス間のコンテキストスイッチを計算するためのソリューションを投稿しています。
#define _GNU_SOURCE
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <sched.h>
#include <stdlib.h>
#include <string.h>
#include <linux/unistd.h>
#include <sys/time.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <errno.h>
pid_t getpid( void )
{
return syscall( __NR_getpid );
}
int main()
{
/*********************************************************************************************
To make sure context-switching processes are located on the same processor :
1. Bind a process to a particular processor using sched_setaffinity.
2. To get the maximum priority value (sched_get_priority_max) that can be used with
the scheduling algorithm identified by policy (SCHED_FIFO).**
**********************************************************************************************/
cpu_set_t set;
struct sched_param prio_param;
int prio_max;
CPU_ZERO( &set );
CPU_SET( 0, &set );
memset(&prio_param,0,sizeof(struct sched_param));
if (sched_setaffinity( getpid(), sizeof( cpu_set_t ), &set ))
{
perror( "sched_setaffinity" );
exit(EXIT_FAILURE);
}
if( (prio_max = sched_get_priority_max(SCHED_FIFO)) < 0 )
{
perror("sched_get_priority_max");
}
prio_param.sched_priority = prio_max;
if( sched_setscheduler(getpid(),SCHED_FIFO,&prio_param) < 0 )
{
perror("sched_setscheduler");
exit(EXIT_FAILURE);
}
/*****************************************************************************************************
1. To create a pipe for a fork, the parent and child processes use pipe to read and write,
read and write string, using this for context switch.
2. The parent process first to get the current timestamp (gettimeofday), then write to the pipe,.
Then the child should be read in from the back,
then the child process to write string, the parent process reads.
After the child process to get the current timestamp.
This is roughly the difference between two timestamps n * 2 times the context switch time.
*******************************************************************************************************/
int ret=-1;
int firstpipe[2];
int secondpipe[2];
int timepipe[2];
int nbytes;
char string[] = "Hello, world!\n";
char temp[] = "Sumit Gemini!\n";
char readbuffer[80];
char tempbuffer[80];
int i=0;
struct timeval start,end;
// Create an unnamed first pipe
if (pipe(firstpipe) == -1)
{
fprintf(stderr, "parent: Failed to create pipe\n");
return -1;
}
// create an unnamed Second pipe
if (pipe(secondpipe) == -1)
{
fprintf(stderr, "parent: Failed to create second pipe\n");
return -1;
}
// Create an unnamed time pipe which will share in order to show time spend between processes
if (pipe(timepipe) == -1)
{
fprintf(stderr, "parent: Failed to create time pipe\n");
return -1;
}
if((ret=fork())==-1)
perror("fork");
else if(ret==0)
{
int n=-1;
printf("Child ----> %d\n",getpid());
for(n=0;n<5;n++)
{
nbytes = read(firstpipe[0], readbuffer, sizeof(readbuffer));
printf("Received string: %s", readbuffer);
write(secondpipe[1], temp, strlen(temp)+1);
}
gettimeofday(&end,0);
n = sizeof(struct timeval);
if( write(timepipe[1],&end,sizeof(struct timeval)) != n )
{
fprintf(stderr, "child: Failed to write in time pipe\n");
exit(EXIT_FAILURE);
}
}
else
{
double switch_time;
int n=-1;
printf("Parent ----> %d\n",getpid());
gettimeofday(&start,0);
/* Read in a string from the pipe */
for(n=0;n<5;n++)
{
write(firstpipe[1], string, strlen(string)+1);
read(secondpipe[0], tempbuffer, sizeof(tempbuffer));
printf("Received temp: %s", tempbuffer);
}
n = sizeof(struct timeval);
if( read(timepipe[0],&end,sizeof(struct timeval)) != n )
{
fprintf(stderr, "Parent: Failed to read from time pipe\n");
exit(EXIT_FAILURE);
}
wait(NULL);
switch_time = ((end.tv_sec-start.tv_sec)*1000000+(end.tv_usec-start.tv_usec))/1000.0;
printf("context switch between two processes: %0.6lfms\n",switch_time/(5*2));
}
return 0;
}