--- nono/vm/thread.cpp 2026/04/29 17:05:28 1.1.1.3 +++ nono/vm/thread.cpp 2026/04/29 17:05:54 1.1.1.6 @@ -11,14 +11,19 @@ #include "thread.h" #include "mainapp.h" #include "mythread.h" +#include +#include +#include + +static void PrintSuggestion(int) __unused; // コンストラクタ ThreadDevice::ThreadDevice(uint objid_) : inherited(objid_) { - // スレッド名はデフォルトでこのオブジェクトと同じ名前にしておく。 - // 継承側コンストラクタ等で SetThreadName() で変更してもよい。 - SetThreadName(GetName().c_str()); + // SetName() はこのクラスでオーバーライドしているので + // ここで(改めて)実行する必要がある。 + SetName(GetName()); } // デストラクタ @@ -28,12 +33,20 @@ ThreadDevice::~ThreadDevice() // 継承側のデストラクタで TerminateThread() をそれぞれ呼ぶこと。 } -// スレッド名を設定する -// (ポインタしか管理しないので実体は呼び出し側で責任を持つこと) +// オブジェクト名を設定(更新)するとともにスレッド名も更新する。 +// Object::SetName() のオーバーロード。 +void +ThreadDevice::SetName(const std::string& newname) +{ + inherited::SetName(newname); + SetThreadName(newname); +} + +// スレッドにつけるスレッド名を覚えておく +// (このスレッドにスレッド名を設定する、ではない)。 void -ThreadDevice::SetThreadName(const char *threadname_) +ThreadDevice::SetThreadName(const std::string& threadname_) { - assert(threadname_); threadname = threadname_; } @@ -41,28 +54,50 @@ ThreadDevice::SetThreadName(const char * bool ThreadDevice::StartThread() { - auto func = [this]() { - PTHREAD_SETNAME(threadname); - std::lock_guard lock_sub(this->thread_starter); - this->ThreadRun(); - }; + // ここまでに名前はセットしておくこと。 + assert(threadname.empty() == false); - // スレッド起動 + // スレッド起動。 std::lock_guard lock(thread_starter); - - thread.reset(new std::thread(func)); + try { + thread.reset(new std::thread(&ThreadDevice::OnStart, this)); + } catch (...) { } if ((bool)thread == false) { + warnx("Failed to initialize thread(%s) at %s", + threadname.c_str(), __method__); return false; } return true; } +// 開始されたスレッドでのエントリポイント。 +void +ThreadDevice::OnStart() +{ + // このスレッドにスレッド名を設定。 + PTHREAD_SETNAME(threadname.c_str()); + + // パフォーマンス計測のためのリストに追加。 + // デバッガスレッドはなくてもいいか。 + if (GetId() != OBJ_DEBUGGER && GetId() != OBJ_HOSTCOM_DBG) { + auto thman = gMainApp.GetThreadManager(); + thman->RegistThread(this, threadname); + } + + // 実行開始。 + std::lock_guard lock_sub(this->thread_starter); + ThreadRun(); +} + // スレッドに終了を要求し、その終了を待つ。 // スレッド外から呼ぶこと。 void ThreadDevice::TerminateThread() { if ((bool)thread) { + auto thman = gMainApp.GetThreadManager(); + thman->UnregistThread(this); + Terminate(); if (thread->joinable()) { @@ -73,84 +108,272 @@ ThreadDevice::TerminateThread() } } -// このスレッドアフィニティを設定する。 -void -ThreadDevice::SetThreadAffinityHint(AffinityClass hint) +// このスレッドのスレッドアフィニティを設定する。 +/*static*/ void +ThreadBase::SetThreadAffinityHint(AffinityClass hint) { #if defined(HAVE_PTHREAD_SETAFFINITY_NP) && defined(HAVE_MICPUSET) - auto aff = gMainApp.cpu_affinity; - if (aff == MainApp::CPUAffinity::None) { - return; - } + std::lock_guard lock(exlock); - // hint, aff, cpu_list から cset を作成する。 - // - // aff hint list - // ----- ---- ---- - // Low Heavy 0 - - // Low Heavy 1 - - // High Light 0 - - // High Light 1 - - // ↑このケースは CPU を固定しないので cset の収集自体不要。 - // - // Low Light 0 - - // Low Light 1 set - // High Heavy 0 - - // High Heavy 1 set - // ↑このケースは list[] が true なところを固定。 + // policy, hint から cpuset を作成する。 // - // Perf Light 0 set - // Perf Light 1 - - // Perf Heavy 0 - - // Perf Heavy 1 set - // ↑このケースは (bool)hint と list[] が一致したところを固定。 - - // このスレッドは CPU を固定しない。 - if ((hint == AffinityClass::Heavy && aff == MainApp::CPUAffinity::Low ) || - (hint == AffinityClass::Light && aff == MainApp::CPUAffinity::High)) - { + // policy hint 動作 + // ------- ----- ---- + // Neutral * nop + // Binding Light set Lo (if 高性能コア数 < 3) + // Binding Light nop (if 高性能コア数 >= 3) + // Binding Heavy set Hi + + if (gMainApp.affinity_policy == MainApp::AffinityPolicy::Neutral) { return; } - const std::vector& cpu_list = gMainApp.cpu_list; + const std::vector& fastcore = gMainApp.fastcore; + + // 高性能コアが少ない場合、Light スレッドに高性能コアを奪われたくないので + // Light スレッドを低性能コアに縛っておきたい。 + // 高性能コアの数が多ければ、そこまでする必要もないか。 + if (hint == AffinityClass::Light) { + uint nhigh = std::count(fastcore.begin(), fastcore.end(), true); + if (nhigh >= 3) { + return; + } + } + + // ここまで来ると、このスレッドが Heavy なら高性能コアに、 + // Light なら低性能コアにバインドすることが確定。 MICPUSet cset; - for (int i = 0, end = cpu_list.size(); i < end; i++) { - if (aff == MainApp::CPUAffinity::Perf) { - // Light,Heavy と list が一致していたらセット。 - bool hintb = (hint == AffinityClass::Heavy); - if (hintb == cpu_list[i]) { - cset.Set(i); - } - } else { - // Low,High と Light,Heavy が一致しているので立ってるほうをセット。 - if (cpu_list[i]) { - cset.Set(i); - } + bool target = (hint == AffinityClass::Heavy); + for (uint i = 0, end = fastcore.size(); i < end; i++) { + if (fastcore[i] == target) { + cset.Set(i); } } + // 設定。 int r = PTHREAD_SETAFFINITY(pthread_self(), cset); if (gMainApp.hostcpu->loglevel >= 1) { std::string csetstr; - for (int i = 0, end = cpu_list.size(); i < end; i++) { - csetstr += '0' + (uint)cset.Get(i); + for (uint i = 0, end = fastcore.size(); i < end; i++) { + // 読みやすさのための区切り。先頭の1つは表示時に捨てる。 + uint n = i % 16; + if (n == 0) { + csetstr += '\''; + } + if (cset.Get(i)) { + csetstr += "0123456789ABCDEF"[n]; + } else { + csetstr += '_'; + } } gMainApp.hostcpu->putmsgn("SetAffinity:%-12s(%s) %s%s%s", PTHREAD_GETNAME().c_str(), (hint == AffinityClass::Light ? "Light" : "Heavy"), - csetstr.c_str(), + csetstr.c_str() + 1, (r == 0 ? "" : " = "), (r == 0 ? "" : strerror(r))); + + // この後のエラーメッセージより先にログスレッドが動いたら嬉しい。 + usleep(1); } // エラーなら errno ではなく戻り値にエラー番号が返ってくる。 - // NetBSD では sysctl で許可しないと一般ユーザはこの機能を使えないため - // EPERM で失敗したら以降はもう何もしない。 - if (r == EPERM) { - gMainApp.cpu_affinity = MainApp::CPUAffinity::None; + // NetBSD では sysctl で許可しないと一般ユーザはこの機能を使えない。 + if (r != 0) { + errno = r; + warn("PTHREAD_SETAFFINITY"); + PrintSuggestion(r); + // 本当は終了したいが、この時点からではちょっと面倒なので放置。 + // 以降は何もしない。 + gMainApp.affinity_policy = MainApp::AffinityPolicy::Neutral; } #else // なければ無視する。 #endif } + +/*static*/ std::mutex ThreadBase::exlock; + + +// +// スレッドマネージャ +// + +// コンストラクタ +ThreadManager::ThreadManager() + : inherited(OBJ_NONE) +{ + SetName("ThreadManager"); +} + +// デストラクタ +ThreadManager::~ThreadManager() +{ +} + +// 表示順のスレッド名 +static const std::vector +disp_order = { + "Main", + "Logger", + "SignalThread", + "VM", + "VirtIOBlock0", + "VirtIOBlock1", + "VirtIOBlock2", + "VirtIOBlock3", + "VirtIOBlock4", + "VirtIOBlock5", + "VirtIOBlock6", + "VirtIOBlock7", + "VirtIONet", + "VirtIOSCSI", + "VirtIOEntropy", + "VideoRenderer", + "SoundRenderer", + "HostNet0", + "HostNet1", + "HostCOM0", + "HostCOM1", + "HostCOM2", + "HostSound", +}; + +// スレッドリストに登録する。 +// 登録したいスレッド自身から呼ぶこと。 +void +ThreadManager::RegistThread(Object *obj, const std::string& name) +{ + // パフォーマンス計測のため自スレッドの clockid を取得。 + clockid_t clockid; + pthread_getcpuclockid(pthread_self(), &clockid); + + std::lock_guard lock(threads_mutex); + threads.emplace_back(obj, name, clockid); + // 都度表示順にソートする。 + std::sort(threads.begin(), threads.end(), + [](const ThreadInfo& a, const ThreadInfo& b) { + auto ia = std::find(disp_order.begin(), disp_order.end(), a.name); + auto ib = std::find(disp_order.begin(), disp_order.end(), b.name); + return ia < ib; + } + ); +} + +// スレッドリストから obj を削除する。 +void +ThreadManager::UnregistThread(Object *obj) +{ + std::lock_guard lock(threads_mutex); + + for (auto it = threads.begin(); it != threads.end(); ++it) { + if ((*it).obj == obj) { + threads.erase(it); + break; + } + } +} + +// スレッドの CPU 利用率を記録する。 +void +ThreadManager::CalcPerf(uint64 rtime) +{ + std::lock_guard lock(threads_mutex); + + for (auto& info : threads) { + struct timespec ts; + clock_gettime(info.clockid, &ts); + uint64 t = (uint64)ts.tv_sec * 1_sec + ts.tv_nsec * 1_nsec; + if (__predict_true(last_rtime != 0)) { + // 2回目以降 + uint percent = (t - info.last_clock) * 100 / (rtime - last_rtime); + info.load->EnqueueForce(percent); + } + info.last_clock = t; + } + last_rtime = rtime; +} + +#define hostcpu_putmsg(lv, fmt...) do { \ + if (hostcpu->loglevel >= (lv)) \ + hostcpu->putmsgn(fmt); \ +} while (0) + +#if defined(__x86_64__) +// x86_64 CPU の P コア、E コアを調べて fastcore 配列に返す。 +// fastcore は事前にコア数分確保してあること。 +// 検出できれば fastcore を更新して true を返す。 +// 検出できなければ fastcore は不定で false を返す。 +// 失敗ならエラーメッセージも表示する。 +// MainApp から呼ばれるが、スレッド関連なのでこっちに置く。 +/*static*/ bool +ThreadManager::DetectCPUAffinity_x86(std::vector& fastcore) +{ + Object *hostcpu = gMainApp.hostcpu.get(); + + // CPUID(0x1a, 0) は Intel Core 12世代以降でハイブリッドアーキテクチャ + // 情報を返す。eax の bit31-24 (最上位バイト) が 0x40 なら P コア、 + // 0x20 なら E コア。0 ならたぶんハイブリッド構成でない CPU。 + // + // まずどのコアでもいいので現在のコアで実行してみる。 + // ここでハイブリッド構成だと分かれば、個別のコアについて調べるのだが、 + // ただしそのためのスレッドアフィニティ指定には特権が必要な場合があるので、 + // 特権を要求する作業が必要になるかどうかを先に調べておきたいということ。 + +#if defined(HAVE_PTHREAD_SETAFFINITY_NP) && defined(HAVE_MICPUSET) + // まず現在のコアで。 + uint32 eax, ebx, ecx, edx; + __cpuid_count(0x1a, 0, eax, ebx, ecx, edx); + + if ((eax >> 24) == 0) { + // CPU はハイブリッド構成ではない。 + hostcpu_putmsg(1, "%s: Not a hybrid type core", __func__); + return false; + } else { + // CPU はハイブリッド構成なので、個別のコアについて調べる。 + for (uint i = 0, end = fastcore.size(); i < end; i++) { + int r; + std::thread th([&] { + MICPUSet cset; + cset.Set(i); + r = PTHREAD_SETAFFINITY(pthread_self(), cset); + __cpuid_count(0x1a, 0, eax, ebx, ecx, edx); + if ((eax >> 24) == 0x40) { + fastcore[i] = true; + } + }); + th.join(); + + if (r != 0) { + errno = r; + warn("PTHREAD_SETAFFINITY"); + PrintSuggestion(r); + return false; + } + hostcpu_putmsg(1, "%s: Core%u is %c", + __func__, i, (fastcore[i] ? 'P' : 'E')); + } + return true; + } +#else + hostcpu_putmsg(1, "%s: No PTHREAD_SETAFFINITY", __func__); + return false; +#endif +} +#endif // __x86_64__ + +// PTHREAD_SETAFFINITY() が EPERM になった時に表示するお知らせ。 +// 2箇所から呼ばれるので関数にしてある。 +static void +PrintSuggestion(int errorcode) +{ +#if defined(__NetBSD__) + if (errorcode == EPERM) { + fprintf(stderr, + " ** To allow non-privileged users to set thread affinity on NetBSD:\n" + " ** # sysctl -w security.models.extensions.user_set_cpu_affinity=1\n" + ); + } +#endif +}