31 Mayıs 2017 Çarşamba

Dinamik Programlama - Dynamic Programming

Lineer Programlama - Linear Programming
Lineer programlama bir şeyin min veya max değerini bulmak için kullanılır. Min max ile de işler daha küçük parçalara ayrılarak load balancing yapılır. Linear Programlama halen geçerli bir şey. Açıklaması şöyle
Linear Programming is probably more useful than 50 years ago.

The software for solving linear programming problems has dramatically improved and more and more practical problems can be solved. Most of these practical problems are mixed integer linear programs (MILPs), but linear programming is integral to solving MILPs.

To support this statement, here a few indicators:

The airline industry heavily uses mathematical optimization for solving problems such as pairing optimization and crew scheduling. The same is true for public transport. The exitance of companies like Sabre and Optibus testifies to that.

The best way to find provenly optimal solutions to the traveling salesman problems is based on linear programming: https://www.math.uwaterloo.ca/tsp/concorde.html.

Companies that focus on mathematical optimization with linear programming at their core are thriving, for example Gurobi. Meanwhile, new competitor enter the market for mathematical optimization, specifically linear programming using the simplex algorithm, such as COPT, MindOpt, and Huawei.

Google has increased interest in linear and integer programming, including having their own linear programming solver GLOP.

Amazon is looking for people with skills in linear optimization: https://www.amazon.jobs/de/jobs/1716646/research-scientist.

Dynamic Algoritma Nedir?
Açıklaması şöyle
Dynamic programming is a technique that allows you to divide your problem into smaller subproblems and store the result of the subproblems in order to be able to use them at recurring calculations.
Dynamic Algoritma Ne Zaman Lazım
Dinamik Programlama için şu lazım.
For a problem to be solved using dp, we need overlapping subproblems.
Dynamic Algorita vs Recursion
Açıklaması şöylee
... recursion is similar to dynamic programming. One of the vital differences in a naive recursive solution is that it answers to sub-problems that may be computed multiple times.
Örnek
Elimde şöyle bir kod olsun. Bu kod recursion kullanıyor ancak verimli değil.
int fib(int n)
{
  if (n <= 1)
    return n;
  return fib(n-1) + fib(n-2);
}
Dynamic Algoritma vs Greedy Algoritma
Dinamik Algoritmalar, Greedy algoritmalara göre daha iyi sonuç verebilirler, çünkü her türlü olasılığı denerler.

Dynamic Algoritma Çözümleri
Dinamik Programlama top-down veya bottom-up olarak çözülebilir. top-down olanlar recursion kullanılır. bottom-up olanlar döngü kullanır.

Dinamik Programlama knapsack problemlerinde kullanılabilir. Tüm kombinasyonları denemek yerine, şimdiye kadar hesaplanmış değer üzerinden devam edebilmeyi sağlar.

Örnek
Örneğin aşağıdaki kod parçası dinamik programlama kullanmıyor.
def Fib(n):
  if n <= 1:
    return 1
  else:
    return Fib(n-1) + Fib(n-2)
Eğer top-down olarak çözmek isteseydik şöyle yapardık
// Note: Size is n + 1 because we store the values from 0 to n
unsigned int lookup[n + 1] = {0};
unsigned int fibonacci(unsigned int n) {
  if (n < 2) {
    return n;
  } else if (lookup[n] != 0) {
    return lookup[n];
  }
  lookup[n] = fibonacci(n - 1) + fibonacci(n - 2);
  return lookup[n];
}
Eğer bottom-up olarak çözmek isteseydik şöyle yapardık.
unsigned int fibonacci(unsigned int n) {
  unsigned int lookup[n + 1] = {0};
  lookup[0] = 0;
  lookup[1] = 1;
  for (unsigned int i = 2; i <= n; i++) {
    lookup[i] = lookup[i - 1] + lookup[i - 2];
  }
  return lookup[i];
}
Brute Force Knapsack Algoritması
Knapsack algoritması bir torbaya belli bir kapasiteyi/ağırlığı aşmayacak ancak toplam değeri en fazla olan nesnelerin seçimi problemidir.

