Peringatan tentang reaktor select / poll vs. epoll di Twisted

95

Semua yang telah saya baca dan alami (aplikasi berbasis Tornado) membuat saya percaya bahwa ePoll adalah pengganti alami untuk jaringan berbasis Pilih dan Poll, terutama dengan Twisted. Yang membuat saya paranoid, sangat jarang teknik atau metodologi yang lebih baik tidak datang dengan harga.

Membaca beberapa lusin perbandingan antara epoll dan alternatif menunjukkan bahwa epoll jelas merupakan juara untuk kecepatan dan skalabilitas, khususnya skala secara linier yang fantastis. Bagaimana dengan prosesor dan pemanfaatan memori, apakah epoll masih juara?

David
sumber

Jawaban:

190

Untuk jumlah soket yang sangat kecil (bervariasi tergantung pada perangkat keras Anda, tentu saja, tetapi kita berbicara tentang sesuatu di urutan 10 atau kurang), pilih dapat mengalahkan epoll dalam penggunaan memori dan kecepatan runtime. Tentu saja, untuk sejumlah kecil soket, kedua mekanisme tersebut sangat cepat sehingga Anda tidak terlalu peduli dengan perbedaan ini di sebagian besar kasus.

Namun satu klarifikasi. Baik pilih dan skala epoll secara linier. Namun, perbedaan besar adalah bahwa API yang menghadap ke ruang pengguna memiliki kerumitan yang didasarkan pada hal-hal yang berbeda. Biaya selectpanggilan kira-kira sama dengan nilai deskriptor file bernomor tertinggi yang Anda berikan. Jika Anda memilih pada satu fd, 100, maka itu kira-kira dua kali lebih mahal daripada memilih pada satu fd, 50. Menambahkan lebih banyak fds di bawah yang tertinggi tidaklah cukup gratis, jadi ini sedikit lebih rumit daripada ini dalam praktiknya, tapi ini adalah perkiraan pertama yang baik untuk sebagian besar implementasi.

Biaya epoll lebih mendekati jumlah deskriptor file yang benar-benar memiliki acara di dalamnya. Jika Anda memantau 200 deskriptor file, tetapi hanya 100 yang memiliki peristiwa di dalamnya, Anda (sangat kasar) hanya membayar untuk 100 deskriptor file aktif tersebut. Di sinilah epoll cenderung menawarkan salah satu keunggulan utamanya dibandingkan pilihan. Jika Anda memiliki seribu klien yang sebagian besar menganggur, maka ketika Anda menggunakan pilih Anda masih membayar untuk seribu klien tersebut. Namun, dengan epoll, sepertinya Anda hanya punya sedikit - Anda hanya membayar yang aktif pada waktu tertentu.

Semua ini berarti epoll akan mengurangi penggunaan CPU untuk sebagian besar beban kerja. Sejauh penggunaan memori berjalan, itu sedikit dilempar. selectberhasil merepresentasikan semua informasi yang diperlukan dengan cara yang sangat kompak (satu bit per deskriptor file). Dan batasan FD_SETSIZE (biasanya 1024) tentang berapa banyak deskriptor file yang dapat Anda gunakan selectberarti Anda tidak akan pernah menghabiskan lebih dari 128 byte untuk masing-masing dari tiga set fd yang dapat Anda gunakan denganselect(baca, tulis, pengecualian). Dibandingkan dengan maksimal 384 byte itu, epoll adalah semacam babi. Setiap deskriptor file diwakili oleh struktur multi-byte. Namun, secara absolut, itu tetap tidak akan menggunakan banyak memori. Anda dapat mewakili sejumlah besar deskriptor file dalam beberapa lusin kilobyte (kira-kira 20k per 1000 file deskriptor, menurut saya). Dan Anda juga dapat membuang fakta bahwa Anda harus menghabiskan semua 384 byte tersebut dengan selectjika Anda hanya ingin memantau satu deskriptor file tetapi nilainya kebetulan 1024, sedangkan dengan epoll Anda hanya menghabiskan 20 byte. Tetap saja, semua angka ini cukup kecil, jadi tidak ada bedanya.

