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

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ı. Bu durum aynı zamanda cpunun en hızlı şekilde eriştiği cache bellek alanlarının da sürekli farklı veri ile dolmasına sebebiyet verir. 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 :).

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, kendi kullanımım için std::threadi sarmalayan, daha önce öğrendiklerimizi de kullanarak 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 
#include 
#include 

using namespace std;

class DedicatedThread
{
public:
 template
 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
DedicatedThread::DedicatedThread(size_t core, Function&& f, Args&&... args):
 mCore(core),
 mThread(std::forward(f), std::forward(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;
}

Advertisements

C++14 – Göze çarpanlar

C++14 – Lambda fonksiyonlarında yakalanan ifadelerde ilk değer

C++11 ile gelen özelliklerden biri olan lambda ifadelerinde içinde olduğu bilinirlik alanından value-copy veya reference ile değişkenler, lambda fonksiyonunun gövdesine enjekte edilebiliyordu.

C++14 teki değişiklik ile bu değişkenlere başlangıç değeri de verilebilmekte bu da aslında std::move edilebilmelerinin önünü açıyor.

#include <iostream>
#include <iomanip>


using namespace std;

int main()
{
    int i = 10;
    auto lambda1 = [i = 5] () {
         cout << i << endl;
    };

    lambda1();

    std::unique_ptr ptr(new int(10));
    auto lambda2 = [value = std::move(ptr)] {return *value;};

    cout << lambda2() << endl;

    return 0;
}

C++14 – Variable Template – Değişken şablonları

C++11 de gelen variadic templatelerle derleme zamanında iyi işler çıkarabiliyorduk. C++14’te derleme zamanına değişken şablonlar da (variable template) eklendi. Baya da hoş oldu 🙂

#include <iostream>
#include <iomanip>


using namespace std;

template
constexpr T pi = T( 3.14159265358979323);

template
constexpr const char* pi = "pi";

int main()
{

    cout << setprecision(20);
    cout << pi << endl;
    cout << pi << endl;

    return 0;
}

C++14 – [[deprecated]]
Deprecated olan neredeyse herşeyin bildirimi için C++14 standartlarında dile eklenen özelliktir.


#include <iostream>

using namespace std;

[[deprecated]] int f();

class [[deprecated]] my_class {
public:
    [[deprecated]] int member;
};

[[deprecated]] int i;

enum [[deprecated]] colors{
    RED, BLUE, BLACK
};


[[deprecated]]
typedef int type;

template  class templ;

template 
class [[deprecated]] templ {};

[[deprecated("g() is thread-unsafe. Use h() instead")]]
void g( int& x );

void h( int& x );

int main()
{

    int a = f(); // warning: 'f' is deprecated
    g(a); // warning: 'g' is deprecated: g() is thread-unsafe. Use h() instead
    my_class my;

    return 0;
}

Bu şekilde bildirimi yapılmış fonksiyonların, sınıfların, değişkenleri v.b kullanımı derleyici tarafından uyarı mesajları alınmasına sebep olur.

C++14 – Generic Lambdalar
C++11 ile gelen anonymous fonksiyonlar oluşturmamızı sağlayan lambdalar, C++14 ile birlikte auto anahtar sözcüğünün kullanımı sayesinde daha genel olarak tanımlanabilirler. Örneğin;

#include <iostream>
#include <vector>

using namespace std;

int main()
{

    vector ivec = {1, 2, 4, 4, 5, 6, 7, 8, 2, 8};

    //C++11
    auto printVec11 = [] (std::vector &vec) {
        for (auto i : vec)
            cout << i << " ";
    };

    //C++14
    auto printVec14 = [] (auto &vec) {
        for (auto i : vec)
            cout << i << " ";
    };

    printVec11(ivec);
    cout << "\n";
    printVec14(ivec);

    return 0;
}