Linuxのスケジューリングポリシーによる違いの確認
Linuxのユーザプロセスにて、スレッド生成時やスレッド生成後にスケジューリングポリシーが設定できます。
そのスケジュールリングポリシーによって処理順番がどのように変化するのかを簡単にですが確認しました。
ユーザプロセスで指定できる以下のスケジューリングポリシーで確認
-
SCHED_FIFO
FIFO方式のリアルタイムスケジューリング -
SCHED_RR
ラウンドロビン方式のリアルタイムスケジューリング -
SCHED_OTHER
通常のスケジューリング(リアルタイムではない)
測定環境
- Raspberry Pi 3(ARM 1400MHz*4コア) (Ubuntu MATE 18.04)
- 同一負荷のスレッドを16スレッド立ち上げて測定
測定結果
テキストログでは処理タイミングが確認しにくかったので、タイムラインチャートでログを可視化
いずれのスレッドも、CPU時間は程同一でした。
SCHED_FIFO (優先度高:3スレッド/優先度中:10スレッド/優先度低:3スレッド)
- 優先度の高い順に処理。
- 同一優先度の場合、優先度の高い処理の割込みやブロック処理などがない限りは、一度割り当てられたスレッドが処理を続ける。
SCHED_RR (優先度高:3スレッド/優先度中:10スレッド/優先度低:3スレッド)
- 優先度の高い順に処理。
- 同一優先度の場合、ラウンドロビン方式により期間を過ぎたらCPUを譲る
※始めに動作した優先度中(priority=10)のスレッドが譲っていないのが気になる。
SCHED_RR (優先度低:16スレッド)
- 同一優先度の場合、ラウンドロビン方式により期間を過ぎたら処理を譲る。
※ラウンドロビン方式だがスレッドによって処理終了タイミングにかなりばらつきが出ているのが気になる
SCHED_OTHER
- 通常のスケジューリング。
- タイムシェアスケジューリングで代替均等に処理されている。
SCHED_FIFOとSCHED_OTHER(FIFO:3スレッド/OTHER:11スレッド)
- リアルタイムスケジューリングと通常スケジューリングの混合。
- 4コア環境で測定したため、リアルタイムスケジューリングの3スレッドは優先でCPUが割り当てられ処理され、
空いた1コア分を通常スケジューリングで11スレッドがシェアしている。
ソースコード
確認に使用したソースコードは以下となります。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/times.h>
#include <vector>
using namespace std;
// timespec差分算出関数
inline void sub_timespec(struct timespec* res, const struct timespec* a, const struct timespec* b)
{
if (a->tv_nsec >= b->tv_nsec) {
res->tv_nsec = a->tv_nsec - b->tv_nsec;
res->tv_sec = a->tv_sec - b->tv_sec;
} else {
res->tv_nsec = 1000000000 + a->tv_nsec - b->tv_nsec;
res->tv_sec = a->tv_sec - b->tv_sec - 1;
}
}
class PriorityThread
{
public:
PriorityThread(const char* name, int policy, int priority) {
this->name = name;
this->policy = policy;
this->priority = priority;
}
bool create() {
int ret;
pthread_attr_t attr;
// スレッド属性オブジェクトの初期化
ret = pthread_attr_init(&attr);
if (ret != 0) {
fprintf(stderr, "PriorityThread::create() : pthread_attr_init() ret=%d\n", ret);
return false;
}
// スレッド属性スケジューリング継承:継承しない
ret = pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);
if (ret != 0) {
fprintf(stderr, "PriorityThread::create() : pthread_attr_setinheritsched() ret=%d\n", ret);
return false;
}
// スレッド属性スケジューリングポリシーの設定
ret = pthread_attr_setschedpolicy(&attr, this->policy);
if (ret != 0) {
fprintf(stderr, "PriorityThread::create() : pthread_attr_setschedpolicy(policy=%d) ret=%d\n", this->policy, ret);
return false;
}
// スレッド属性スケジューリングパラメータの設定
struct sched_param param;
param.sched_priority = this->priority;
ret = pthread_attr_setschedparam(&attr, ¶m);
if (ret != 0) {
fprintf(stderr, "PriorityThread::create() : pthread_attr_setschedparam(priority=%d) ret=%d\n", this->priority, ret);
return false;
}
// スレッド作成
ret = pthread_create(&this->tid, &attr, PriorityThread::_run, (void*)this);
if (ret != 0) {
fprintf(stderr, "PriorityThread::create() : pthread_create() ret=%d\n", ret);
return false;
}
// スレッド属性オブジェクトの破棄
pthread_attr_destroy(&attr);
return true;
}
bool join() {
int ret;
ret = pthread_join(this->tid, NULL);
if (ret != 0) {
fprintf(stderr, "PriorityThread::join() : pthread_join() ret=%d\n", ret);
return false;
}
return true;
}
// 測定用の高負荷処理(面倒だったのでfor分繰り返しで)
virtual void* run() {
int64_t ans = 0;
for (int64_t i = 0; i <= 1000000000; i++) ans += i;
}
// スレッド実行開始の制御のためmutexを使用
static bool lockRun() {
pthread_mutex_lock(&PriorityThread::g_thread_block);
}
static bool unlockRun() {
pthread_mutex_unlock(&PriorityThread::g_thread_block);
}
const char* getName() {
return name;
}
void printlog() {
struct timespec elapsed;
printf(" %12s", name);
sub_timespec(&elapsed, &etime_moni, &stime_moni);
printf(": time(%ld.%09ld, %ld.%09ld, %ld.%09ld)", stime_moni.tv_sec, stime_moni.tv_nsec,
etime_moni.tv_sec, etime_moni.tv_nsec,elapsed.tv_sec, elapsed.tv_nsec);
sub_timespec(&elapsed, &etime_tcpu, &stime_tcpu);
printf(": cpu(%ld.%09ld, %ld.%09ld, %ld.%09ld)", stime_tcpu.tv_sec, stime_tcpu.tv_nsec,
etime_tcpu.tv_sec, etime_tcpu.tv_nsec,elapsed.tv_sec, elapsed.tv_nsec);
printf("\n");
}
private:
static void* _run(void* param) {
PriorityThread* obj = ((PriorityThread*)param);
// ロック解除待ち
pthread_mutex_lock(&PriorityThread::g_thread_block);
pthread_mutex_unlock(&PriorityThread::g_thread_block);
// 処理開始時間測定
clock_gettime(CLOCK_MONOTONIC, &obj->stime_moni);
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &obj->stime_tcpu);
// 処理実行
void* ret = ((PriorityThread*)param)->run();
// 処理終了時間測定
clock_gettime(CLOCK_MONOTONIC, &obj->etime_moni);
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &obj->etime_tcpu);
return ret;
}
private:
static pthread_mutex_t g_thread_block;
const char* name;
int policy;
int priority;
pthread_t tid;
struct timespec stime_moni;
struct timespec stime_tcpu;
struct timespec etime_moni;
struct timespec etime_tcpu;
};
pthread_mutex_t PriorityThread::g_thread_block = PTHREAD_MUTEX_INITIALIZER;
int main(void)
{
bool result;
vector<PriorityThread*> threads;
// スレッド定義
threads.push_back(new PriorityThread("Thread_0", SCHED_FIFO , 50));
threads.push_back(new PriorityThread("Thread_1", SCHED_FIFO , 50));
threads.push_back(new PriorityThread("Thread_2", SCHED_FIFO , 50));
threads.push_back(new PriorityThread("Thread_3", SCHED_FIFO , 5));
threads.push_back(new PriorityThread("Thread_4", SCHED_FIFO , 5));
threads.push_back(new PriorityThread("Thread_5", SCHED_FIFO , 5));
threads.push_back(new PriorityThread("Thread_6", SCHED_FIFO , 10));
threads.push_back(new PriorityThread("Thread_7", SCHED_FIFO , 10));
threads.push_back(new PriorityThread("Thread_8", SCHED_FIFO , 10));
threads.push_back(new PriorityThread("Thread_9", SCHED_FIFO , 10));
threads.push_back(new PriorityThread("Thread_A", SCHED_FIFO , 10));
threads.push_back(new PriorityThread("Thread_B", SCHED_FIFO , 10));
threads.push_back(new PriorityThread("Thread_C", SCHED_FIFO , 10));
threads.push_back(new PriorityThread("Thread_D", SCHED_FIFO , 10));
threads.push_back(new PriorityThread("Thread_E", SCHED_FIFO , 10));
threads.push_back(new PriorityThread("Thread_F", SCHED_FIFO , 10));
// スレッド実行開始をロック
PriorityThread::lockRun();
// スレッドの作成
printf("Thread Create.\n");
for (auto item : threads) {
result = item->create();
if (!result) {
fprintf(stderr, "Create Error: name=%s\n", item->getName());
exit(1);
}
}
printf("Thread Create done.\n");
// スレッド実行開始のロック解除
sleep(1);
printf("Thread Run.\n");
PriorityThread::unlockRun();
// スレッドの終了待ち
for (auto item : threads) {
result = item->join();
if (!result) {
fprintf(stderr, "Thread Join Error: name=%s\n", item->getName());
exit(1);
}
}
printf("Thread Join done.\n");
// スレッド実行時間のログ出力
for (auto item : threads) {
item->printlog();
}
// スレッド情報の破棄
for (auto item : threads) {
delete item;
}
threads.clear();
}
それぞれのスケジューリングポリシーを確認時には、
PriorityThread()クラス生成時のパラメータを書き換えて測定しました。
SCHED_FIFOやSCHED_RRの属性を付与する場合、実行時にroot権限が必要になります。
sudoなどで権限を与えて実行してください。
参考サイト
https://access.redhat.com/documentation/ja-jp/red_hat_enterprise_linux/6/html/performance_tuning_guide/s-cpu-scheduler
https://linuxjm.osdn.jp/html/glibc-linuxthreads/man3/pthread_attr_init.3.html