Thread overview
Karşılaştırma: synchronized, atomicOp, cas
Nov 14, 2021
Ali Çehreli
Nov 15, 2021
Salih Dincer
Nov 15, 2021
Salih Dincer
Nov 15, 2021
Ali Çehreli
November 14, 2021
Dünkü sohbette baktığımız bir programı geliştirdim. Birden fazla işçinin aynı değişkeni arttırdığı durumda kullanıbilecek yöntemleri karşılaştırıyor.

Örnek çıktı:

--- deneme.korumasız ---
Süre: 36 msecs
Sonuç HATALI: 3,235,173 != 10,000,000

--- deneme.korumalı ---
Süre: 1,352 msecs
Sonuç doğru.

--- deneme.atomicOpİle ---
Süre: 181 msecs
Sonuç doğru.

--- deneme.casİle ---
Süre: 849 msecs
Sonuç doğru.

// ---8<---------------------------------

// Çok sayıda işçi bu değişkeni degiştirecek
shared(int) paylaşılan;

void main() {
  import std.meta;
  import std.traits;

  alias işlevler = AliasSeq!(korumasız, korumalı, atomicOpİle, casİle);

  foreach (işlev; işlevler) {
    enum isim = fullyQualifiedName!işlev;
    dene(isim, &işlev);
  }
}

// Bu, paylaşılan değişkeni doğrudan değiştirdiğinden
// sonuç hatalıdır.
void korumasız(size_t artış) {
  foreach (_; 0 .. artış) {
    // D, hatalı olduğunu bildiğinden
    //
    //   ++paylaşılan
    //
    // yazmamıza izin vermez. O yüzden:

    paylaşılan = paylaşılan + 1;
  }
}

// Bu, synchronized kullandığından en yavaşıdır.
void korumalı(size_t artış) {
  foreach (_; 0 .. artış) {
    // 'synchronized' anahtar sözcüğü, perde arkasında
    //  mutex gibi bir olanaktan yararlanır.

    synchronized {
      paylaşılan = paylaşılan + 1;
    }
  }
}

// Bu, en hızlısıdır.
void atomicOpİle(size_t artış) {
  import core.atomic : atomicOp;

  foreach (_; 0 .. artış) {
    atomicOp!"+="(paylaşılan, 1);
  }
}

// Bu normalde daha hızlıdır ama bu programda çok
// çatışma olduğundan fazla yavaş çıkıyor.
void casİle(size_t artış) {
  import core.atomic : cas;

  foreach (_; 0 .. artış) {
    while (true) {
      // Şu anda gördüğümüz değeri bu değiskende tutacağız
      const görülenDeğer = paylaşılan;

      // cas (compare and swap), "kimse araya girmediyse
      // şu adresteki baytları şunlardan şunlara değiştir"
      // işlemini gerçekleştirir. Şanslı olup olmadığımızı bildirir.

      const değiştirdik_mi = cas(&paylaşılan, görülenDeğer, görülenDeğer + 1);
      if (değiştirdik_mi) {
        break;
      }
    }
  }
}

// Verilen işlevi kullanan işçiler başlatır, zaman tutar, ve
// sonucun doğru olup olmadığını denetler.
void dene(string isim, void function(size_t) işlev) {
  import std.stdio;
  import core.thread;
  import std.concurrency;
  import std.datetime.stopwatch;

  writef!"\n--- %s ---\n[bekliyorum]"(isim);
  stdout.flush();

  enum işçiAdedi = 10;
  enum işçiBaşınaArtış = 1_000_000;
  enum beklenenDeğer = işçiAdedi * işçiBaşınaArtış;

  // Önceki denemenin bıraktığı değeri silmeliyiz.
  paylaşılan = 0;

  auto sw = StopWatch();
  sw.start();

  // Paylaşılan değişkenin değerini değiştirecek olan
  // işçileri başlatıyoruz.
  foreach (_; 0 .. işçiAdedi) {
    spawn(işlev, işçiBaşınaArtış);
  }

  // Sonucu kullanmadan önce bütün işçiler sonlanana
  // kadar beklemeliyiz.
  thread_joinAll();

  const ölçüm = sw.peek();
  enum birim = "msecs";
  int süre;
  ölçüm.split!birim(süre);
  writefln!"\rSüre: %,s %s"(süre, birim);

  if (paylaşılan == beklenenDeğer) {
    writeln("Sonuç doğru.");

  } else {
    writefln!"Sonuç HATALI: %,s != %,s"(
      paylaşılan, beklenenDeğer);
  }
}

