C++11 - Threadlere Cpu Tahsisi (Cpu Affinity)


#1

C++ dilinin tercih edilmesinin en önemli sebeplerinden biri de performanstır. Hem düşük seviyeli hem de birden çok programlama yaklaşımına (procedural, OOP) destek vermesi dili farklı bir noktaya taşır.

Performans kayıplarının birçok sebebi olsa da en önemli sebeplerinden biri de ‘context switching’ dir. Yani işletim sisteminin takvimlendirmesine (scheduling) göre çalıştırılabilir dosyanızın farklı cpular veya corelar arasında durumunun değişerek taşınması. Bunu engellemek için herhangi bir core’ a posix thread kütüphanesini kullanarak programınızı atayabilirsiniz, örneğin

   cpu_set_t cpuset;
   CPU_ZERO(&cpuset);
   CPU_SET(core_id, &cpuset); // core_id atamak istediginiz core' un idsi
   pthread_t current_thread = pthread_self();    
   pthread_setaffinity_np(current_thread, sizeof(cpu_set_t), &cpuset);

şeklinde dileğimizi gerçekleştirebiliriz. Fakat yıl olmuş 2018, kimsenin std::thread varken posix threadi kullanmaz diye düşünüyorum :slight_smile:.

std::thread::native_handle

Daha önce bahsettiğimiz gibi std::thread sınıfı posix thread kütüphanesi üzerine bina edilmiş. native_handle fonksiyonu ise direkt olarak pthread_t türünden mevcut threadi döner. Bu bize her ne kadar standart kütüphanede thread affinityi gerçekleştirme imkanımız olmasa da pthread kütüphanesinin fonksiyonlarını kullanmamızı sağlar. O halde,

#include <iostream>
#include <thread>
#include <pthread.h>

using namespace std;

#define  core_id  0

void func()
{
    int loop = 10000000;
    while (loop--) {
        cout << "loop:" << loop << endl;
    }

}

int main()
{
    thread th(func);

    cpu_set_t cpuset;
    CPU_ZERO(&cpuset);
    CPU_SET(core_id, &cpuset); // core_id atamak istediginiz core' un idsi

    pthread_setaffinity_np(th.native_handle(), sizeof(cpu_set_t), &cpuset);

    th.join();
    return 0;
}

şeklinde std::thread şablon sınıfını kullanarak niyetimizi gerçekleyebiliriz.

Unutmadan şu noktalara değinmekte fayda var, eğer cpunuz hyper threading teknolojisine sahipse kapatmak da fayda var. Hyper threading teknolojisi donanımsal coreları çoklayarak birden fazla core gibi kullanmayı sağlar biz de tam olarak bundan kaçıyoruz. Bir diğer optimizasyon ise işletim sistemine de sen sadece şu coreları kullanabilirsin diye ayar yapmak lazım. Aksi halde işletim sistemi sizin kendi programınız için ayırdığınızı core’u da kendi yönettiği herhangi bir process için kullanabilir. Bu da yine context switchinge sebep olabilir.

Bu şekilde kullanmak benim pek hoşuma gitmediği için, ben kendi kullanımım için std::threadi sarmalayan bir sınıf yapısı kullanmak istedim. Bu sınıf yapısı tıpkı std::thread gibi değişken sayıda parametre alabilecek. İsteğe bağlı olarak std::threaddeki diğer yardımcı fonksiyonlar da eklebilir.

#include <thread>
#include <iostream>
#include <pthread.h>

using namespace std;


class DedicatedThread
{
public:
    template< class Function, class... Args>
    explicit DedicatedThread(size_t core, Function&& f,  Args&&... args);
    DedicatedThread() = default;
    DedicatedThread(DedicatedThread &&r);
    DedicatedThread& operator=(DedicatedThread&& r);
    void join();
    ~DedicatedThread();

private:
    size_t  mCore;
    std::thread    mThread;
};


template<class Function, class... Args>
DedicatedThread::DedicatedThread(size_t core, Function&& f,  Args&&... args):
        mCore(core),
        mThread(std::forward<Function>(f), std::forward<Args>(args)...)
{
		cout << "ctor" << endl;
   	  cpu_set_t cpu_set;
          CPU_ZERO(&cpu_set);
          CPU_SET(mCore, &cpu_set);
          pthread_setaffinity_np(mThread.native_handle(), sizeof(cpu_set_t), &cpu_set);
}

DedicatedThread& DedicatedThread::operator=(DedicatedThread&& r)
{
        mThread = std::move(r.mThread);
        mCore = std::move(r.mCore);
   	return *this;
}

void DedicatedThread::join()
{
	if (mThread.joinable())
	    mThread.join();
}


DedicatedThread::~DedicatedThread()
{
	cout << "dtor" << endl;
}

DedicatedThread::DedicatedThread(DedicatedThread &&r)
{

	cout << "move ctor" << endl;
        mThread = std::move(r.mThread);
        mCore = std::move(r.mCore);
}

class A
{

public:

    void  f()
    {
	size_t loop = 100000000000;
        while (loop--) {
            cout << "loop:" << loop << endl;
        }
    }

};


int main()
{
    A a;

    DedicatedThread dt = std::move(DedicatedThread(2, &A::f, &a));
   
    dt.join();

  return 0;
}