Dan ada juga manfaat lain dari epoll, yang mungkin sudah Anda ketahui, tidak terbatas pada deskriptor file FD_SETSIZE. Anda dapat menggunakannya untuk memantau deskriptor file sebanyak yang Anda miliki. Dan jika Anda hanya memiliki satu deskriptor file, tetapi nilainya lebih besar dari FD_SETSIZE, epoll juga berfungsi dengan itu, tetapi selecttidak.

Secara acak, saya juga baru-baru ini menemukan satu kelemahan kecil epolldibandingkan dengan selectatau poll. Meskipun tidak satu pun dari ketiga API ini mendukung file normal (yaitu, file pada sistem file), selectdan pollkurangnya dukungan ini seperti melaporkan deskriptor seperti yang selalu dapat dibaca dan selalu dapat ditulisi. Hal ini membuat mereka tidak cocok untuk semua jenis I / O sistem file non-pemblokiran yang berarti, program yang menggunakan selectatau polldan kebetulan menemukan deskriptor file dari sistem file setidaknya akan terus beroperasi (atau jika gagal, itu tidak akan terjadi karena dari selectatau poll), meskipun itu mungkin tidak dengan performa terbaik.

Di sisi lain, epollakan gagal dengan cepat dengan kesalahan ( EPERM, tampaknya) ketika diminta untuk memantau deskriptor file tersebut. Sebenarnya, ini hampir tidak salah. Ini hanya menandakan kurangnya dukungan secara eksplisit. Biasanya saya akan memuji kondisi kegagalan eksplisit, tetapi yang ini tidak berdokumen (sejauh yang saya tahu) dan menghasilkan aplikasi yang benar-benar rusak, daripada yang hanya beroperasi dengan kinerja yang berpotensi terdegradasi.

Dalam praktiknya, satu-satunya tempat yang pernah saya lihat ini muncul adalah saat berinteraksi dengan stdio. Seorang pengguna mungkin mengarahkan stdin atau stdout dari / ke file normal. Padahal sebelumnya stdin dan stdout akan menjadi pipa - didukung oleh epoll saja - itu kemudian menjadi file normal dan epoll gagal dengan keras, melanggar aplikasi.

Jean-Paul Calderone
sumber
Jawaban yang sangat bagus. Pertimbangkan untuk bersikap eksplisit tentang perilaku pollkelengkapan?
Quark
6
Dua sen saya tentang perilaku membaca dari file biasa: Saya biasanya lebih suka kegagalan langsung daripada penurunan kinerja. Alasannya adalah bahwa itu jauh lebih mungkin untuk dideteksi selama pengembangan, dan dengan demikian bekerja dengan baik (katakanlah dengan memiliki metode alternatif untuk melakukan I / O untuk file yang sebenarnya). YMMV tentu saja: mungkin tidak ada perlambatan yang nyata dalam hal kegagalan tidak lebih baik. Tetapi perlambatan dramatis yang hanya terjadi dalam kasus-kasus khusus bisa sangat sulit ditangkap selama pengembangan, meninggalkannya sebagai bom waktu ketika benar-benar diterapkan.
quark
1
Baru saja membaca suntingan Anda sepenuhnya. Dalam arti saya setuju bahwa mungkin tidak tepat untuk epoll untuk tidak meniru pendahulunya tetapi sekali lagi saya dapat membayangkan dev yang menerapkan kesalahan EPERM berpikir "Hanya karena itu selalu rusak, tidak membuatnya benar untuk merusak milik saya sebagai baik." Dan argumen balasan lainnya, saya seorang programmer defensif apa pun yang melewati 1 + 1 dicurigai dan saya membuat kode sedemikian rupa untuk memungkinkan kegagalan anggun. Memecat kernel karena kesalahan yang tidak diharapkan tidaklah baik atau penuh perhatian.
David
1
@ Jean-Paul Bisakah Anda menambahkan penjelasan tentang kqueue juga?
Orang Baik
Mengesampingkan kinerja, apakah ada masalah yang diakibatkan dari ini (dari man select) Kernel Linux tidak memberlakukan batasan tetap, tetapi implementasi glibc membuat fd_set menjadi tipe ukuran tetap, dengan FD_SETSIZE didefinisikan sebagai 1024, dan makro FD _ * () beroperasi sesuai dengan batas itu. Untuk memantau deskriptor file yang lebih besar dari 1023, gunakan poll (2) sebagai gantinya. Di CentOS 7 saya telah melihat masalah di mana kode saya sendiri gagal memilih () karena kernel mengembalikan pegangan file> 1023 dan saat ini saya sedang melihat masalah yang berbau seperti Twisted mengenai masalah yang sama.
Paul D Smith
4

