Select, Poll ve Epoll - Linux

Only Linux <3

Günümüzde code baselerimizi I/O odaklı büyütürken async kullanımından çekinmiyoruz. Hatta scalable olması açısından özellikle kullanıyoruz. Peki yaptığımız async işlemler okyanusun dibinde neler yapıyor?

O halde select, poll ve epoll sistem çağrılarını (syscall) inceleylim.

Bu yazıdan önce hakim değilseniz File Descriptor mekanizmasını okumanız ve anlamanız gerekmektedir.
https://man7.org/linux/man-pages/man2/open.2.html
https://en.wikipedia.org/wiki/File_descriptor#File_locking
https://en.wikipedia.org/wiki/Everything_is_a_file

Çok basit bir web server ayağa kaldırdınız ve gelen requestleri accept çağrısı ile işleyip response dönüyorsunuz. Bu durumda client'in her an request yapabileceğini ve bunu her daim yakalamanız gerektiğini biliyorsunuz.

Yukarıda bahsettiğimiz mekanizmayı genellikle şu şekilde kullanırız;

let listen = TcpListener::bind("127.0.0.1:8090").unwrap();

for stream in listen.incoming() {
	// Handle request
}


Yukarıdaki döngünün CPU'a sürekli "request geldi mi? ya şimdi? ya şimdi? hala mı yok? .." sorularını sorduğunu düşünün. Var olan mimarileri göz önünde bulundurunca çok fazla farkedemeyeceğimiz bir zaman kaybı gibi dursa da aslında çok büyük trafiklerde bunu çok rahatlıkla hissedebiliriz.

Bunun yerine CPU'a "Hey, sana 100 adet FD (File Descriptor) veriyorum, herhangi biri update olduğunda beni bilgilendir. (notify)" dersek büyük bir zaman ve resource tasarrufu sağlamış oluruz.

select & poll

Bu 2 çağrı epoll'un aksine herhangi bir UNIX sisteminde bulunmaktadır. Takip etmek istediğiniz FD'lerin listesini verdiğiniz taktirde aralarında hangilerinin read/write için hazır olduğunu size bildirecektir.

Bu 2 çağrının aralarındaki tek fark ise;

poll size FD hakkında birçok cevap dönebiliyorken (POLLRDNORM | POLLRDBAND | POLLIN | POLLHUP | POLLER) select ise sadece input, output ve error sonuçlarını dönebilmektedir. Yani select, poll'dan dönen detaylı cevap yerine daha basit bir anlam kullanıyor.

Peki neden select veya poll kullanmıyoruz?

NodeJS üzerinde bir server oluşturduğunuzda select ve poll yerine epoll kullandığını göreceksiniz. Bunun en büyük sebebi ise; siz her select veya poll'u trigger ettiğinizde verdiğiniz FD set içerisindeki her bir verinin hazır olup olmadığını kontrol etmelidir. Yani kernel, FD setinin monitor edilebilme durumunu hatırlamamaktadır. FD setiniz ne kadar büyükse kullanacağınız resource da doğal olarak artacaktır.

Signal-driven I/O

Yukarıda select ve poll'un FD seti hatırlamadığından bahsetmiştik. epoll ise herhangi bir FD update olduğunda kernel aracılığıyla signal gönderilerek handle etmemizi sağlamaktadır. Bu durumda sürekli update durumuna bakmamız gerekmiyor.

Pekiii, epoll ne?

lwn.net

epoll, yukarıda da bahsettiğimiz signal-driven mekanizmasını kullanarak verdiğimiz FD set üzerinde herhangi bir update olduğunda kendi içerisinde bulunan call functionlar ile bize bildiren bir diğer arkadaşımız oluyor.

epoll, kendi içerisinde 3 adet sistem çağrısı (syscall) bulundurmaktadır. Bunlar;

  • epoll_create
    • Kernel'a epoll FD'si almak istediğimizi söyleyeceğimiz call.
  • epoll_ctl
    • epoll FD'si üzerinde update yapabilmek için kullanacağımız call.
  • epoll_wait
    • Verdiğimiz FD set üzerinde herhangi bir update olduğunda bizi bilgilendirecek olan call.

Performans

# operations  |  poll  |  select   | epoll
10            |   0.61 |    0.73   | 0.41
100           |   2.9  |    3.0    | 0.42
1000          |  35    |   35      | 0.53
10000         | 990    |  930      | 0.66
https://jvns.ca



Bir sonraki yazıda epoll'u Rust üzerinde implemente edeceğiz.

Keyifli kodlamalar.