3 puan yazan GN⁺ 2024-10-20 | 1 yorum | WhatsApp'ta paylaş
  • Linux çekirdeği, throughput ile yanıt süresi arasındaki ödünleşim için çeşitli preemption modlarını uzun süredir koruyor ve Peter Zijlstra’nın yeni yama setiyle tembel preemption (PREEMPT_LAZY) tartışması yeniden ciddi biçimde hız kazanıyor
  • Mevcut PREEMPT_NONE, PREEMPT_VOLUNTARY, PREEMPT_FULL, PREEMPT_RT modları, preemption’a izin verilen kapsam bakımından farklılık gösteriyor; preemption ne kadar sık olursa yanıt verebilirlik o kadar artabiliyor, ancak bunun throughput ve lock rekabeti üzerinde maliyeti büyüyor
  • PREEMPT_LAZY, TIF_NEED_RESCHED_LAZY bayrağıyla “yeniden zamanlama gerekli ama hemen değil” durumunu işaretliyor ve preemption’ın çoğunu zamanlayıcı tikine kadar erteliyor
  • Uzun vadede amaç, gerçek zamanlı olmayan preemption modlarını PREEMPT_LAZY ve PREEMPT_FULL ile sınırlamak ve çekirdeğin çeşitli yerlerindeki cond_resched() çağrılarının büyük bölümünü kaldırmak
  • Mevcut yama seti için hâlâ kararlılık, çağrı noktalarının gözden geçirilmesi ve performans testleri gerekiyor; ilk testlerde PREEMPT_LAZY throughput’u PREEMPT_VOLUNTARY’nin biraz gerisinde kalıyor

Linux çekirdeğinin mevcut preemption modları

  • Mevcut çekirdek, çalışan bir görevin başka bir görev tarafından ne zaman preempt edilebileceğini kontrol eden çeşitli preemption modları sunuyor
    • PREEMPT_NONE: çalışan görevin yalnızca zaman dilimini tamamen kullandığında preempt edilmesine izin veren en basit mod
    • PREEMPT_VOLUNTARY: çekirdek içine gerektiğinde preempt edilebilecek çok sayıda nokta ekleyen mod
    • PREEMPT_FULL: spinlock tutulması gibi çekirdeğin engellediği bölümler dışında neredeyse her noktada preemption’a izin veren mod
    • PREEMPT_RT: çoğu şeyin önüne preemption’ı koyan ve spinlock tutan kodun büyük bölümünü de preempt edilebilir hâle getiren mod
  • Preemption seviyesi yüksek olduğunda, fare hareketi ya da bir nükleer reaktörde yaklaşan anormallik sinyali gibi olaylara daha hızlı tepki verilebiliyor
  • Ancak preemption sıklaştıkça, uzun süre çalışan CPU yoğun işlerin toplam throughput değeri düşebiliyor ve lock rekabeti artabiliyor
  • Birçok dağıtım, çekirdeği PREEMPT_DYNAMIC sahte modu ile derliyor
    • Önyükleme sırasında yukarıdaki üç gerçek zamanlı olmayan moddan biri seçilebiliyor
    • Varsayılan değer PREEMPT_VOLUNTARY
    • debugfs bağlanmış sistemlerde mevcut mod /sys/kernel/debug/sched/preempt üzerinden görülebiliyor

cond_resched() neden gerekliydi?

  • PREEMPT_NONE ve PREEMPT_VOLUNTARY, çekirdek kodu çalışırken rastgele preemption’a izin vermiyor
  • Çekirdek içinde uzun işler sürdüğünde, en düşük gecikmenin öncelik olmadığı sistemlerde bile aşırı gecikme ortaya çıkabiliyor
  • Bunu önlemek için uzun çalışan döngülerin çeşitli yerlerine cond_resched() çağrıları eklendi
    • Her çağrı ek bir gönüllü preemption noktası
    • PREEMPT_NONE modunda da çalışıyor
    • Çekirdekte bu tür yüzlerce çağrı var
  • Bu yaklaşım, yalnızca geliştiricilerin yerleştirdiği noktalarda çalışan bir heuristic
    • Gereksiz çağrılar olabilir
    • Gerekli yerlere çağrı eklenmemiş olabilir
    • Zamanlama kararı mantığının çekirdek kodunun tamamına yayılmasına yol açıyor

