Thread overview
Hassas bir süre ölçer
May 24, 2011
erdem
May 24, 2011
erdem
May 25, 2011
erdem
May 24, 2011

Oyun için her ne kadar amatör de olsa, bana göre şimdilik yetersiz olan oyun döngüsü ve zamanlayıcı için daha iyi alternatifler arama çabalarım sürüyor :)

Ali beyle bu XNA'in oyun için kullandığı zamanlayıcı nasıl çalışıyor diye konuşuyorduk.

Game Timing in XNA Game Studio 2.0 (http://blogs.msdn.com/b/shawnhar/archive/2007/11/23/game-timing-in-xna-game-studio-2-0.aspx)

Yalnız konuya yabancı olan arkadaşlar için belirli kavramları biraz daha açmak istiyorum. Örneğin oyunumuz 60 kare/saniye FPS'de çalışacak olsun. Bu demekki her saniyede 60 kare çizilecek.

1sn = 1000 ms (milisaniye)
1ms = 1000 ns (nanosaniye)

Saniyede 60 kare çizmek istediğimize göre 1000/60 ms = 16.7 milisaniyede bir kare çizdirmek isteyeceğiz.

XNA'in 1.0 sürümünde sistem şu şekilde çalışıyormuş:

  • 60 FPS'de çalışan VSYNC açık 75hz'de çalışan bir monitörümüz var
  • güncelle diyelim ki 3 milisaniyede
  • çiz 5 milisaniye
  • toplam 8 milisaniye tamamlanmış oldu.
  • 60 FPS için zaman aralığı 16.7 ms ve süre dolmadığı için tekrar bir güncelleme yapmıyor
  • çiz tekrar çağrılıyor
  • VSYNC açık olduğu için ekran kartının sürücüsü 13 ms daha bekliyor
  • bu yüzden geç kalmış olduk.

Yani süre tamamlanana kadar 'çiz()' metodunu tekrar tekrar çağırıyormuş.

XNA 2.0 sürümünde doğru anladıysam örneğin 'güncelle()' işlevini tam olarak 3ms'de, 'çiz()' 5ms'de ve geriye kalan 8.7 saniyede bekliyormuş.

Ben de Ali beye örneğin sistemi tam olarak sistemi 3ms nasıl bekletebiliriz diye sormuştum. Hatta bu konuda daha önceden yazdığım bir test programını incelemek isteyebilirsiniz.

https://github.com/erdemoncel/oyun/blob/master/src/test.d

Ali bey de güzel bir örnek yazmış. Bunu sizinle paylaşmak istedim. Ve işin güzel tarafı dilin kendi olanaklarını kullanıyor ve daha hassas bir ölçüm yapıyor. Buradaki birim ne bilmiyorum ama karşılıkları şu şekilde:
'
1 nanosaniye = 1/100
1 mikrosaniye = 10
1 milisaniye = 10_000
1 saniye = 10_000_000
'

import std.stdio;
import core.thread;
import std.datetime;

void main()
{
  ulong[TickDuration] histogram;

  foreach (i; 0 .. 100) {
      StopWatch kronometre;
      kronometre.start();

      Thread.sleep(30_000);    // 3ms bekle
      kronometre.stop();

      auto geçenSüre = kronometre.peek();
      ++histogram[geçenSüre];

      /*
       sleep'in bütün kullanımları:

       Thread.sleep(1000);              // 100 mikrosaniye
       Thread.sleep(dur!"msecs"(10)); // 10 milisaniye
       Thread.sleep(dur!"seconds"(1));  // 1 saniye
      */
  }

  foreach (geçenSüre, adet; histogram) {
      writeln(geçenSüre.usecs, ": ", adet);
  }
}

--
[ Bu gönderi, http://ddili.org/forum'dan dönüştürülmüştür. ]

May 24, 2011

Evet haklısınız. Tekrar düzeltiyorum :)

1 saniye = 1000 milisaniye(ms)
1 milisaniye = 1000 mikrosaniye(µs)
1 mikrosaniye = 1000 nanosaniye(ns)

--
[ Bu gönderi, http://ddili.org/forum'dan dönüştürülmüştür. ]

May 24, 2011

Mikrosaniyeye ne oldu? :-p sleep()'in oradaki kullanımındaki birim 100 nanosaniye.

100 nanosaniye: 1
1 mikrosaniye: 10
1 milisaniye: 10_000
1 saniye: 10_000_000

Ali

--
[ Bu gönderi, http://ddili.org/forum'dan dönüştürülmüştür. ]

May 25, 2011

Alıntı (acehreli):

>

Önemli olan, 3 milisaniye duraklamasını istediğimiz halde aslından değişik sürelerde beklenebiliyor. Ben 6 milisaniye kadar beklendiğini de gördüm.

Ben de test ettiğim sistemde şöyle sonuçlar gördüm

'3000 2845
3100 371
10100 1
'
O zaman demek ki bu süre 10ms bile kadar bile çıkabiliyor.

Alıntı (acehreli):

>

Bazen de bir kaç kere çok uzun beklenmiş olabiliyor (sistem real time olmayınca... :)):

Ama windows da real time değil değil mi :)

Bir de bu kadar task ya da iş parçacığı (thread) mi oluşturuyoruz. Yani yaptığımız 3000'in üzerinde iş parçacığı oluşturup onları 3ms durdurmak mıydı. Yoksa tek bir programı 3000 kere çalıştırıp 3 ms durdurmak mı.

Alıntı (acehreli):

>

O yüzden belki de en iyisi asıl ne kadar beklenmiş olduğunu ölçüp oyunun karelerini ona göre ayarlanan aralıklarla göstermektir.

Bunu biraz daha açabilirmisiniz.

Benim tahminim onlar da örneğin 'güncelle ()' işlevini 3ms bekletmeye çalışıyorlardır. Ama diyelim 6ms mi sürdü. 'çiz()' işlevi de 5ms sürüyor. Bizim bir aralığımız 16.7 ms idi. Hala geriye 5.7 ms kalıyor. Sistem bu sürede de bekliyordur.

Böylece 60 kere 'güncelle()' çağrılmış oldu.

Ama bir de şöyle düşünelim. 'çiz()' işlevi çok zaman alıyor ve 'güncelle() / çiz()' 16ms'den fazla sürüyor. O zaman da oyun saniyede 60 kere 'güncelle()' işlevinin çağrıldığından emin olmak için bazı çizimleri es geçiyordur herhalde :cool:

--
[ Bu gönderi, http://ddili.org/forum'dan dönüştürülmüştür. ]

May 24, 2011

Programla biraz oynadım. Başındaki sabitleri değiştirerek farklı biçimde işletilebiliyor:

import std.stdio;
import core.thread;
import std.datetime;
import std.array;
import std.parallelism;

/* Kaç hane duyarlıkla ölçüleceği */
immutable duyarlık = 2;

/* Mikrosaniye cinsinden denenen süre */
immutable sleepSüresi = 3_000;

/* Saniye cinsinden toplam test süresi */
immutable testSüresi = 10;

ulong[ulong] ölç(double kayıtÇarpanı)
{
   immutable testSüresi_mikro = testSüresi * 1_000_000;
   immutable testAdedi = testSüresi_mikro / sleepSüresi;
   immutable sleepSüresi_100nano = sleepSüresi * 10;

   /* Her sürenin kaç kere çıktığını sayan histogram */
   ulong[ulong] histogram;

   foreach (i; 0 .. testAdedi) {
       StopWatch kronometre;
       kronometre.start();

       Thread.sleep(sleepSüresi_100nano);

       kronometre.stop();

       immutable geçenSüre = kronometre.peek();
       immutable kaydedilecekSüre =
           cast(ulong)(geçenSüre.usecs * kayıtÇarpanı);

       ++histogram[kaydedilecekSüre];
   }

   return histogram;
}

void göster(ulong[ulong] histogram, double kayıtÇarpanı)
{
   auto süreler = histogram.keys;
   süreler.sort;

   foreach (süre; süreler.front .. süreler.back + 1) {
       auto kayıt = süre in histogram;

       /* Grafiğin noktaları eksik kalmasın diye aradaki
        * çıkmayan değerleri 0 olarak gösteriyoruz */
       auto adet = kayıt ? *kayıt : 0;

       writefln("%s %8s", süre / kayıtÇarpanı, adet);
   }
}

void gelişimiGöster()
{
   foreach (i; 1 .. testSüresi + 1) {
       Thread.sleep(dur!"seconds"(1));
       writefln("%%%s", i * 100 / testSüresi);
   }

   writeln();
}

void main()
{
   auto gelişim = task!gelişimiGöster();
   gelişim.executeInNewThread();

   immutable kayıtÇarpanı = 10.0^^(duyarlık - 1) / 1000.0;

   auto histogram = ölç(kayıtÇarpanı);
   gelişim.yieldForce();
   göster(histogram, kayıtÇarpanı);
}

std.parallelism.task'tan yararlanarak yan taraftan da programın gelişimini gösteren bir işlev başlattım.

Önemli olan, 3 milisaniye duraklamasını istediğimiz halde aslından değişik sürelerde beklenebiliyor. Ben 6 milisaniye kadar beklendiğini de gördüm. Bazen böyle mantıklı oluyor:

'3000 1061
3100 2269
3200 3
'

Bazen de bir kaç kere çok uzun beklenmiş olabiliyor (sistem real time olmayınca... :)):

'3000 1221
3100 2106
3200 2
3300 1
3400 0
3500 0
3600 0
3700 0
3800 1
3900 0
4000 0
4100 1
4200 1
'

O yüzden belki de en iyisi asıl ne kadar beklenmiş olduğunu ölçüp oyunun karelerini ona göre ayarlanan aralıklarla göstermektir. (Oyun programlama bilirmiş gibi konuşuyorum. :-p)

Ali

--
[ Bu gönderi, http://ddili.org/forum'dan dönüştürülmüştür. ]

May 25, 2011

Alıntı (erdem):

>

Ama windows da real time değil değil mi :)

"Değil" diyecektim ama hangi Windows olduğuna bağlıymış: :-p

http://en.wikipedia.org/wiki/List_of_real-time_operating_systems

Alıntı:

>

Yoksa tek bir programı 3000 kere çalıştırıp 3 ms durdurmak mı.

Tek iş parçacığı var. İşletim sistemi bizimkine geçtikçe biz "işimiz yok, 3 ms sonra tekrar uyandır" demiş oluyoruz. (Tabii aslında işimiz var: kronometreyle ilgileniyoruz ve süre bilgileri topluyoruz.)

(O programın ikinci iş parçacığı, yeni öğrendiğim std.parallelism'i de denemiş olmak için yazdığım yüzde bilgisi veren gelişimiGöster().)

Alıntı:

>

Alıntı (acehreli):

>

O yüzden belki de en iyisi asıl ne kadar beklenmiş olduğunu ölçüp oyunun karelerini ona göre ayarlanan aralıklarla göstermektir.

Bunu biraz daha açabilirmisiniz.

Ben de emin değilim. :) Bu sefer istediğimizden uzun beklendi diye bir sonrakinde daha kısa sleep() istemeyi düşünüyordum. Ama o zaman karelerin gösterimleri arasındaki süreler yine de farklı olacaktır. Bunun ne kadar önemli olduğunu bilmiyorum. Belki de güncellemeye ve çizmeye zaman kalmadığı farkedildiğinde bu karenin atlanması daha doğru bir çözüm olur.

Denemek gerek. O sırada sistemde yüklü bir program işletip oyunun nasıl göründüğüne bakılabilir.

Alıntı:

>

Benim tahminim onlar da örneğin 'güncelle ()' işlevini 3ms bekletmeye çalışıyorlardır. Ama diyelim 6ms mi sürdü. 'çiz()' işlevi de 5ms sürüyor. Bizim bir aralığımız 16.7 ms idi. Hala geriye 5.7 ms kalıyor. Sistem bu sürede de bekliyordur.

Zaten herhalde bekleme süresini her kare için ölçmek daha doğru olmalı. Oyunun mantığına göre güncellemeler her kare için farklı zaman alabilir. (Çizim de ölçülmeli ama belirli bir grafik kartında herhalde hep aynı zamanda çiziliyordur.)

Hatta daha da iyisi, örneğin 3 kere 1ms beklemek olabilir. Amacımız toplam 3 ms beklemek olsa da örneğin ondan önceki 2 bekleme toplam 2.9 ms tutmuşsa üçüncü beklemeyi yapmayabiliriz. Ama bu sefer de oyunun fazla hızlı gitme riski var. Bilmiyorum; uzman sensin. :)

Alıntı:

>

Böylece 60 kere 'güncelle()' çağrılmış oldu.

Ama bir de şöyle düşünelim. 'çiz()' işlevi çok zaman alıyor ve 'güncelle() / çiz()' 16ms'den fazla sürüyor. O zaman da oyun saniyede 60 kere 'güncelle()' işlevinin çağrıldığından emin olmak için bazı çizimleri es geçiyordur herhalde :cool:

Ben de öyle düşünmüşüm. :)

Ali

--
[ Bu gönderi, http://ddili.org/forum'dan dönüştürülmüştür. ]