Dalam pengujian di perusahaan saya, satu masalah dengan epoll () muncul, jadi satu biaya dibandingkan untuk memilih.

Ketika mencoba membaca dari jaringan dengan waktu tunggu, membuat epoll_fd (bukan FD_SET), dan menambahkan fd ke epoll_fd, jauh lebih mahal daripada membuat FD_SET (yang merupakan malloc sederhana).

Sesuai jawaban sebelumnya, karena jumlah FD dalam proses menjadi besar, biaya select () menjadi lebih tinggi, tetapi dalam pengujian kami, bahkan dengan nilai fd di 10.000, pilih masih menjadi pemenang. Ini adalah kasus di mana hanya ada satu fd yang ditunggu oleh thread, dan hanya mencoba mengatasi fakta bahwa jaringan membaca, dan menulis jaringan, tidak timeout saat menggunakan model thread pemblokiran. Tentu saja, model thread pemblokiran berkinerja rendah dibandingkan dengan sistem reaktor non-pemblokiran, tetapi ada kalanya, untuk berintegrasi dengan basis kode lama tertentu, diperlukan.

Kasus penggunaan semacam ini jarang terjadi pada aplikasi berkinerja tinggi, karena model reaktor tidak perlu membuat epoll_fd baru setiap saat. Untuk model di mana epoll_fd berumur panjang --- yang jelas lebih disukai untuk desain server berkinerja tinggi apa pun --- epoll adalah pemenang yang jelas dalam segala hal.

Brian Bulkowski
sumber
5
Tetapi Anda bahkan tidak dapat menggunakan select()jika Anda memiliki nilai deskriptor file dalam rentang 10k + - kecuali Anda mengkompilasi ulang setengah sistem Anda untuk mengubah FD_SETSIZE - jadi saya bertanya-tanya bagaimana strategi ini bekerja sama sekali. Untuk skenario yang Anda gambarkan, saya mungkin akan melihat poll()mana yang lebih mirip select()daripada yang sebenarnya epoll()- tetapi menghapus batasan FD_SETSIZE.
Jean-Paul Calderone
Anda bisa menggunakan select () jika Anda memiliki nilai deskriptor file dalam rentang 10K, karena Anda bisa malloc () FD_SET. Faktanya, karena FD_SETSIZE adalah waktu kompilasi dan batas fd sebenarnya adalah saat runtime, HANYA penggunaan yang aman dari FD_SET memeriksa jumlah deskriptor file terhadap ukuran FD_SET, dan melakukan malloc (atau setara moral) jika FD_SET adalah terlalu kecil. Saya terkejut ketika saya melihat ini dalam produksi dengan pelanggan. Setelah soket pemrograman selama 20 tahun, semua kode yang pernah saya tulis - dan sebagian besar tutorial di web - tidak aman.
Brian Bulkowski
5
Ini tidak benar, sejauh yang saya tahu, pada platform populer mana pun. FD_SETSIZEadalah konstanta waktu kompilasi yang ditetapkan saat library C Anda dikompilasi. Jika Anda mendefinisikannya ke nilai yang berbeda ketika Anda membangun aplikasi Anda, maka aplikasi Anda dan pustaka C tidak akan setuju dan semuanya akan berjalan buruk. Jika Anda memiliki referensi yang mengklaim aman untuk didefinisikan ulang, FD_SETSIZEsaya akan tertarik untuk melihatnya.
Jean-Paul Calderone