Ada banyak jawaban bagus di sini yang sudah mencakup banyak poin penting, jadi saya hanya akan menambahkan beberapa masalah yang tidak saya lihat langsung di atas. Artinya, jawaban ini seharusnya tidak dianggap sebagai komprehensif dari pro dan kontra, tetapi merupakan tambahan untuk jawaban lain di sini.
mmap sepertinya sihir
Mengambil kasus di mana file sudah sepenuhnya di-cache 1 sebagai baseline 2 , mmap
mungkin tampak seperti sulap :
mmap
hanya membutuhkan 1 panggilan sistem untuk (berpotensi) memetakan seluruh file, setelah itu tidak ada lagi panggilan sistem yang diperlukan.
mmap
tidak memerlukan salinan data file dari kernel ke ruang pengguna.
mmap
memungkinkan Anda untuk mengakses file "sebagai memori", termasuk memprosesnya dengan trik canggih apa pun yang dapat Anda lakukan terhadap memori, seperti kompilasi otomatis vektor, intrinsik SIMD , prefetching, rutinitas parsing dalam memori yang dioptimalkan, OpenMP, dll.
Jika file tersebut sudah ada dalam cache, sepertinya tidak mungkin dikalahkan: Anda langsung mengakses cache halaman kernel sebagai memori dan itu tidak bisa lebih cepat dari itu.
Ya bisa.
mmap sebenarnya bukan sihir karena ...
mmap masih berfungsi per halaman
Biaya tersembunyi utama mmap
vs read(2)
(yang sebenarnya merupakan syscall OS-level yang sebanding untuk membaca blok ) adalah bahwa mmap
Anda harus melakukan "beberapa pekerjaan" untuk setiap halaman 4K di ruang pengguna, meskipun mungkin disembunyikan oleh mekanisme kesalahan halaman.
Sebagai contoh implementasi khas yang hanya mmap
s seluruh file perlu kesalahan-in sehingga 100 GB / 4K = 25 juta kesalahan untuk membaca file 100 GB. Sekarang, ini akan menjadi kesalahan kecil , tetapi 25 miliar kesalahan halaman masih tidak akan menjadi super cepat. Biaya kesalahan kecil mungkin dalam 100-an nanos dalam kasus terbaik.
mmap sangat bergantung pada kinerja TLB
Sekarang, Anda dapat meneruskan MAP_POPULATE
untuk mmap
mengatakannya untuk mengatur semua tabel halaman sebelum kembali, sehingga seharusnya tidak ada kesalahan halaman saat mengaksesnya. Sekarang, ini memiliki sedikit masalah karena ia juga membaca seluruh file menjadi RAM, yang akan meledak jika Anda mencoba memetakan file 100GB - tapi mari kita abaikan itu untuk saat ini 3 . Kernel perlu melakukan pekerjaan per halaman untuk mengatur tabel halaman ini (muncul sebagai waktu kernel). Ini akhirnya menjadi biaya besar dalam mmap
pendekatan, dan itu sebanding dengan ukuran file (yaitu, itu tidak menjadi relatif kurang penting karena ukuran file bertambah) 4 .
Akhirnya, bahkan dalam ruang pengguna mengakses pemetaan seperti itu tidak benar-benar gratis (dibandingkan dengan buffer memori besar yang tidak berasal dari berbasis file mmap
) - bahkan setelah tabel halaman diatur, setiap akses ke halaman baru akan, secara konseptual, menimbulkan miss TLB. Karena mmap
ing file berarti menggunakan cache halaman dan halaman 4K, Anda sekali lagi dikenai biaya 25 juta kali untuk file 100GB.
Sekarang, biaya sebenarnya dari kehilangan TLB ini sangat bergantung pada setidaknya aspek-aspek berikut dari perangkat keras Anda: (a) berapa banyak 4K TLB yang Anda miliki dan bagaimana kinerja caching terjemahan lainnya bekerja (b) seberapa baik perangkat keras mengambil penawaran dengan dengan TLB - mis., dapatkah prefetch memicu berjalan halaman? (c) seberapa cepat dan sejajar dengan hardware berjalan halaman. Pada prosesor Intel high-end x86 modern, perangkat keras laman berjalan secara umum sangat kuat: setidaknya ada 2 pejalan halaman paralel, berjalan halaman dapat terjadi secara bersamaan dengan eksekusi lanjutan, dan pengambilan perangkat keras dapat memicu berjalannya halaman. Jadi dampak TLB pada beban baca streaming cukup rendah - dan beban seperti itu akan sering melakukan hal yang sama terlepas dari ukuran halaman. Namun, perangkat keras lain biasanya jauh lebih buruk!
baca () hindari perangkap ini
The read()
syscall, yang adalah apa yang umumnya mendasari "blok membaca" jenis panggilan yang ditawarkan misalnya, di C, C ++ dan bahasa lainnya memiliki satu kelemahan utama bahwa setiap orang baik-menyadari:
- Setiap
read()
panggilan N byte harus menyalin N byte dari kernel ke ruang pengguna.
Di sisi lain, ia menghindari sebagian besar biaya di atas - Anda tidak perlu memetakan 25 juta halaman 4K ke dalam ruang pengguna. Anda biasanya dapat malloc
buffer tunggal buffer kecil di ruang pengguna, dan menggunakannya kembali berulang kali untuk semua read
panggilan Anda . Di sisi kernel, hampir tidak ada masalah dengan 4K halaman atau TLB meleset karena semua RAM biasanya dipetakan secara linear menggunakan beberapa halaman yang sangat besar (misalnya, 1 GB halaman pada x86), sehingga halaman yang mendasarinya di cache halaman tertutup. sangat efisien dalam ruang kernel.
Jadi pada dasarnya Anda memiliki perbandingan berikut untuk menentukan mana yang lebih cepat untuk membaca satu file besar:
Apakah pekerjaan ekstra per halaman yang tersirat oleh mmap
pendekatan ini lebih mahal daripada pekerjaan byte per menyalin konten file dari kernel ke ruang pengguna tersirat dengan menggunakan read()
?
Pada banyak sistem, mereka sebenarnya kurang lebih seimbang. Perhatikan bahwa masing-masing skala dengan atribut perangkat keras dan tumpukan OS yang sangat berbeda.
Secara khusus, mmap
pendekatan menjadi relatif lebih cepat ketika:
- OS memiliki penanganan kesalahan kecil yang cepat dan terutama optimasi gangguan kesalahan kecil seperti kesalahan di sekitar.
- OS memiliki
MAP_POPULATE
implementasi yang baik yang dapat secara efisien memproses peta besar dalam kasus-kasus di mana, misalnya, halaman yang mendasarinya berdekatan dalam memori fisik.
- Perangkat keras ini memiliki kinerja terjemahan halaman yang kuat, seperti TLB besar, TLB tingkat kedua cepat, walker halaman paralel dan cepat, interaksi prefetch yang baik dengan terjemahan dan sebagainya.
... sementara read()
pendekatannya menjadi relatif lebih cepat ketika:
- The
read()
syscall memiliki kinerja salinan yang baik. Misalnya, copy_to_user
kinerja yang baik di sisi kernel.
- Kernel memiliki cara yang efisien (relatif terhadap pengguna) untuk memetakan memori, misalnya, hanya menggunakan beberapa halaman besar dengan dukungan perangkat keras.
- Kernel memiliki syscalls cepat dan cara untuk menjaga entri kernel TLB di seluruh syscalls.
Faktor perangkat keras di atas sangat bervariasi di berbagai platform yang berbeda, bahkan dalam keluarga yang sama (misalnya, dalam generasi x86 dan terutama segmen pasar) dan tentunya lintas arsitektur (misalnya, ARM vs x86 vs PPC).
Faktor OS terus berubah juga, dengan berbagai peningkatan di kedua sisi menyebabkan lompatan besar dalam kecepatan relatif untuk satu pendekatan atau yang lain. Daftar terbaru termasuk:
- Penambahan patahan, dijelaskan di atas, yang benar-benar membantu
mmap
kasus tanpa MAP_POPULATE
.
- Penambahan
copy_to_user
metode jalur cepat di arch/x86/lib/copy_user_64.S
, misalnya, menggunakan REP MOVQ
ketika itu cepat, yang sangat membantu read()
kasus ini.
Perbarui setelah Specter dan Meltdown
Mitigasi untuk kerentanan Spectre dan Meltdown sangat meningkatkan biaya pemanggilan sistem. Pada sistem yang saya ukur, biaya pemanggilan sistem "tidak melakukan apa-apa" (yang merupakan perkiraan overhead murni dari pemanggilan sistem, terlepas dari pekerjaan aktual yang dilakukan oleh panggilan tersebut) naik dari sekitar 100 ns pada tipikal sistem Linux modern sekitar 700 ns. Lebih jauh, tergantung pada sistem Anda, perbaikan isolasi halaman-tabel khusus untuk Meltdown dapat memiliki efek hilir tambahan selain dari biaya panggilan sistem langsung karena kebutuhan untuk memuat ulang entri TLB.
Semua ini adalah kerugian relatif untuk read()
metode berbasis dibandingkan dengan mmap
metode berbasis, karena read()
metode harus membuat satu panggilan sistem untuk setiap "ukuran buffer" nilai data. Anda tidak dapat secara sewenang-wenang meningkatkan ukuran buffer untuk mengamortisasi biaya ini karena menggunakan buffer besar biasanya berkinerja lebih buruk karena Anda melebihi ukuran L1 dan karenanya terus-menerus mengalami kesalahan cache.
Di sisi lain, dengan mmap
, Anda dapat memetakan di wilayah memori yang besar dengan MAP_POPULATE
dan mengaksesnya secara efisien, dengan biaya hanya satu panggilan sistem.
1 Ini kurang lebih juga termasuk kasus di mana file tidak sepenuhnya di-cache untuk memulai dengan, tetapi di mana OS read-depan cukup baik untuk membuatnya tampak begitu (yaitu, halaman biasanya di-cache pada saat Anda ingin). Ini adalah masalah yang halus karena cara baca-depan bekerja sering sangat berbeda antara mmap
dan read
panggilan, dan dapat lebih lanjut disesuaikan dengan panggilan "saran" seperti yang dijelaskan dalam 2 .
2 ... karena jika file tersebut tidak di- cache, perilaku Anda akan sepenuhnya didominasi oleh kekhawatiran IO, termasuk seberapa simpatik pola akses Anda terhadap perangkat keras yang mendasarinya - dan semua usaha Anda harus memastikan bahwa akses tersebut sama simpatiknya dengan mungkin, misalnya melalui penggunaan madvise
atau fadvise
panggilan (dan perubahan level aplikasi apa pun yang dapat Anda lakukan untuk meningkatkan pola akses).
3 Anda bisa menyiasatinya, misalnya, dengan secara berurutan mmap
masuk ke jendela dengan ukuran lebih kecil, katakanlah 100 MB.
4 Faktanya, ternyata MAP_POPULATE
pendekatannya adalah (setidaknya satu kombinasi beberapa hardware / OS) hanya sedikit lebih cepat daripada tidak menggunakannya, mungkin karena kernel menggunakan faultaround - sehingga jumlah sebenarnya dari kesalahan kecil dikurangi dengan faktor 16 atau lebih.
mmap()
adalah 2-6 kali lebih cepat daripada menggunakan syscalls, misread()
.