Kod olarak şöyle yazılır. Önce bir kapasite değeri tanımlanır.
// This is the total weight limit.
public double Capacity { get; set; }
Daha sonra bu kapasiteyi dolduracak nesnelerin atandığı bir dizi tanımlanır. Böylece kaç tane nesne alabileceğimizi biliriz.
>// This is an array with all the given items.
public Item[] Items { get; set; }
Daha sonra kodun içinde her nesneyi teker teker denediğimiz bir permüstasyon hesaplanır.
var permutations = (long)Math.Pow(2,size);
Kodun brute force olmasının sebebi her nesnenin teker teker denenmesidir.
for (int i = 0; i<permutations; i++)
{ 
    // ...
}
Nihayetinden elimizi şöyle bir kod çıkar
public Data Run(){
  int bestValue = 0;
  int bestPosition = 0;
  int size = Items.Length;

  var permutations = (long)Math.Pow(2,size);

  for (int i = 0; i<permutations; i++){
    int total = 0;
    int weight = 0;
    for (int j = 0; j<size; j++){
       //bit "not included"
       if(((i>>j)&1)!=1)
         continue;
         total += Items[j].v;
         weight += Items[j].w;
    }
    if (weight <= Capacity && total>bestValue){
      bestPosition = i;
      bestValue = total;
    }
  }
  var include = new List<Item>();
  for (int j = 0; j<size; j++){
    if (((bestPosition>>j) & 1)==1)
      include.Add(Items[j]);
  }
  return new Data { BestValue = bestValue, Include = include };

}//End of Run

Knapsack ve Job Scheduling
Bir diğer problem ise belli bir zamanda bitmesi gereken en değerli işleri bitirme algoritması. Problem şöyle çözülüyor.
1. Elimizdeki tüm işleri değerine göre azalacak şekilde sıralıyoruz.
2. Sıradan ilk işi alıyoruz.
3. Bir sonraki iş için süresine bakarak bitip bitmeyeceğini hesaplıyoruz. Eğer zaman dizisinde boşluk varsa iş bitebilir, boşluk yoksa bir sonraki işi deniyoruz.



26 Mayıs 2017 Cuma

getpid metodu

Giriş
Get process id demek. Şöyle yaparız.
pid_t pid = getpid();
İki thread aynı uygulama içinde çalıştıkları için getpid() metodu aynı değeri döner. Eski C kütüphanelerinde NPTL kullanılmadığı için dönmüyor! getpid kernel'da şuna benzer bir şeye tekabül eder.
static inline pid_t sys_getpid(void)
{
  pid_t pid;
  asm volatile("int %1\n"
             : "=a" (pid)
             : "i" (INT_SYS_GETPID)
             : "cc", "memory");
  return pid;
}

Örnek
Şöyle yaparız.
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

void* thread_function (void* arg)
{
    fprintf (stderr, "child thread pid is %d\n", (int) getpid ());
    /* Spin forever. */
    while (1);
    return NULL;
}

int main ()
{
    pthread_t thread;
    fprintf (stderr, "main thread pid is %d\n", (int) getpid ());
    pthread_create (&thread, NULL, &thread_function, NULL);
    /* Spin forever. */
    while (1);
    return 0;
}
Çıktı olarak şunu alırız.
main thread pid is 3615
child thread pid is 3615

Linux Scheduler'ları

Process Scheduler
Process Scheduler koşmaya hazır durumdaki uygulamaları ve thread'leri sıraya koyar ve sırası geleni koşturur. Linux'ta 3 tip scheduling policy var.

Soft Real Time Nedir?
Standart Linux çekirdeği soft real time bir çekirdektir. Hard real time çekirdek elde etmek için RT PREEMPT gibi bazı yamalar kullanmak gerekir.

Soft Real Time zamanlamayı garanti etmez. En basit tabirle best effort olarak çalışır. Örneğin işi %97 oranında 20 ms. içinde tamamlar ancak bazen bu süreyi aşabilir.

Bu scheduler'ların amacı N tane işlemci varsa her zaman N tane en yüksek önceliğe sahip thread'i koşabilir hale getirmektir. Dolayısıyla soft real time sınıfları içinde daha iyi bir preemption elde edilir. Ancak kesin zamanla garantisinin halen verilmediğine dikkat etmek gerekir.

