Saya menelusuri web pada beberapa detail teknis tentang memblokir I / O dan non memblokir I / O dan saya menemukan beberapa orang yang menyatakan bahwa non-pemblokiran I / O akan lebih cepat daripada memblokir I / O. Misalnya dalam dokumen ini .
Jika saya menggunakan pemblokiran I / O, maka tentu saja thread yang saat ini diblokir tidak dapat melakukan apa-apa lagi ... Karena sudah diblokir. Tetapi begitu utas mulai diblokir, OS dapat beralih ke utas lain dan tidak beralih kembali hingga ada sesuatu yang harus dilakukan untuk utas yang diblokir. Jadi selama ada utas lain pada sistem yang membutuhkan CPU dan tidak diblokir, seharusnya tidak ada lagi waktu idle CPU dibandingkan dengan pendekatan non-pemblokiran berbasis peristiwa, bukan?
Selain mengurangi waktu CPU dalam keadaan idle, saya melihat satu opsi lagi untuk meningkatkan jumlah tugas yang dapat dilakukan komputer dalam kerangka waktu tertentu: Kurangi overhead yang diperkenalkan dengan mengganti utas. Tapi bagaimana ini bisa dilakukan? Dan apakah biaya overhead cukup besar untuk menunjukkan efek yang dapat diukur? Berikut ini ide tentang bagaimana saya bisa membayangkannya bekerja:
- Untuk memuat konten file, aplikasi mendelegasikan tugas ini ke kerangka kerja i / o berbasis peristiwa, meneruskan fungsi panggilan balik bersama dengan nama file
- Kerangka acara mendelegasikan ke sistem operasi, yang memprogram pengontrol DMA dari hard disk untuk menulis file secara langsung ke memori
- Kerangka acara memungkinkan kode lebih lanjut untuk dijalankan.
- Setelah menyelesaikan penyalinan disk-ke-memori, pengontrol DMA menyebabkan interupsi.
- Penangan interupsi sistem operasi memberi tahu kerangka kerja i / o berbasis peristiwa tentang file yang dimuat sepenuhnya ke dalam memori. Bagaimana cara melakukannya? Menggunakan sinyal ??
- Kode yang saat ini dijalankan dalam event i / o framework selesai.
- Kerangka kerja i / o berbasis peristiwa memeriksa antriannya dan melihat pesan sistem operasi dari langkah 5 dan menjalankan callback yang didapatnya di langkah 1.
Apakah itu cara kerjanya? Jika tidak, bagaimana cara kerjanya? Itu berarti bahwa sistem acara dapat bekerja tanpa perlu menyentuh tumpukan secara eksplisit (seperti penjadwal nyata yang perlu mencadangkan tumpukan dan menyalin tumpukan utas lain ke dalam memori saat beralih utas)? Berapa banyak waktu yang sebenarnya dihemat? Apakah ada lebih dari itu?
sumber
Jawaban:
Keuntungan terbesar dari nonblocking atau asynchronous I / O adalah thread Anda dapat melanjutkan pekerjaannya secara paralel. Tentu saja Anda dapat mencapai ini juga menggunakan utas tambahan. Seperti yang Anda nyatakan untuk kinerja keseluruhan (sistem) terbaik, saya kira akan lebih baik menggunakan I / O asinkron dan bukan beberapa utas (jadi mengurangi peralihan utas).
Mari kita lihat kemungkinan implementasi program server jaringan yang akan menangani 1000 klien yang terhubung secara paralel:
Setiap utas membutuhkan sumber daya memori (juga memori kernel!), Itu merupakan kerugian. Dan setiap utas tambahan berarti lebih banyak pekerjaan untuk penjadwal.
Ini mengambil beban dari sistem karena kami memiliki lebih sedikit utas. Tetapi itu juga mencegah Anda menggunakan kinerja penuh mesin Anda, karena Anda mungkin akhirnya menggerakkan satu prosesor hingga 100% dan membiarkan semua prosesor lain menganggur.
Ini mengambil beban dari sistem karena jumlah utasnya lebih sedikit. Dan itu bisa menggunakan semua prosesor yang tersedia. Pada Windows, pendekatan ini didukung oleh Thread Pool API .
Tentu saja memiliki lebih banyak utas bukanlah masalah. Seperti yang mungkin sudah Anda ketahui, saya memilih jumlah koneksi / utas yang cukup tinggi. Saya ragu Anda akan melihat perbedaan antara tiga kemungkinan implementasi jika kita berbicara tentang hanya selusin utas (ini juga yang disarankan Raymond Chen di posting blog MSDN Apakah Windows memiliki batas 2000 utas per proses? ).
Pada Windows menggunakan file I / O unbuffered berarti bahwa menulis harus dari ukuran yang merupakan kelipatan dari ukuran halaman. Saya belum mengujinya, tetapi sepertinya ini juga dapat memengaruhi kinerja tulis secara positif untuk penulisan buffer sinkron dan asinkron.
Langkah 1 hingga 7 yang Anda jelaskan memberikan gambaran yang bagus tentang cara kerjanya. Di Windows, sistem operasi akan memberi tahu Anda tentang penyelesaian I / O asinkron (
WriteFile
denganOVERLAPPED
struktur) menggunakan peristiwa atau callback. Fungsi panggilan balik hanya akan dipanggil misalnya ketika kode Anda memanggilWaitForMultipleObjectsEx
denganbAlertable
disetel ketrue
.Beberapa bacaan lagi di web:
sumber
I / O mencakup berbagai jenis operasi seperti membaca dan menulis data dari hard drive, mengakses sumber daya jaringan, menelepon layanan web, atau mengambil data dari database. Bergantung pada platform dan jenis operasinya, I / O asinkron biasanya akan memanfaatkan perangkat keras apa pun atau dukungan sistem tingkat rendah untuk melakukan operasi. Ini berarti bahwa itu akan dilakukan dengan dampak sekecil mungkin pada CPU.
Pada tingkat aplikasi, asinkron I / O mencegah thread menunggu operasi I / O selesai. Segera setelah operasi I / O asinkron dimulai, ini akan melepaskan utas tempat peluncurannya dan callback terdaftar. Saat operasi selesai, callback diantrekan untuk dieksekusi pada thread pertama yang tersedia.
Jika operasi I / O dijalankan secara sinkron, maka thread yang sedang berjalan tidak melakukan apa pun hingga operasi selesai. Runtime tidak tahu kapan operasi I / O selesai, sehingga secara berkala akan menyediakan beberapa waktu CPU ke thread menunggu, waktu CPU yang seharusnya dapat digunakan oleh thread lain yang memiliki operasi terikat CPU yang sebenarnya untuk dijalankan.
Jadi, seperti yang disebutkan @ user1629468, asynchronous I / O tidak memberikan kinerja yang lebih baik melainkan skalabilitas yang lebih baik. Ini terlihat jelas saat berjalan dalam konteks yang memiliki jumlah untaian yang terbatas, seperti halnya dengan aplikasi web. Aplikasi web biasanya menggunakan kumpulan utas tempat mereka menetapkan utas untuk setiap permintaan. Jika permintaan diblokir pada operasi I / O yang berjalan lama, ada risiko menghabiskan kumpulan web dan membuat aplikasi web macet atau lambat merespons.
Satu hal yang saya perhatikan adalah bahwa asynchronous I / O bukanlah pilihan terbaik saat menangani operasi I / O yang sangat cepat. Dalam hal ini, manfaat dari tidak membuat thread sibuk sambil menunggu operasi I / O selesai tidak terlalu penting dan fakta bahwa operasi dimulai pada satu thread dan diselesaikan di thread lain menambah overhead pada keseluruhan eksekusi.
Anda dapat membaca penelitian lebih rinci yang baru-baru ini saya buat tentang topik asynchronous I / O vs. multithreading di sini .
sumber
Alasan utama menggunakan AIO adalah untuk skalabilitas. Jika dilihat dalam konteks beberapa utas, manfaatnya tidak jelas. Namun saat sistem menskalakan hingga 1000 utas, AIO akan menawarkan kinerja yang jauh lebih baik. Peringatannya adalah bahwa pustaka AIO tidak boleh menyebabkan kemacetan lebih lanjut.
sumber
Untuk mengasumsikan peningkatan kecepatan karena segala bentuk multi-komputasi, Anda harus menganggap bahwa beberapa tugas berbasis CPU sedang dijalankan secara bersamaan pada beberapa sumber daya komputasi (umumnya inti prosesor) atau bahwa tidak semua tugas bergantung pada penggunaan bersamaan dari sumber daya yang sama - yaitu, beberapa tugas mungkin bergantung pada satu subkomponen sistem (misalnya penyimpanan disk) sementara beberapa tugas bergantung pada yang lain (menerima komunikasi dari perangkat periferal) dan yang lainnya mungkin memerlukan penggunaan inti prosesor.
Skenario pertama sering disebut sebagai pemrograman "paralel". Skenario kedua sering disebut sebagai pemrograman "konkuren" atau "asinkron", meskipun "konkuren" terkadang juga digunakan untuk merujuk pada kasus hanya mengizinkan sistem operasi untuk melakukan interleave eksekusi beberapa tugas, terlepas dari apakah eksekusi tersebut harus dilakukan tempatkan secara serial atau jika banyak sumber daya dapat digunakan untuk mencapai eksekusi paralel. Dalam kasus terakhir ini, "bersamaan" umumnya mengacu pada cara eksekusi ditulis dalam program, bukan dari perspektif simultanitas eksekusi tugas yang sebenarnya.
Sangat mudah untuk membicarakan semua ini dengan asumsi diam-diam. Misalnya, beberapa orang cepat membuat klaim seperti "Asynchronous I / O akan lebih cepat daripada I / O multi-threaded". Klaim ini meragukan karena beberapa alasan. Pertama, mungkin saja beberapa kerangka kerja I / O asinkron tertentu diimplementasikan secara tepat dengan multi-threading, dalam hal ini keduanya adalah satu konsep yang sama dan tidak masuk akal untuk mengatakan satu konsep "lebih cepat daripada" yang lain .
Kedua, bahkan dalam kasus ketika ada implementasi single-threaded dari framework asynchronous (seperti event loop single-threaded), Anda masih harus membuat asumsi tentang apa yang dilakukan loop tersebut. Misalnya, satu hal konyol yang dapat Anda lakukan dengan loop peristiwa single-threaded adalah memintanya untuk menyelesaikan dua tugas terikat CPU yang berbeda secara asinkron. Jika Anda melakukan ini pada mesin yang hanya memiliki inti prosesor tunggal yang ideal (mengabaikan pengoptimalan perangkat keras modern), maka melakukan tugas ini "secara asinkron" tidak akan benar-benar berfungsi dengan cara yang berbeda daripada menjalankannya dengan dua utas yang dikelola secara independen, atau hanya dengan satu proses - - perbedaannya mungkin terletak pada pengalihan konteks utas atau pengoptimalan jadwal sistem operasi, tetapi jika kedua tugas akan dikirim ke CPU, itu akan serupa dalam kedua kasus.
Berguna untuk membayangkan banyak kasus sudut yang tidak biasa atau bodoh yang mungkin Anda hadapi.
"Asynchronous" tidak harus serentak, misalnya seperti di atas: Anda "secara asinkron" menjalankan dua tugas yang terikat CPU pada mesin dengan tepat satu inti prosesor.
Eksekusi multi-utas tidak harus bersamaan: Anda menelurkan dua utas pada mesin dengan inti prosesor tunggal, atau meminta dua utas untuk memperoleh jenis sumber daya langka lainnya (bayangkan, katakanlah, basis data jaringan yang hanya dapat membuat satu koneksi pada satu waktu). Eksekusi utas mungkin disisipkan, namun penjadwal sistem operasi menganggapnya cocok, tetapi total runtime mereka tidak dapat dikurangi (dan akan ditingkatkan dari peralihan konteks utas) pada satu inti (atau lebih umum, jika Anda menelurkan lebih banyak utas daripada yang ada core untuk menjalankannya, atau memiliki lebih banyak utas yang meminta sumber daya daripada yang dapat dipertahankan sumber daya). Hal yang sama juga berlaku untuk multi-pemrosesan.
Jadi baik I / O asinkron maupun multi-threading tidak harus menawarkan peningkatan kinerja apa pun dalam hal waktu berjalan. Mereka bahkan bisa memperlambat segalanya.
Namun, jika Anda menentukan kasus penggunaan tertentu, seperti program tertentu yang membuat panggilan jaringan untuk mengambil data dari sumber daya yang terhubung ke jaringan seperti database jarak jauh dan juga melakukan beberapa komputasi terikat CPU lokal, Anda dapat mulai mempertimbangkannya. perbedaan kinerja antara kedua metode dengan asumsi tertentu tentang perangkat keras.
Pertanyaan untuk ditanyakan: Berapa banyak langkah komputasi yang perlu saya lakukan dan berapa banyak sistem sumber daya independen yang ada untuk melakukannya? Adakah subkumpulan dari langkah-langkah komputasi yang memerlukan penggunaan subkomponen sistem independen dan dapat mengambil manfaat dari melakukannya secara bersamaan? Berapa banyak inti prosesor yang saya miliki dan berapa biaya tambahan untuk menggunakan beberapa prosesor atau utas untuk menyelesaikan tugas pada inti terpisah?
Jika tugas Anda sangat bergantung pada subsistem independen, maka solusi asinkron mungkin bagus. Jika jumlah utas yang diperlukan untuk menanganinya akan banyak, sehingga peralihan konteks menjadi tidak sepele untuk sistem operasi, maka solusi asinkron beruntai tunggal mungkin lebih baik.
Setiap kali tugas terikat oleh sumber daya yang sama (misalnya beberapa kebutuhan untuk secara bersamaan mengakses jaringan atau sumber daya lokal yang sama), maka multi-threading mungkin akan menyebabkan overhead yang tidak memuaskan, dan sementara asinkron beruntai tunggal dapat menyebabkan lebih sedikit overhead, dalam sumber daya seperti itu- situasi terbatas itu juga tidak dapat menghasilkan percepatan. Dalam kasus seperti itu, satu-satunya pilihan (jika Anda ingin dipercepat) adalah membuat banyak salinan dari sumber daya tersebut tersedia (misalnya, beberapa inti prosesor jika sumber daya yang langka adalah CPU; database yang lebih baik yang mendukung lebih banyak koneksi bersamaan jika sumber daya langka adalah database dengan koneksi terbatas, dll.).
Cara lain untuk menjelaskannya adalah: mengizinkan sistem operasi untuk menyisipkan penggunaan sumber daya tunggal untuk dua tugas tidak bisa lebih cepat daripada hanya membiarkan satu tugas menggunakan sumber daya sementara yang lain menunggu, lalu membiarkan tugas kedua selesai secara berurutan. Selanjutnya, biaya penjadwal dari interleaving berarti dalam situasi nyata apa pun itu benar-benar menciptakan perlambatan. Tidak masalah jika penggunaan interleaved terjadi pada CPU, sumber daya jaringan, sumber daya memori, perangkat periferal, atau sumber daya sistem lainnya.
sumber
Salah satu kemungkinan implementasi I / O non-pemblokiran adalah persis seperti yang Anda katakan, dengan kumpulan thread latar belakang yang memblokir I / O dan memberi tahu thread pembuat I / O melalui beberapa mekanisme callback. Faktanya, beginilah cara kerja modul AIO di glibc. Berikut adalah beberapa detail samar tentang penerapannya.
Meskipun ini adalah solusi bagus yang cukup portabel (selama Anda memiliki utas), OS biasanya dapat melayani I / O non-pemblokiran dengan lebih efisien. Artikel Wikipedia ini mencantumkan kemungkinan penerapan selain kumpulan utas.
sumber
Saat ini saya sedang dalam proses mengimplementasikan async io pada platform tertanam menggunakan protothreads. Tanpa pemblokiran io membuat perbedaan antara berjalan pada 16000fps dan 160fps. Manfaat terbesar dari non blocking io adalah Anda dapat menyusun kode Anda untuk melakukan hal-hal lain sementara perangkat keras melakukan tugasnya. Bahkan inisialisasi perangkat dapat dilakukan secara paralel.
Martin
sumber
Di Node, beberapa utas diluncurkan, tetapi ini adalah lapisan bawah dalam waktu proses C ++.
https://codeburst.io/how-node-js-single-thread-mechanism-work-understanding-event-loop-in-nodejs-230f7440b0ea
https://itnext.io/multi-threading-and-multi-process-in-node-js-ffa5bb5cde98
Penjelasan "Node lebih cepat karena tidak memblokir ..." adalah sedikit pemasaran dan ini adalah pertanyaan yang bagus. Ini efisien dan dapat diskalakan, tetapi tidak sepenuhnya berulir tunggal.
sumber
Peningkatan sejauh yang saya tahu adalah bahwa Asynchronous I / O penggunaan (saya bicarakan MS System, hanya untuk memperjelas) sehingga disebut I / O port selesai . Dengan menggunakan panggilan Asynchronous, framework memanfaatkan arsitektur tersebut secara otomatis, dan ini diharapkan jauh lebih efisien daripada mekanisme threading standar. Sebagai pengalaman pribadi, saya dapat mengatakan bahwa Anda akan merasa aplikasi Anda lebih reaktif jika Anda lebih memilih AsyncCalls daripada memblokir utas.
sumber
Izinkan saya memberi Anda contoh balasan bahwa I / O asinkron tidak berfungsi. Saya menulis proxy yang mirip dengan di bawah ini menggunakan boost :: asio. https://github.com/ArashPartow/proxy/blob/master/tcpproxy_server.cpp
Namun, skenario kasus saya adalah, pesan masuk (dari sisi klien) cepat sementara yang keluar (ke sisi server) lambat untuk satu sesi, untuk mengimbangi kecepatan masuk atau untuk memaksimalkan throughput proxy total, kita harus menggunakan beberapa sesi dalam satu koneksi.
Jadi kerangka kerja I / O asinkron ini tidak berfungsi lagi. Kami membutuhkan kumpulan utas untuk dikirim ke server dengan menetapkan setiap utas sesi.
sumber