mmap () vs. blok bacaan


185

Saya sedang mengerjakan program yang akan memproses file yang berpotensi 100GB atau lebih. File-file tersebut berisi kumpulan catatan panjang variabel. Saya telah menerapkan dan menjalankan pertama dan sekarang sedang mencari cara untuk meningkatkan kinerja, terutama dalam melakukan I / O lebih efisien karena file input akan dipindai berkali-kali.

Apakah ada aturan praktis untuk menggunakan mmap()versus membaca dalam blok melalui fstreampustaka C ++ ? Yang ingin saya lakukan adalah membaca blok besar dari disk menjadi buffer, memproses catatan lengkap dari buffer, dan kemudian membaca lebih lanjut.

The mmap()kode berpotensi mendapatkan sangat berantakan karena mmap'd blok perlu berbaring di halaman berukuran batas (pemahaman saya) dan catatan bisa berpotensi seperti di halaman batas. Dengan fstreams, saya hanya bisa mencari ke awal catatan dan mulai membaca lagi, karena kita tidak terbatas pada membaca blok yang terletak pada batas ukuran halaman.

Bagaimana saya bisa memutuskan antara dua opsi ini tanpa benar-benar menulis implementasi yang lengkap terlebih dahulu? Adakah aturan praktis (misalnya, mmap()2x lebih cepat) atau tes sederhana?


1
Ini adalah bacaan yang menarik: medium.com/@sasha_f/... Dalam percobaan mmap()adalah 2-6 kali lebih cepat daripada menggunakan syscalls, mis read().
mplattner

Jawaban:


208

Saya mencoba untuk menemukan kata terakhir pada mmap / membaca kinerja di Linux dan saya menemukan posting bagus ( tautan ) di milis kernel Linux. Ini dari tahun 2000, jadi ada banyak perbaikan pada IO dan memori virtual di kernel sejak itu, tapi itu menjelaskan alasan mengapa mmapatau readmungkin lebih cepat atau lebih lambat.

  • Panggilan ke mmapmemiliki lebih banyak overhead daripada read(sama seperti epollmemiliki lebih banyak overhead daripada poll, yang memiliki lebih banyak overhead daripadaread ). Mengubah pemetaan memori virtual adalah operasi yang cukup mahal pada beberapa prosesor karena alasan yang sama bahwa beralih di antara proses yang berbeda itu mahal.
  • Sistem IO sudah dapat menggunakan cache disk, jadi jika Anda membaca file, Anda akan menekan cache atau melewatkannya apa pun metode yang Anda gunakan.

Namun,

  • Peta memori umumnya lebih cepat untuk akses acak, terutama jika pola akses Anda jarang dan tidak dapat diprediksi.
  • Peta memori memungkinkan Anda tetap menggunakan halaman dari cache sampai selesai. Ini berarti bahwa jika Anda menggunakan file banyak untuk jangka waktu yang lama, kemudian tutup dan buka kembali, halaman-halaman akan tetap di-cache. Dengan read, file Anda mungkin telah dihapus dari cache sejak lama. Ini tidak berlaku jika Anda menggunakan file dan segera membuangnya. (Jika Anda mencoba mlockhalaman hanya untuk menyimpannya dalam cache, Anda mencoba mengakali cache disk dan kebodohan semacam ini jarang membantu kinerja sistem).
  • Membaca file secara langsung sangat sederhana dan cepat.

Diskusi mmap / baca mengingatkan saya pada dua diskusi kinerja lainnya:

  • Beberapa programmer Java terkejut menemukan bahwa I / O nonblocking seringkali lebih lambat daripada memblokir I / O, yang masuk akal jika Anda tahu bahwa I / O nonblocking memerlukan membuat lebih banyak syscalls.

  • Beberapa pemrogram jaringan lain terkejut mengetahui bahwa epollsering kali lebih lambat daripada poll, yang masuk akal jika Anda tahu bahwa pengelolaan epollmembutuhkan lebih banyak panggilan syscalls.

Kesimpulan: Gunakan peta memori jika Anda mengakses data secara acak, simpan di sana untuk waktu yang lama, atau jika Anda tahu Anda dapat membagikannya dengan proses lain ( MAP_SHAREDtidak terlalu menarik jika tidak ada pembagian yang sebenarnya). Baca file secara normal jika Anda mengakses data secara berurutan atau membuangnya setelah membaca. Dan jika salah satu metode membuat program Anda kurang kompleks, lakukan itu . Untuk banyak kasus di dunia nyata, tidak ada cara pasti untuk menunjukkan satu lebih cepat tanpa menguji aplikasi Anda yang sebenarnya dan BUKAN patokan.