Tembel preemption’ın temel çalışma biçimi

  • Çekirdek, mevcut görevin preempt edilip edilemeyeceğine karar verirken birden fazla değişkeni birlikte değerlendiriyor
  • Bunlardan TIF_NEED_RESCHED, daha yüksek öncelikli bir görevin CPU erişimi beklediğini gösteren bir bayrak
    • Daha yüksek öncelikli bir görev uyandığında, bu bayrak o anda çalışan göreve ayarlanabiliyor
    • Bu bayrak yoksa çekirdeğin mevcut görevi preempt etmesine gerek kalmıyor
  • Çekirdek çeşitli noktalarda TIF_NEED_RESCHED değerini kontrol ederek mevcut görevi preempt edebiliyor
    • Zamanlayıcının timer tick’i
    • Sistem çağrısından sonra kullanıcı alanına dönerken
    • Interrupt handler tamamlandığında
    • cond_resched() çağrısında
  • Tembel preemption yaması yeni bir bayrak, TIF_NEED_RESCHED_LAZY, ekliyor
    • Yeniden zamanlama gerekiyor ama mutlaka hemen yürütülmesi gerekmiyor anlamına geliyor
    • PREEMPT_LAZY modunda olayların çoğu TIF_NEED_RESCHED yerine bu yeni bayrağı ayarlıyor
  • Çekirdekten kullanıcı alanına dönülen noktalarda, bu iki bayraktan yalnızca birinin ayarlı olması bile zamanlayıcının çağrılmasına yol açıyor
  • Gönüllü preemption noktaları ve interrupt dönüş yollarında ise yalnızca TIF_NEED_RESCHED kontrol ediliyor

PREEMPT_LAZY’nin getirdiği ödünleşim

  • PREEMPT_LAZY modunda, çekirdek içindeki çoğu olay mevcut görevi hemen preempt etmiyor
  • Bunun yerine timer tick işleyicisi TIF_NEED_RESCHED_LAZY ayarlı mı diye bakıyor
    • Ayarlıysa TIF_NEED_RESCHED de ayarlanıyor
    • Sonuç olarak çalışan görev preempt edilebiliyor
  • Görevler, genellikle CPU’yu gönüllü olarak bırakmadıkları sürece, kendi zaman dilimlerine yakın bir süre çalışıyor
    • Bunun iyi throughput sağlaması bekleniyor
  • Bu değişiklikle PREEMPT_LAZY de PREEMPT_FULL gibi, çekirdek preemption’ı neredeyse her zaman etkinmiş gibi çalışabiliyor
    • Preemption sayacı izin verdiği sürece her an preempt edilebiliyor
    • Başka koşullar engel olmuyorsa uzun süre çalışan çekirdek kodu da preempt edilebiliyor
  • Gerçekten anında preemption gereken durumlarda erteleme yapılmıyor
    • Örneğin interrupt işleme sonucunda bir gerçek zamanlı görev çalışabilir hâle gelirse TIF_NEED_RESCHED ayarlanıyor
    • Bu durumda timer tick beklenmeden neredeyse anında preemption gerçekleşiyor
  • Yalnızca TIF_NEED_RESCHED_LAZY ayarlıysa preemption olmuyor
    • Bu nedenle PREEMPT_LAZY çekirdeği, PREEMPT_FULL çekirdeğine göre çalışan görevi preempt etme olasılığı çok daha düşük

cond_resched() kaldırılana kadar kalan işler

  • Uzun vadeli hedef, gerçek zamanlı olmayan preemption modlarını ikiye indirmek
    • PREEMPT_LAZY
    • PREEMPT_FULL
  • PREEMPT_LAZY, PREEMPT_NONE ile PREEMPT_VOLUNTARY arasında konumlanıyor ve bu ikisinin yerini alması planlanıyor
  • Preemption neredeyse her yerde mümkün hâle geldiğinde, belirli noktalara ayrıca gönüllü preemption noktaları ekleme ihtiyacı azalıyor
  • Şu anda cond_resched() çağrıları hâlâ duruyor
    • PREEMPT_NONE ve PREEMPT_VOLUNTARY var oldukça bunlara ihtiyaç var
    • Ayrıca tembel preemption kararlı hâle getirilirken sorun çıkmamasına yardımcı oluyorlar
  • Mevcut yama setinde cond_resched() yalnızca TIF_NEED_RESCHED değerini kontrol ediyor
    • Bu yüzden PREEMPT_VOLUNTARY ya da PREEMPT_NONE altında hemen preempt edilecek birçok durum artık gecikebiliyor
  • Steve Rostedt, özellikle PREEMPT_VOLUNTARY için cond_resched() eski anlamını korursa geçişin daha kolay olup olmayacağını sordu
  • Thomas Gleixner, yalnızca TIF_NEED_RESCHED kontrol edilmesi tercihinin doğru olduğunu düşünüyor
    • Çünkü bu, tüm cond_resched() çağrılarının tek tek gözden geçirilmesini zorunlu kılıyor
    • Tembel bit kontrolü gerekmeyen çağrılar, PREEMPT_LAZY uygulanırken kaldırılabilir
    • Tembel bit kontrolü gerektiren çağrılar ise kalmalı
  • Gleixner, cond_resched() çağrılarının %5’inden azının TIF_NEED_RESCHED_LAZY kontrolüne ihtiyaç duyacağını öngörüyor
  • Geçiş tamamlanmadan önce yüzlerce cond_resched() çağrısının incelenmesi ve bunların çoğunun kaldırılması gerekiyor
  • Ankur Arora’nın ayrı bir yama seti, buna ilişkin bazı ayrıntıları ele alıyor
  • Kapsamlı performans testleri de gerekli
    • Mike Galbraith’in ilk testlerinde tembel preemption throughput’u PREEMPT_VOLUNTARY’nin biraz altında kaldı

