Önce bir başlangıç noktası
Bilgisayarın çoğunda pille çalışan küçük bir donanım saati var. Çekirdek açılışta bu saati bir kez okuyor, sonra ağ üzerinden NTP (zaman sunucularıyla saat eşitleme protokolü) ile düzeltiyor. Ama “şu an saat kaç?” sorusu saniyede binlerce kez soruluyor. Her seferinde o yavaş saate gitmek olmaz.
Videoda Matt Godbolt eski günlerden başlıyor. ZX Spectrum ve BBC Micro gerçek saati bilmiyordu. Ekran saniyede 50 kez yeniden çizilirken gelen kesmede (interrupt) bir sayacı artırıyor, açılıştan beri geçen süreyi buradan hesaplıyorlardı.
Sayaçtan saate
Bugünkü işlemcilerde çok daha hızlı bir sayaç var. x86’da adı TSC (time-stamp counter). rdtsc komutuyla tek adımda okunuyor. Godbolt’un bilgisayarında bu sayaç saniyede 3 milyar artıyor.
Saati bulmak için çekirdek şunu hesaplıyor: Son kez kesin olarak bilinen saat, artı o andan beri sayacın ne kadar arttığı, çarpı bir dönüşüm katsayısı. Videoya göre burada bölme yapılmıyor. Bölme yavaş, o yüzden bir tam sayıyla çarpılıp sonuç bitleri sağa kaydırılıyor. “3’e böl” yerine “85 ile çarp, 256’ya böl” gibi.
Katsayı da sabit değil. Kristal ısındıkça ya da soğudukça biraz hızlı ya da yavaş gidiyor. Godbolt kendi makinesini ölçmüş: Tam 3 GHz değil, ona çok yakın. Düzeltmezsen saat günde iki saniye kadar kayar. NTP bu yüzden katsayıyı yavaş yavaş ayarlıyor, saati ileri geri atlatmamaya çalışıyor.
Sistem çağrısı olmadan saat okumak
Normalde çekirdekten bir şey istemek bir sistem çağrısı (syscall) demek: İşlemci ayrıcalıklı moda geçer, sonra geri döner. Saat gibi sık sorulan bir şey için bu çok pahalı. Linux’un çözümü vDSO: Çekirdek her programın belleğine küçük bir kütüphane yerleştiriyor. clock_gettime bu kütüphane üzerinden çalışıyor, gerekli değerleri salt okunur paylaşılan bellekten alıp hesabı programın içinde yapıyor. Godbolt’a göre böyle bir okuma 35 nanosaniye civarında.
Üç ayrı saat
clock_gettime tek bir saat değil, birkaç saat sunuyor. Fark kritik.
- Duvar saati (
CLOCK_REALTIME): Takvimdeki gerçek saat. Yönetici elle değiştirebilir, NTP atlatabilir. Yani geri gidebilir. Bunun nelere mal olduğunu artık saniye yazısında anlattık. - Monoton saat (
CLOCK_MONOTONIC): Ayarlanamıyor ve asla geri gitmiyor. Linux’ta açılıştan beri geçen süreyi sayıyor. Süre ölçmek için doğru saat bu. - CPU zamanı (
CLOCK_PROCESS_CPUTIME_ID): Programının işlemciyi gerçekten ne kadar kullandığı. Bütün iş parçacıklarının (thread) toplamı.
Bu yüzden CPU zamanı duvar saatine eşit değil. Program diski ya da ağı beklerken veya uyurken CPU zamanı ilerlemiyor. Tersine, sekiz çekirdekte paralel çalışan bir program bir saniyede sekiz saniyeye yakın CPU zamanı harcayabilir. Python’da time.perf_counter() monoton saati kullanıyor ve uykuyu da sayıyor; time.process_time() ise sadece CPU zamanını veriyor.
İşlemci seni kandırabilir
Nanosaniyeleri ölçerken bir tuzak daha var. İşlemciler komutları yazıldığı sırayla çalıştırmıyor, girdisi hazır olanı öne alıyor. rdtsc hiçbir şeye bağlı olmadığı için ölçmek istediğin kod bitmeden çalışabiliyor. Intel’in belgeleri de bunu söylüyor: rdtsc önceki komutların bitmesini beklemek zorunda değil. rdtscp öncekileri bekliyor, ama sonraki komutlar yine erken başlayabiliyor. Godbolt’a göre bu yüzden ölçümün iki yanına cpuid ya da bellek bariyerleri (fence) gibi sıralamayı zorlayan komutlar konuyor.
Bir de pratik ipucu: İki sürümü karşılaştırıyorsan nanosaniyeye çevirmen bile gerekmeyebilir. Sayaç farkı, hangisinin hızlı olduğunu zaten söylüyor. Ama önce neyi ölçtüğünü bil. Casey Muratori’nin dediği gibi yavaş yeri tahmin etmek yerine ölçerek bulmak gerekiyor.
Düşün
- Bir web isteğinin süresini
CLOCK_PROCESS_CPUTIME_IDile ölçseydin, veritabanını beklediği süre nereye giderdi? - Kodunda süre ölçmek için takvim saati kullanan bir yer var mı? Saat geri giderse ne olur?
Videoyu izle
Kaynaklar (7, İngilizce)
- Linux man-pages: clock_gettime(2) (yeni sekmede açılır)
- Linux man-pages: vdso(7) (yeni sekmede açılır)
- Linux man-pages: time(7) (yeni sekmede açılır)
- Python docs: time (perf_counter, process_time, monotonic) (yeni sekmede açılır)
- x86 instruction reference (Intel SDM): RDTSC (yeni sekmede açılır)
- x86 instruction reference (Intel SDM): RDTSCP (yeni sekmede açılır)
- Computerphile: Why CPU Time ≠ Wall Clock Time! (video) (yeni sekmede açılır)
Kendi cümlelerimizle yazılmış bir özettir, çeviri değildir. Tüm kaynaklar yukarıda.