(Maaf karena membatalkan pertanyaan ini, tetapi saya sedang mencari jawaban dan pertanyaan ini terus muncul di bagian atas hasil Google.)


Perlu diingat bahwa menggunakan saran apa pun berdasarkan perangkat keras dan perangkat lunak dari tahun 2000-an, tanpa mengujinya hari ini akan menjadi pendekatan yang sangat mencurigakan. Juga, sementara banyak fakta tentang mmapvs read()di utas itu masih benar seperti sebelumnya, kinerja keseluruhan tidak dapat benar-benar ditentukan dengan menambahkan pro dan kontra, tetapi hanya dengan menguji pada konfigurasi perangkat keras tertentu. Misalnya, dapat diperdebatkan bahwa "Panggilan ke mmap memiliki lebih banyak overhead daripada membaca" - ya mmapharus menambahkan pemetaan ke tabel halaman proses, tetapi readharus menyalin semua byte baca dari kernel ke ruang pengguna.
BeeOnRope

Hasilnya adalah bahwa, pada perangkat keras (Intel modern, sekitar 2018) saya, mmapmemiliki overhead yang lebih rendah daripada readuntuk yang berukuran lebih besar daripada halaman (4 KiB). Sekarang sangat benar bahwa jika Anda ingin mengakses data secara jarang dan acak, mmapitu benar-benar baik - tetapi sebaliknya tidak perlu benar: mmapmungkin masih yang terbaik untuk akses berurutan juga.
BeeOnRope

1
@BeeOnRope: Anda mungkin skeptis terhadap saran berdasarkan perangkat keras dan perangkat lunak dari tahun 2000-an, tetapi saya bahkan lebih skeptis terhadap tolok ukur yang tidak memberikan metodologi dan data. Jika Anda ingin membuat kasing yang mmaplebih cepat, saya berharap melihat minimal seluruh perangkat pengujian (kode sumber) dengan hasil yang ditabulasi, dan nomor model prosesor.
Dietrich Epp

@BeeOnRope: Ingatlah juga bahwa ketika Anda menguji bit dari sistem memori seperti ini, microbenchmark bisa sangat menipu karena siraman TLB dapat berdampak negatif pada kinerja sisa program Anda, dan dampak ini tidak akan muncul jika Anda hanya mengukur mmap itu sendiri.
Dietrich Epp

2
@DietrichEpp - ya, saya akan berpengalaman dalam efek TLB. Catatan yang mmaptidak membilas TLB kecuali dalam keadaan yang tidak biasa (tapi munmapmungkin). Tes saya termasuk microbenchmark (termasuk munmap) dan juga "dalam aplikasi" yang berjalan dalam kasus penggunaan dunia nyata. Tentu saja aplikasi saya tidak sama dengan aplikasi Anda, jadi orang harus menguji secara lokal. Bahkan tidak jelas apakah mmapyang disukai oleh benchmark mikro: read()juga mendapat dorongan besar karena buffer tujuan sisi pengguna umumnya tetap di L1, yang mungkin tidak terjadi dalam aplikasi yang lebih besar. Jadi ya, "ini rumit".
BeeOnRope

47

Biaya kinerja utama adalah disk i / o. "mmap ()" tentu saja lebih cepat daripada istream, tetapi perbedaannya mungkin tidak terlihat karena disk i / o akan mendominasi run-times Anda.

Saya mencoba fragmen kode Ben Collins (lihat di atas / di bawah) untuk menguji pernyataannya bahwa "mmap () jauh lebih cepat" dan tidak menemukan perbedaan yang dapat diukur. Lihat komentar saya pada jawabannya.

Saya pasti tidak akan merekomendasikan secara terpisah mmap'ing masing-masing catatan pada gilirannya kecuali "catatan" Anda besar - itu akan sangat lambat, membutuhkan 2 panggilan sistem untuk setiap catatan dan mungkin kehilangan halaman dari cache disk-memori .... .