Nihai hedef

  • Tembel preemption çalışmasının sonucunda çekirdek biraz daha küçük ve basit hâle gelebilir
  • Amaç, zamanlayıcıyla ilgili çağrıları kod tabanının her yanına serpiştirmeden öngörülebilir gecikme süreleri sunan bir çekirdek elde etmek
  • Mevcut yaklaşım daha iyi bir çözüm gibi görünüyor, ancak o noktaya ulaşmak için hâlâ zamana ihtiyaç var

1 yorum

 
GN⁺ 2024-10-20
Hacker News yorumları
  • Umut verici görünüyor. EEVDF gibi mevcut durumu sadeleştirirken iyileştiren bir yön olduğu için bundan daha iyisi zor olurdu.

  • Öncelik alma düzeyinin neden küresel bir mod değil de belirli bir olayın özelliği olmadığını merak ediyorum. Bazı olayların diğerlerinden daha düşük gecikmeyle işlenmesi gerekir.

    • Bir olayın önceliğini değerlendirmek için önce CPU zamanı gerekir. Bu değerlendirme ancak o anda CPU’da çalışan süreç kesildikten sonra yapılabilir.
      Dolayısıyla bir olayın sahip olabileceği en yüksek öncelik bile, programın bağlam değişimi yaşamadan önce alabileceği zaman diliminin ne kadar kısa olduğuyla sınırlıdır. Herhangi bir tür olaya düşük gecikmeyle güvenilir biçimde yanıt vermek için, CPU yoğun tüm programların o olay ne kadar nadir olursa olsun her zaman bir performans bedeli ödemesi gerekir.
    • Burada kolayca karıştırılabilecek iki kavram var. Biri bir sürecin önceliğinin alınabileceği an, diğeri ise gerçekten önceliğinin alınıp alınmayacağıdır.
      Olası öncelik alma noktaları zamanlayıcının bir özelliğidir ve burada küresel mod olarak tartışılan da budur. Öncelik alma noktaları arttıkça elbette bir sürecin uygunsuz bir anda önceliğinin alınma olasılığı artar; ama aynı zamanda öncelikleri doğru yansıtma fırsatı da artar. Soruda bahsedilen öncelik alma düzeyi, yani zamanlayıcının verdiği öncelik, gerçekten de sürecin bir özelliğidir ve ayarlanabilir. Linux’un varsayılan zamanlayıcısı da önceliği olan süreçlere daha fazla zaman dilimi vermeye ve diğer süreçlerin önceliğini daha az almaya çalışır.
    • Makalede açıklanan PREEMPT_VOLUNTARY bir ölçüde bu yönde bir denemeydi ve artık kullanımdan kaldırılmakta olduğu söylenebilir.
    • Bu yama bir ölçüde o rolü üstleniyor. https://lwn.net/ml/all/20241008144829.GG14587@noisy.programm... bağlantısına göre:
      SCHED_IDLE, SCHED_BATCH, SCHED_NORMAL/OTHER gecikmeli öncelik alma kullanıyor; FIFO, RR, DEADLINE ise mevcut Full davranışını kullanıyor.
    • Böyle bir sistem, programların birbirleriyle “ben önemliyim” diyerek öncelik isteme kavgası yaratmasına çok açık olur. Gerçekçi olarak büyük şirketlerin bunu “daha iyi” kullanıcı deneyimi için kullanma olasılığı yüksek.
      Bu yüzden çalışan uygulama sayısını en aza indirmek ya da çoğu kullanıcının yaşadığı kısa anlar için elle kontrol etmek önemli. CPU yoğun işler de gerçek anlamda verimli kaynak kullanımından ziyade kötü kod olma ihtimalini daha sık taşıyabilir. Oyunlarda performansa öncelik vermek gerekir; ama çoklu görev için sistemi durma noktasına getirmemek gibi hassas bir denge gerekir. Her hâlükârda bu esasen boşta kalan işler için olduğundan, kullanıcının bir betikten çeşitli davranışları açıp kapatabileceği basit bir komut vermekten daha fazla otomasyona pek gerek yok gibi görünüyor.
  • “Mevcut çekirdekte bir görevin başka bir görev için ne zaman önceliğinin alınabileceğini ayarlayan dört mod var” deniyor; bunun çekirdek görevleri için mi, yoksa kullanıcı görevlerini de kapsayıp kapsamadığını merak ediyorum.

    • Çekirdek kodundan bahsediliyor. Kullanıcı alanı kodu her zaman önceliği alınabilir durumdadır.
  • Yamanın gönderildiği bağlantılı dizide sayıları bulamadım. Bu değişikliğin pratik potansiyelini gösterecek ilk benchmarkların artık yapılmış olmasını beklerdim, merak ediyorum.

    • Makalenin sondan ikinci paragrafında geçiyor.
      Kapsamlı performans testlerinin gerektiği, Mike Galbraith’in ilk çalışmaya başladığı ve gecikmeli öncelik almanın iş hacminin PREEMPT_VOLUNTARY’den biraz düşük olduğunu gösteren sonuçlar sunduğu söyleniyor.
    • Böyle bir şeyin nasıl benchmark edileceğini merak ediyorum. Birden çok süreci aynı anda çalıştırıp toplam çalışma süresine göre sıralamak mı gerekir, yoksa tek tek süreçlerin bekleme süresini mi ölçmek gerekir?
  • Zamanlayıcının çekirdeğin geri kalan koduyla ne kadar sıkı bağlı olduğunu merak ediyorum.
    Örneğin öncelik almayı hiç önemsemeyen bilimsel hesaplama uygulamaları için zamanlayıcıyı ciddi ölçüde sadeleştirmek istesek, bunu temiz ve modüler bir şekilde yapmak mümkün olur mu? Gerçek bir faydası olur mu?

    • Bir süreç kümesini mümkün olduğunca az öncelik almayla çalıştırmak istiyorsanız, örneğin HPC ortamlarında bazı çekirdekleri yalıtılmış CPU olarak ayarlayıp yeniden başlattıktan sonra işleri taskset ile doğrudan oraya koymak en güçlü yöntemdir.
      Ancak o zaman işleri gerçekten elle CPU’lara atamanız gerekir ve tüm işlerin yanlış CPU’ya düşmesi de kolayca yaşanabilir. Standart yöntem, kesme maskelerini ayarlayıp kesmelerin “iş” CPU’suna gitmesini engellemek ve belirli cgroup’ların yalnızca verilen cpuset içinde çalışmasını sağlamak için cpuset kullanmaktır.
    • Neredeyse hiç daemon olmayan temiz bir sistemde, uygulamayı CPU iş parçacığı başına bir işletim sistemi iş parçacığı olacak şekilde ayarlayıp CPU sabitleme uygulayarak yer değiştirmesini engellerseniz yaklaşık %95’e ulaşabilirsiniz.
      Çalışma listesi çok kısalacağı için zamanlayıcı ne yaparsa yapsın etkisi oldukça küçük olur. Uygulama çok fazla G/Ç yapmıyorsa kesme de fazla olmaz. Tickless çekirdek kullanabiliyorsanız — bugün hâlâ ayrı bir seçenek mi yoksa varsayılan mı bilmiyorum — uzun süreler boyunca neredeyse hiç kesme olmayabilir.
    • Son baktığımda şaşırtıcı derecede iyi ayrıştırılmıştı.
      Ancak ciddi ölçüde sadeleştirme isteğinin nedeni hatalardan kaçınmak olur; iyi ayarlanmış varsayılan zamanlayıcıya kıyasla elde edilecek performans çok fazla değildir. Çok sayıda ayar var ama o tarafta da fazla hata yoktu. Safça sadeleştirme çoğunlukla performans kazandırmak yerine kaybettirir. Etkileşimsiz bir sistem çalıştırıyorsanız en kolay değişiklik süreç zaman kotasını artırmaktır.
    • Ben sadece RT Linux kullanırdım. Kendi temel zamanlayıcısı var; çekirdek zamanlayıcısı boşta iş olarak çalışır ve gerçek zamanlı işler her şeyden daha yüksek önceliğe sahip olur.