1. SCHED_OTHER
Linu Scheduler'ları yazısına taşıdım

2. SCHED_FIFO
Linu Scheduler'ları yazısına taşıdım

3. SCHED_RR
Linu Scheduler'ları yazısına taşıdım

4. SCHED_DEADLINE
Bunun ne olduğunu bilmiyorum.

POSIX Uygulama İçin Scheduler API'si
Scheduler'lara baktıktan sonra nasıl kullanacağımıza bakalım. POSIX scheduler için bir sürü API tanımlamış.

API'ler scheduler ve thread ile çalışanlar olarak iki grupta toplanabilir. Scheduler ile ilgili olanlar sched_XXX şeklinde. Thread ile ilgili olanlar pthread_XXX şeklinde.

Scheduler Sabitleri
SCHED_OTHER, SCHED_FIFO ve SCHED_RR scheduler tipleri için kullanılan sabitlerdir. Değerleri şöyle
/*
 * Scheduling policies
 */
#define SCHED_NORMAL    0
#define SCHED_FIFO      1
#define SCHED_RR        2
#define SCHED_BATCH     3
/* SCHED_ISO: reserved but not implemented yet */
#define SCHED_IDLE      5

sched_get_priority_min ve sched_get_priority_max
Her scheduler kendi içinde bir öncelik sırası da içerir. Minimum ve maximum öncelik sırası şöyle alınır.
#include <sched.h>
sched_get_priority_min(SCHED_OTHER),
sched_get_priority_max(SCHED_OTHER));
Örnek
Open BSD'den her scheduler'ın priority değeri şöyledir.
Valid priority range for SCHED_OTHER: 0 - 31
Valid priority range for SCHED_FIFO: 0 - 31
Valid priority range for SCHED_RR: 0 - 31
Örnek
Linux'ta her scheduler'ın priority değeri şöyledir. Bu sonuca göre Linux'ta SCHED_OTHER kullanan uygulamalar aynı priority değerine sahiptir. Aralarındaki tek fark nice değeridir.
Valid priority range for SCHED_OTHER: 0 - 0
Valid priority range for SCHED_FIFO: 1 - 99
Valid priority range for SCHED_RR: 1 - 99
Örnek
Şöyle yaparız.
sched_get_priority_max(SCHED_FIFO) = 99
sched_get_priority_min(SCHED_FIFO) = 1
sched_setscheduler
sched_setscheduler metodu yazısına taşıdım.

sched_setparam
sched_setparam ile uygulamanın önceliği atanabiliyor. Numara büyüdükçe, öncelik te artıyor.

POSIX Thread İçin Scheduler API'si
pthread_getschedparam metodu
Şöyle yaparız.
sched_param param;
int policy;
if(pthread_getschedparam(pthread_self(), &policy, &param) == 0) {
  ...
}
pthread_setschedparam metodu
Bu metod ile thread için yukarıda anlatılan scheduler'lardan birisi seçilebilir. Birinci parametre thread handle, ikinci parametre policy  (SCHED_FIFO vb.), üçüncü parametre sched_param yapısı.

Bu yapı ile sched_priority de atanabilir. Eğer istersek önce pthread_getschedparam () çağrısı ile mevcut değeri alıp aynen kullanabiliriz.

Bir projede policy olarak SCHED_FIFO ve sched_param.priority = 99 yapmıştım. Eğer çağrı başarılı ise 0 döner. Başarısız ise 0'dan farklı bir sonuç döner. Şöyle yaparız.
int priority = 99;
int policy = SCHED_FIFO;

sched_param param {};
param.sched_priority = priority;

int ret = pthread_setschedparam(pthread_self(), policy, &param);
if(ret != 0) { 
  ...Error...
}

pthread_setschedprio
Bu metod ile thread için öncelik sırası atanır. Örnekte direkt sched_get_priority_max kullanılmamış. Bunun yerine önce scheduler bulunuyor. Daha sonra en yüksek öncelik değeri bulunuyor ve bu değer thread'e atanıyor.
#include <pthread.h>