Dalam kasus Anda, saya pikir mmap (), istream dan panggilan tingkat rendah terbuka () / baca () semua akan hampir sama. Saya akan merekomendasikan mmap () dalam kasus ini:

  1. Ada akses acak (tidak berurutan) di dalam file, DAN
  2. semuanya cocok dengan nyaman dalam memori ATAU ada lokalitas referensi dalam file sehingga halaman tertentu dapat dipetakan dan halaman lain dipetakan. Dengan begitu sistem operasi menggunakan RAM yang tersedia untuk keuntungan maksimal.
  3. ATAU jika banyak proses membaca / bekerja pada file yang sama, maka mmap () fantastis karena semua proses berbagi halaman fisik yang sama.

(btw - I love mmap () / MapViewOfFile ()).


Poin bagus tentang akses acak: ini mungkin salah satu hal yang mendorong persepsi saya.
Ben Collins

1
Saya tidak akan mengatakan file tersebut harus sesuai dengan memori, hanya ke ruang alamat. Jadi pada sistem 64bit, seharusnya tidak ada alasan untuk tidak memetakan file besar. OS tahu bagaimana mengatasinya; itu logika yang sama digunakan untuk bertukar tetapi dalam hal ini tidak memerlukan ruang swap tambahan pada disk.
MvG

@ MVG: Apakah Anda mengerti maksud tentang disk i / o? Jika file tersebut sesuai dengan ruang alamat tetapi tidak memiliki memori dan Anda memiliki akses acak maka Anda dapat memiliki setiap akses rekaman yang memerlukan disk head move and seek, atau operasi halaman SSD, yang akan menjadi bencana bagi kinerja.
Tim Cooper

3
Aspek disk i / o harus independen dari metode akses. Jika Anda benar-benar memiliki akses acak ke file yang lebih besar dari RAM, baik mmap dan seek + read sangat terikat disk. Kalau tidak keduanya akan mendapat manfaat dari cache. Saya tidak melihat ukuran file dibandingkan dengan ukuran memori sebagai argumen yang kuat di kedua arah. Ukuran file vs ruang alamat, di sisi lain, adalah argumen yang sangat kuat, terutama untuk akses yang benar-benar acak.
MvG

Jawaban asli saya memiliki dan memiliki poin ini: "semuanya cocok dengan nyaman dalam memori ATAU ada lokalitas referensi dalam file". Jadi poin ke-2 membahas apa yang Anda katakan.
Tim Cooper

43

mmap jauh lebih cepat. Anda dapat menulis tolok ukur sederhana untuk membuktikannya kepada diri sendiri:

char data[0x1000];
std::ifstream in("file.bin");

while (in)
{
  in.read(data, 0x1000);
  // do something with data
}

melawan:

const int file_size=something;
const int page_size=0x1000;
int off=0;
void *data;

int fd = open("filename.bin", O_RDONLY);

while (off < file_size)
{
  data = mmap(NULL, page_size, PROT_READ, 0, fd, off);
  // do stuff with data
  munmap(data, page_size);
  off += page_size;
}

Jelas, saya meninggalkan detail (seperti bagaimana menentukan kapan Anda mencapai akhir file jika file Anda bukan kelipatan page_size, misalnya), tapi itu seharusnya tidak jauh lebih rumit dari ini .

Jika Anda bisa, Anda dapat mencoba memecah data Anda menjadi beberapa file yang dapat mmap () - ed secara keseluruhan alih-alih sebagian (jauh lebih sederhana).