Ali

November 15, 2021

On Sunday, 14 November 2021 at 21:35:36 UTC, Ali Çehreli wrote:

>

Dünkü sohbette baktığımız bir programı geliştirdim. Birden fazla işçinin aynı değişkeni arttırdığı durumda kullanıbilecek yöntemleri karşılaştırıyor.

Örnek çıktı:

--- deneme.korumasız ---
Süre: 36 msecs
Sonuç HATALI: 3,235,173 != 10,000,000

--- deneme.korumalı ---
Süre: 1,352 msecs
Sonuç doğru.

--- deneme.atomicOpİle ---
Süre: 181 msecs
Sonuç doğru.

--- deneme.casİle ---
Süre: 849 msecs
Sonuç doğru.

// ------------------------------------

// Çok sayıda işçi bu değişkeni degiştirecek
shared(int) paylaşılan;

void main() {
import std.meta;
import std.traits;

alias işlevler = AliasSeq!(korumasız, korumalı, atomicOpİle, casİle);

foreach (işlev; işlevler) {
enum isim = fullyQualifiedName!işlev;
dene(isim, &işlev);
}
}

Çok güzel bir örnek olmuş hocam! Özellikle AliasSeq ile harika. Tek anlamadığım bir şey var; o da işçiBaşınaArtış değeri nasıl oluyor da 4 işleve size_t artış vasaıtasıyla aktarılıyor! Sanırım spawn()'nın 2. parametresi değil mi?

Dcoder ile bendeki sonuçlar şöyle:

  • dmd shared.d -ofmain.out
  • ./main.out

--- shared.korumasız ---
[bekliyorum]
Süre: 195 msecs
Sonuç HATALI: 3,399,470 != 10,000,000

--- shared.korumalı ---
[bekliyorum]
Süre: 2,197 msecs
Sonuç doğru.

--- shared.atomicOpİle ---
[bekliyorum]
Süre: 919 msecs
Sonuç doğru.

--- shared.casİle ---
[bekliyorum]
Süre: 1,501 msecs
Sonuç doğru.

November 15, 2021

Bu arada son bir not. Bazen sürelerde, atomic ile cas yer değiştiriyor. Belki Dcoder sunucularının anlık işlem gücü değişiklikleri ile ilgili bir şeydir.

İşte yeni sonuçlar:

--- shared.atomic ---
[bekliyorum]
Süre: 1,060 msecs
Beklenen değer (10,000,000) eşit!

--- shared.cas ---
[bekliyorum]
Süre: 809 msecs
Beklenen değer (10,000,000) eşit!

--- shared.synchron ---
[bekliyorum]
Süre: 1,305 msecs
Beklenen değer (10,000,000) eşit!

November 15, 2021
On 11/14/21 8:54 PM, Salih Dincer wrote:

> işçiBaşınaArtış değeri nasıl oluyor da 4
> işleve ```size_t artış``` vasaıtasıyla aktarılıyor! Sanırım spawn()'nın
> 2. parametresi değil mi?

Evet, spawn'ın geri kalan parametreleri verilen işleve kopyalanır.

Yararlı tabii ama insan fiber işlevlerinin öyle olmadıklarını farketmeden edemiyor: Fiber işlevleri parametre almazlar çünkü gereken parametreleri örneğin bir lambda içinde bir araya getirebiliriz. Yani spawn da parametre geçirmeyebilirmiş; küçük bir tutarsızlık...

Ali