int main()
{
    pthread_t thId = pthread_self();
    pthread_attr_t thAttr;
    int policy = 0;
    int max_prio_for_policy = 0;
    //Scheduler'ı öğren
    pthread_attr_init(&thAttr);
    pthread_attr_getschedpolicy(&thAttr, &policy);
    max_prio_for_policy = sched_get_priority_max(policy);

    //Thread için en yüksek önceliği ata
    pthread_setschedprio(thId, max_prio_for_policy);
    pthread_attr_destroy(&thAttr);

    return 0;
}

Diğer Platformlar
Yazının bütünlüğü açısından diğer platformlarda da thread önceliğinin nasıl değiştirildiğine bakmak gerekir. Windows'ta da Linux'ta olduğu gibi scheduler'lar var. Yine aynı şekilde thread önceliğini değiştirmek mümkün.

Windows
SetPriorityClass
Bu metod ile farklı bir scheduler kullanılıyor.
SetPriorityClass(HProcess, REALTIME_PRIORITY_CLASS);
SetThreadPriority
Bu metod ile thread'in önceliği değiştiriliyor.Windows'ta thread önceliği için şu sabitler kullanılır.
  • THREAD_PRIORITY_IDLE (-15)
  • THREAD_PRIORITY_LOWEST (-2)
  • THREAD_PRIORITY_BELOW_NORMAL (-1)
  • THREAD_PRIORITY_NORMAL (0)
  • THREAD_PRIORITY_ABOVE_NORMAL (1)
  • THREAD_PRIORITY_HIGHEST (2)
  • THREAD_PRIORITY_TIME_CRITICAL (15)
SetThreadPriority(HThread, THREAD_PRIORITY_TIME_CRITICAL);
Bir başka örnek
SetThreadPriority(GetCurrentThread(),THREAD_PRIORITY_HIGHEST);
Bir projede THREAD_PRIORITY_TIME_CRITICAL kullanmıştım.
Java
Thread.setPriority yazısına taşıdım

Process Scheduler ve CPU Affinity
Bir uygulamayı veya thread'i sistemdeki CPU'ya bağlamak için programın programlarken belli metodlar kullanılabilir veya komut satırından işlem gerçekleştirilebilir. Ancak unutulmaması gereken nokta, bir uygulamayı CPU'ya bağlamak diğer uygulamaların bu CPU'yu kullanmasını engellemez!

Linux
sched_setaffinity metodu yazısına taşıdım.

GNU
pthread_setaffinity_np
pthread_setaffinity_np metodu yazısına taşıdım

Komut Satırı
Linux'ta taskset komutu kullanılır.

Windows
SetProcessAffinityMask veya SetThreadAffinityMask kullanılabilir.


Process Scheduler ve Pre-Emptive Multitasking
Bu soruda Process Scheduler'ın donanım tarafından periyodik olarak çağırılan bir kesmeyi kullandığı, çalışan bir uygulamayı yarıda kesebileceği ve sırayı bir başka uygulamaya verebileceği anlatılıyor. Dolayısıyla burada da açıklandığı gibi Process Scheduler bir thread gibi değilde, direkt işlemci üzerinde koşuyor gibi düşünülmeli.


Çalışan uygulamayı yarıda kesme işlemine Pre-emptive multitasking deniyor. Türkçesi ise sanırım geçişli çoklu görev olarak kullanılıyor. Understanding the Linux Kernel bölüm 10'da güzel bilgiler mevcut.

Thread scheduling Round Robin / scheduling dispatch sorusunda ise kesme koduna ait bir örnek var.

Pre-emptive multitasking ile ses kartları arasında ilginç bir ilişki var. Bazen CPU ses kartının tampon belleğini doldurup başka işlerle meşgul olsa bile ses kartının donanımı müziği çalmaya devam edebiliyor.

I/O Scheduler
Process Scheduler sadece koşmaya hazır/koşan durumdaki uygulamalar ile ilgilenirken I/O Scheduler giriş/çıkış işlemleri için bekleyen uygulamaları yönetir.