Beberapa bulan yang lalu saya menerapkan setengah-slide mmap () - ed stream class untuk boost_iostreams, tetapi tidak ada yang peduli dan saya sibuk dengan hal-hal lain. Sayangnya, saya menghapus arsip proyek yang belum selesai beberapa minggu yang lalu, dan itu adalah salah satu korban :-(

Pembaruan : Saya juga harus menambahkan peringatan bahwa benchmark ini akan terlihat sangat berbeda di Windows karena Microsoft menerapkan cache file bagus yang melakukan sebagian besar dari apa yang Anda lakukan dengan mmap di tempat pertama. Yaitu, untuk file yang sering diakses, Anda bisa melakukan std :: ifstream.read () dan itu akan secepat mmap, karena cache file sudah melakukan pemetaan memori untuk Anda, dan transparan.

Pembaruan Akhir : Lihat, orang-orang: di banyak kombinasi platform OS dan perpustakaan standar serta disk dan hierarki memori yang berbeda, saya tidak dapat mengatakan dengan pasti bahwa panggilan sistem mmap, dipandang sebagai kotak hitam, akan selalu selalu jauh lebih cepat secara substansial dari read. Itu bukan maksud saya, bahkan jika kata-kata saya bisa ditafsirkan seperti itu. . Satu-satunya cara untuk benar-benar yakin bahwa Anda menggunakan i / o yang dipetakan dalam memori dengan cara portabel adalah dengan menggunakannya . Jika Anda tidak peduli tentang portabilitas dan Anda dapat mengandalkan karakteristik khusus platform target Anda, maka menggunakan mungkin cocok tanpa mengorbankan kinerja apa pun yang terukur.Pada akhirnya, maksud saya adalah bahwa memori yang dipetakan i / o umumnya lebih cepat dari byte berbasis i / o; ini masih benar . Jika Anda menemukan secara eksperimental bahwa tidak ada perbedaan antara keduanya, maka satu-satunya penjelasan yang tampaknya masuk akal bagi saya adalah bahwa platform Anda mengimplementasikan pemetaan memori di bawah penutup dengan cara yang menguntungkan bagi kinerja panggilan kereadmmapread

Edit untuk membersihkan daftar jawaban: @ jbl:

jendela geser mmap terdengar menarik. Bisakah Anda mengatakan lebih banyak tentang itu?

Tentu - saya sedang menulis perpustakaan C ++ untuk Git (libgit ++, jika Anda mau), dan saya mengalami masalah yang sama dengan ini: Saya harus dapat membuka file besar (sangat besar) dan tidak memiliki kinerja menjadi total dog (Seperti halnya dengan std::fstream).

Boost::Iostreamssudah memiliki Sumber mapped_file, tetapi masalahnya adalah ia meng- mmapping seluruh file, yang membatasi Anda untuk 2 ^ (wordsize). Pada mesin 32-bit, 4GB tidak cukup besar. Bukan tidak masuk akal untuk berharap memiliki .packfile di Git yang menjadi jauh lebih besar dari itu, jadi saya perlu membaca file dalam potongan tanpa menggunakan file i / o biasa. Di balik sampul Boost::Iostreams, saya menerapkan Sumber, yang kurang lebih merupakan pandangan lain tentang interaksi antara std::streambufdan std::istream. Anda juga bisa mencoba pendekatan serupa dengan hanya mewarisi std::filebufke dalam mapped_filebufdan juga mewarisi std::fstreamke dalam a mapped_fstream. Interaksi antara keduanya itulah yang sulit dilakukan dengan benar. Boost::Iostreams memiliki beberapa pekerjaan yang dilakukan untuk Anda, dan juga menyediakan kait untuk filter dan rantai, jadi saya pikir akan lebih bermanfaat untuk menerapkannya dengan cara itu.


3
RE: mmaped file cache pada Windows. Tepatnya: ketika buffering file diaktifkan, memori kernel memetakan file yang Anda baca secara internal, membaca buffer itu dan menyalinnya kembali ke proses Anda. Seolah-olah memori Anda memetakannya sendiri kecuali dengan langkah menyalin tambahan.
Chris Smith

6
Saya enggan untuk tidak setuju dengan jawaban yang diterima, tetapi saya yakin jawaban ini salah. Saya mengikuti saran Anda dan mencoba kode Anda, pada mesin Linux 64bit, dan mmap () tidak lebih cepat dari implementasi STL. Juga, secara teoritis saya tidak berharap 'mmap ()' lebih cepat (atau lebih lambat).
Tim Cooper

3
@Tim Cooper: Anda mungkin menemukan utas ini ( markmail.org/message/… ) menarik. Perhatikan dua hal: mmap tidak dioptimalkan dengan benar di Linux, dan satu juga perlu menggunakan madvise dalam pengujian mereka untuk mendapatkan hasil terbaik.
Ben Collins

9
Dear Ben: Saya sudah membaca tautan itu. Jika 'mmap ()' tidak lebih cepat di Linux, dan MapViewOfFile () tidak lebih cepat di Windows, maka dapatkah Anda membuat klaim bahwa "mmap jauh lebih cepat"? Juga, untuk alasan teoretis, saya percaya mmap () tidak lebih cepat untuk pembacaan berurutan - apakah Anda punya penjelasan sebaliknya?
Tim Cooper

11
Ben, mengapa repot-repot mmap()mengarsipkan halaman sekaligus? Jika a size_tcukup luas untuk menampung ukuran file (sangat mungkin pada sistem 64-bit), maka hanya mmap()seluruh file dalam satu panggilan.
Steve Emmerson

39

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 , mmapmungkin tampak seperti sulap :

  1. mmap hanya membutuhkan 1 panggilan sistem untuk (berpotensi) memetakan seluruh file, setelah itu tidak ada lagi panggilan sistem yang diperlukan.
  2. mmap tidak memerlukan salinan data file dari kernel ke ruang pengguna.
  3. mmapmemungkinkan 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 mmapvs read(2)(yang sebenarnya merupakan syscall OS-level yang sebanding untuk membaca blok ) adalah bahwa mmapAnda harus melakukan "beberapa pekerjaan" untuk setiap halaman 4K di ruang pengguna, meskipun mungkin disembunyikan oleh mekanisme kesalahan halaman.

Sebagai contoh implementasi khas yang hanya mmaps 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_POPULATEuntuk mmapmengatakannya 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 mmappendekatan, 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 mmaping 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 mallocbuffer tunggal buffer kecil di ruang pengguna, dan menggunakannya kembali berulang kali untuk semua readpanggilan 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 mmappendekatan 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, mmappendekatan 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_POPULATEimplementasi 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_userkinerja 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 mmapkasus tanpa MAP_POPULATE.
  • Penambahan copy_to_usermetode jalur cepat di arch/x86/lib/copy_user_64.S, misalnya, menggunakan REP MOVQketika 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 mmapmetode 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_POPULATEdan 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 mmapdan readpanggilan, 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 madviseatau fadvisepanggilan (dan perubahan level aplikasi apa pun yang dapat Anda lakukan untuk meningkatkan pola akses).

3 Anda bisa menyiasatinya, misalnya, dengan secara berurutan mmapmasuk ke jendela dengan ukuran lebih kecil, katakanlah 100 MB.

4 Faktanya, ternyata MAP_POPULATEpendekatannya 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.


4
Terima kasih telah memberikan jawaban yang lebih bernuansa untuk masalah kompleks ini. Tampak jelas bagi kebanyakan orang bahwa mmap lebih cepat, padahal kenyataannya seringkali tidak demikian. Dalam percobaan saya, secara acak mengakses database 100GB besar dengan indeks dalam memori ternyata lebih cepat dengan pread (), meskipun saya membuat buffer untuk masing-masing dari jutaan akses. Dan sepertinya banyak orang di industri telah mengamati hal yang sama .
Caetano Sauer

5
Ya, itu sangat tergantung pada skenario. Jika Anda membaca cukup kecil dan dari waktu ke waktu Anda cenderung membaca byte yang sama berulang kali, mmapakan memiliki keuntungan yang tidak dapat diatasi karena ia menghindari overhead panggilan kernel yang tetap. Di sisi lain, mmapjuga meningkatkan tekanan TLB, dan benar-benar membuat menjadi lebih lambat untuk fase "pemanasan" di mana byte sedang dibaca untuk pertama kalinya dalam proses saat ini (meskipun mereka masih di halaman halaman), karena mungkin lebih banyak pekerjaan daripada read, misalnya ke "kesalahan sekitar" halaman yang berdekatan ... dan untuk aplikasi yang sama "pemanasan" adalah yang terpenting! @CaetanoSauer
BeeOnRope

Saya pikir di mana Anda mengatakan "... tetapi 25 miliar kesalahan halaman masih tidak akan menjadi super cepat ..." itu seharusnya berbunyi "... tapi 25 juta kesalahan halaman masih tidak akan menjadi sangat cepat ..." . Saya tidak 100% positif, jadi itu sebabnya saya tidak mengedit secara langsung.
Ton van den Heuvel

7

Maaf Ben Collins kehilangan kode sumber windows mmap sliding-nya. Itu bagus untuk dimiliki di Boost.

Ya, memetakan file jauh lebih cepat. Anda pada dasarnya menggunakan subsistem memori virtual OS untuk mengaitkan memori ke disk dan sebaliknya. Pikirkan seperti ini: jika pengembang kernel OS dapat membuatnya lebih cepat, mereka akan melakukannya. Karena melakukan hal itu membuat segalanya menjadi lebih cepat: database, waktu boot, waktu buka program, dan sebagainya.

Pendekatan sliding window sebenarnya tidak terlalu sulit karena banyak halaman yang bisa dipetakan sekaligus. Jadi ukuran rekaman tidak masalah asalkan yang terbesar dari setiap rekaman akan masuk ke dalam memori. Yang penting adalah mengelola pembukuan.

Jika catatan tidak dimulai pada batas getpagesize (), pemetaan Anda harus dimulai pada halaman sebelumnya. Panjang wilayah yang dipetakan memanjang dari byte pertama catatan (dibulatkan ke bawah jika perlu hingga kelipatan getpagesize ()) terdekat ke byte terakhir dari catatan (dibulatkan ke kelipatan getpagesize terdekat ()). Saat Anda selesai memproses catatan, Anda dapat menghapus peta (), dan melanjutkan ke yang berikutnya.

Ini semua berfungsi dengan baik di bawah Windows juga menggunakan CreateFileMapping () dan MapViewOfFile () (dan GetSystemInfo () untuk mendapatkan SYSTEM_INFO.dwAllocationGranularity --- bukan SYSTEM_INFO.dwPageSize).


Saya baru saja mencari di Google dan menemukan potongan kecil ini tentang dwAllocationGranularity - Saya menggunakan dwPageSize dan semuanya rusak. Terima kasih!
wickedchicken

4

mmap harus lebih cepat, tetapi saya tidak tahu berapa banyak. Ini sangat tergantung pada kode Anda. Jika Anda menggunakan mmap yang terbaik adalah mmap seluruh file sekaligus, itu akan membuat hidup Anda jauh lebih mudah. Satu masalah potensial adalah bahwa jika file Anda lebih besar dari 4GB (atau dalam praktiknya batasnya lebih rendah, seringkali 2GB) Anda akan memerlukan arsitektur 64bit. Jadi jika Anda menggunakan lingkungan 32, Anda mungkin tidak ingin menggunakannya.

Karena itu, mungkin ada rute yang lebih baik untuk meningkatkan kinerja. Anda mengatakan file input akan dipindai berkali-kali , jika Anda bisa membacanya dalam satu pass dan kemudian selesai dengan itu, itu bisa berpotensi jauh lebih cepat.


3

Mungkin Anda harus melakukan pra-proses file, sehingga setiap catatan dalam file yang terpisah (atau setidaknya bahwa setiap file adalah ukuran yang mampu mmap).

Bisakah Anda melakukan semua langkah pemrosesan untuk setiap record, sebelum pindah ke yang berikutnya? Mungkin itu akan menghindari beberapa overhead IO?


3

Saya setuju bahwa file mmap'd I / O akan menjadi lebih cepat, tapi saat Anda pembandingan kode, seharusnya tidak counter contoh akan agak dioptimalkan?

Ben Collins menulis:

char data[0x1000];
std::ifstream in("file.bin");

while (in)
{
    in.read(data, 0x1000);
    // do something with data 
}

Saya sarankan juga mencoba:

char data[0x1000];
std::ifstream iifle( "file.bin");
std::istream  in( ifile.rdbuf() );

while( in )
{
    in.read( data, 0x1000);
    // do something with data
}

Dan lebih dari itu, Anda mungkin juga mencoba membuat ukuran buffer dengan ukuran yang sama dengan satu halaman memori virtual, jika 0x1000 bukan ukuran satu halaman memori virtual pada mesin Anda ... IMHO mmap akan mengajukan I / O masih menang, tetapi ini harus membuat segalanya lebih dekat.


2

Menurut saya, menggunakan mmap () "hanya" mencabut pengembang dari harus menulis kode caching mereka sendiri. Dalam kasus sederhana "baca file sekali saja", ini tidak akan sulit (meskipun seperti yang ditunjukkan mlbrock Anda masih menyimpan salinan memori ke dalam ruang proses), tetapi jika Anda bolak-balik dalam file atau melewatkan bit dan sebagainya, saya percaya pengembang kernel mungkin telah melakukan pekerjaan yang lebih baik dalam mengimplementasikan caching daripada yang saya bisa ...


1
Kemungkinan besar Anda bisa melakukan pekerjaan yang lebih baik dari caching data spesifik aplikasi Anda daripada kernel dapat, yang beroperasi pada potongan ukuran halaman dengan cara yang sangat buta (misalnya, itu hanya menggunakan skema pseudo-LRU sederhana untuk memutuskan halaman mana yang akan diusir ) - sementara Anda mungkin tahu banyak tentang granularity caching yang tepat dan juga memiliki gagasan yang baik tentang pola akses di masa depan. Manfaat sebenarnya dari mmapcaching adalah Anda cukup menggunakan kembali cache halaman yang sudah ada yang sudah ada di sana, sehingga Anda mendapatkan memori itu secara gratis, dan itu dapat dibagikan di seluruh proses juga.
BeeOnRope

2

Saya ingat memetakan file besar yang berisi struktur pohon ke memori tahun yang lalu. Saya kagum dengan kecepatan dibandingkan dengan normalisasi serialisasi yang melibatkan banyak pekerjaan dalam memori, seperti mengalokasikan node pohon dan pengaturan pointer. Jadi sebenarnya saya membandingkan satu panggilan ke mmap (atau rekannya di Windows) terhadap banyak (BANYAK) panggilan ke panggilan operator dan konstruktor baru. Untuk tugas semacam itu, mmap tidak terkalahkan dibandingkan dengan de-serialisasi. Tentu saja orang harus melihat ke dalam pointer relocatable untuk ini.


Itu terdengar seperti resep untuk bencana. Apa yang Anda lakukan jika tata letak objek berubah? Jika Anda memiliki fungsi virtual, semua pointer vftbl mungkin salah. Bagaimana Anda mengontrol ke mana file dipetakan? Anda dapat memberikannya alamat, tetapi itu hanya petunjuk dan kernel dapat memilih alamat basis lainnya.
Jens

Ini berfungsi dengan baik ketika Anda memiliki tata letak pohon yang stabil dan jelas. Kemudian Anda dapat melemparkan semuanya ke struct yang relevan dan mengikuti pointer file internal dengan menambahkan offset "alamat awal mmap" setiap kali. Ini sangat mirip dengan sistem file menggunakan inode dan pohon direktori
Mike76

1

Ini kedengarannya seperti kasus penggunaan yang baik untuk multi-threading ... Saya pikir Anda bisa dengan mudah mengatur satu utas untuk membaca data sementara yang lain memprosesnya. Itu mungkin cara untuk secara dramatis meningkatkan kinerja yang dirasakan. Hanya pemikiran saja.


Ya. Saya telah memikirkan hal itu dan mungkin akan mencobanya nanti. Satu-satunya reservasi yang saya miliki adalah pemrosesan jauh lebih pendek daripada latensi I / O, jadi mungkin tidak banyak manfaatnya.
jbl

1

Saya pikir hal terbesar tentang mmap berpotensi membaca asinkron dengan:

    addr1 = NULL;
    while( size_left > 0 ) {
        r = min(MMAP_SIZE, size_left);
        addr2 = mmap(NULL, r,
            PROT_READ, MAP_FLAGS,
            0, pos);
        if (addr1 != NULL)
        {
            /* process mmap from prev cycle */
            feed_data(ctx, addr1, MMAP_SIZE);
            munmap(addr1, MMAP_SIZE);
        }
        addr1 = addr2;
        size_left -= r;
        pos += r;
    }
    feed_data(ctx, addr1, r);
    munmap(addr1, r);

Masalahnya adalah saya tidak dapat menemukan MAP_FLAGS yang tepat untuk memberikan petunjuk bahwa memori ini harus disinkronkan dari file secepatnya. Saya harap MAP_POPULATE memberikan petunjuk yang tepat untuk mmap (artinya ia tidak akan mencoba memuat semua konten sebelum kembali dari panggilan, tetapi akan melakukannya dalam async. Dengan feed_data). Setidaknya itu memberikan hasil yang lebih baik dengan flag ini bahkan manual itu menyatakan bahwa ia tidak melakukan apa-apa tanpa MAP_PRIVATE sejak 2.6.23.


2
Anda ingin posix_madvisedenganWILLNEED bendera untuk petunjuk malas untuk mengisi lebih dulu.
ShadowRanger

@ShadowRanger, terdengar masuk akal. Meskipun saya akan memperbarui halaman manual untuk secara jelas menyatakan bahwa itu posix_madviseadalah panggilan async. Juga akan menyenangkan untuk referensi mlockbagi mereka yang ingin menunggu sampai seluruh wilayah memori tersedia tanpa kesalahan halaman.
ony
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.