mmap () ve okuma blokları


185

Potansiyel olarak 100 GB veya daha fazla boyutta dosyaları işleyecek bir program üzerinde çalışıyorum. Dosyalar değişken uzunluktaki kayıt kümelerini içerir. İlk çalıştırmayı çalıştırdım ve şimdi giriş dosyası birçok kez tarandığından, özellikle G / Ç'yi daha verimli bir şekilde yapmak için performansı iyileştirmeye çalışıyorum.

mmap()C ++ fstreamkütüphanesi aracılığıyla bloklar halinde okumaya karşı kullanmak için bir kural var mı ? Ne yapmak istiyorum büyük blokları diskten bir arabellek okumak, arabellekten tam kayıtları işlemek ve daha fazlasını okuyun.

mmap()Kod potansiyel olarak çok beri dağınık alabilir mmapd blokları sayfa sınırlarını (benim anlayış) büyüklüğünde üzerine yalan gerekir' ve kayıtlar olabilir potansiyel karşısında gibi sayfa sınırları. İle fstreams, ben sadece bir kaydın başlangıç için talep edebilirsiniz ve biz sayfadaki yalan sınırlarını büyüklüğünde olduğunu bloklarını okurken sınırlı değiliz, çünkü tekrar okumaya başlar.

İlk önce tam bir uygulama yazmadan bu iki seçenek arasında nasıl karar verebilirim? Temel kurallar (örneğin, mmap()2 kat daha hızlı) veya basit testler var mı?


1
Bu ilginç bir okuma: medium.com/@sasha_f/… Deneylerde mmap(), sistem çağrılarını kullanmaktan 2-6 kat daha hızlıdır, örn read().
mplattner

Yanıtlar:


208

Linux'ta mmap / read performansıyla ilgili son kelimeyi bulmaya çalışıyordum ve Linux çekirdek posta listesinde güzel bir gönderiye ( bağlantı ) rastladım . Bu 2000 yılından, yani IO ve o zamandan beri çekirdekte sanal bellek birçok gelişmeler olmuştur, ama güzel nedenini açıklar mmapveya readdaha hızlı veya daha yavaş olabilir.

  • Bir çağrının mmapdaha fazla yükü vardır read(tıpkı epolldaha fazla yükü pollvardır, daha fazla yükü vardır read). Sanal bellek eşlemelerini değiştirmek, bazı işlemcilerde farklı işlemler arasında geçişin pahalı olması nedeniyle oldukça pahalı bir işlemdir.
  • IO sistemi disk önbelleğini zaten kullanabilir, bu nedenle bir dosyayı okursanız, hangi yöntemi kullanırsanız kullanın önbelleğe çarpar veya özlüyorsunuz.

Ancak,

  • Bellek haritaları, rasgele erişim için genellikle daha hızlıdır, özellikle erişim düzenleriniz seyrek ve öngörülemezse.
  • Hafıza haritaları izin tutmak bitirdiniz kadar önbellekten sayfaları kullanarak. Bu, bir dosyayı uzun süre yoğun bir şekilde kullanırsanız, kapatın ve yeniden açın, sayfaların hala önbelleğe alınacağı anlamına gelir. İle read, dosyanız yıllar önce önbellekten temizlenmiş olabilir. Bir dosyayı kullanır ve hemen atarsanız bu geçerli değildir. ( mlockSayfaları yalnızca önbellekte tutmaya çalışırsanız , disk önbelleğini alt etmeye çalışıyorsunuz ve bu tür bir aptallık nadiren sistem performansına yardımcı oluyor).
  • Bir dosyayı doğrudan okumak çok basit ve hızlıdır.

Mmap / read tartışması bana diğer iki performans tartışmasını hatırlatıyor:

  • Bazı Java programcıları, engellemesiz G / Ç'nin genellikle G / Ç'yi engellemekten daha yavaş olduğunu keşfettiklerinde şok oldular.

  • Diğer bazı ağ programcıları epoll, genellikle daha yavaş olduğunu öğrenmek için şok oldu poll, bu da yönetimin epolldaha fazla sistem araması gerektirdiğini biliyorsanız mükemmel bir anlam ifade ediyor .

Sonuç: Verilere rasgele erişirseniz, uzun süre saklarsanız veya diğer işlemlerle paylaşabileceğinizi biliyorsanız ( MAP_SHAREDgerçek paylaşım yoksa çok ilginç değildir) bellek haritalarını kullanın . Verilere sırayla erişirseniz veya okuduktan sonra atarsanız dosyaları normal şekilde okuyun. Her iki yöntem programınızı daha az karmaşık hale getirir eğer, bunu o . Birçok gerçek dünya vakası için, gerçek uygulamanızı test etmeden bir ölçüt DEĞİL olmadan daha hızlı olduğunu göstermenin hiçbir yolu yoktur.

(Bu soruyu soruşturduğum için üzgünüm, ancak bir cevap arıyordum ve bu soru Google sonuçlarının en üstünde yer almaya devam etti.)


2000'li yıllardan itibaren donanım ve yazılıma dayalı herhangi bir tavsiyenin bugün test edilmeden kullanılmasının çok şüpheli bir yaklaşım olacağını unutmayın. Ayrıca, bu iş parçacığında mmapvs read()ile ilgili gerçeklerin çoğu geçmişte olduğu gibi hala doğru olsa da, genel performans gerçekten artıları ve eksileri ekleyerek belirlenemez, ancak sadece belirli bir donanım yapılandırmasını test ederek belirlenir. Örneğin, "mmap çağrısının okunmaktan daha fazla yükü vardır" - evet mmap, işlem sayfası tablosuna eşlemeler eklemek readzorundadır , ancak tüm okunan baytları çekirdekten kullanıcı alanına kopyalamak zorundadır.
BeeOnRope

Sonuç olarak, (modern Intel, 2018 dolaylarında) donanımımda, sayfa boyutundan daha büyük (4 KiB) okumalara göre daha mmapdüşük ek yükü var read. Verilere seyrek ve rasgele erişmek istiyorsanız mmap, gerçekten, gerçekten iyi - ancak bunun tersi gerekli değil: mmapsıralı erişim için de en iyisi olabilir.
BeeOnRope

1
@BeeOnRope: 2000'li yıllardan itibaren donanım ve yazılıma dayalı tavsiye konusunda şüpheci olabilirsiniz, ancak bir metodoloji ve veri sağlamayan kıstaslardan daha şüpheliyim. Daha mmaphızlı bir vaka yapmak istiyorsanız, en azından tablo sonuçlarının ve işlemci model numarasının tüm test cihazını (kaynak kodu) çıplak bir şekilde görmeyi beklerim.
Dietrich Epp

@BeeOnRope: Ayrıca, bu gibi bellek sisteminin bitlerini test ederken, mikrobenchmarkların son derece aldatıcı olabileceğini unutmayın, çünkü bir TLB yıkaması programınızın geri kalanının performansını olumsuz etkileyebilir ve bu etki, sadece mmap'in kendisini ölçersiniz.
Dietrich Epp

2
@DietrichEpp - evet, TLB efektleri konusunda bilgili olacağım. mmapAlışılmadık durumlar dışında TLB'yi temizlemediğini unutmayın (ancak munmapolabilir). Testlerim hem mikrobenç işaretleri (hem de dahil munmap) hem de gerçek dünyadaki kullanım durumunda çalışan "uygulamada" yı içeriyordu . Tabii ki uygulamam uygulamanızla aynı değil, bu yüzden insanlar yerel olarak test etmelidir. mmapMikro bir ölçüt tarafından tercih edilen net değil : read()ayrıca kullanıcı tarafı hedef tamponu genellikle daha büyük bir uygulamada olmayabilecek olan L1'de kaldığı için büyük bir destek alır. Yani evet, "karmaşık".
BeeOnRope

47

Ana performans maliyeti disk i / o olacak. "mmap ()" kesinlikle istream'den daha hızlıdır, ancak disk g / Ç çalışma sürelerinize hakim olacağından fark fark edilmeyebilir.

(Aşağıya / yukarıya bakınız) "mmap () olduğunu iddiasını test etmek Ben Collins'in kod parçası çalıştı yolu daha hızlı" ve ölçülebilir bir fark bulunamadı. Cevabı hakkındaki yorumlarımı görün.

Kesinlikle ediyorum değil sizin "kayıtları" büyük olmadıkça ayrı ayrı sırayla her kayıt mmap'ing tavsiye - o korkunç yavaş olacağını, her kayıt için 2 sistem çağrıları gerektiren ve muhtemelen disk önbellek dışına sayfasını kaybediyor .... .

Sizin durumunuzda mmap (), istream ve düşük seviyeli open () / read () çağrılarının hepsi aynı olacaktır. Bu durumlarda mmap () tavsiye ederim:

  1. Dosyada rasgele erişim (sıralı değil) var, VE
  2. her şey belleğe rahatça sığıyor VEYA belirli sayfaların eşlenebilmesi ve diğer sayfaların eşlenebilmesi için dosya içinde referans konumu var. Bu şekilde işletim sistemi maksimum fayda sağlamak için kullanılabilir RAM'i kullanır.
  3. VEYA birden çok işlem aynı dosya üzerinde okuyor / çalışıyorsa, mmap () harika çünkü işlemlerin hepsi aynı fiziksel sayfaları paylaşıyor.

(btw - mmap () / MapViewOfFile ()) seviyorum.


Rasgele erişim hakkında iyi bir nokta: bu benim algımı yönlendiren şeylerden biri olabilir.
Ben Collins

1
Dosyanın belleğe, sadece adres alanına rahatça sığması gerektiğini söyleyemem. 64bit sistemlerde, büyük dosyaları eşlememek için hiçbir neden olmamalıdır. İşletim sistemi bununla nasıl başa çıkacağını bilir; takas için kullanılan mantıkla aynıdır, ancak bu durumda diskte ek takas alanı gerektirmez.
MvG

@MvG: Disk i / o ile ilgili noktayı anlıyor musunuz? Dosya adres alanına sığar ancak belleğe sığmazsa ve rasgele erişiminiz varsa, disk kafası taşıma ve arama gerektiren her kayıt erişimine veya performans için felaket olabilecek bir SSD sayfa işlemine sahip olabilirsiniz.
Tim Cooper

3
Disk i / o yönü erişim yönteminden bağımsız olmalıdır. RAM'den daha büyük dosyalara gerçekten rasgele erişebiliyorsanız, hem mmap hem de seek + read ciddi şekilde disklere bağlıdır. Aksi takdirde her ikisi de önbelleklerden yararlanır. Her iki yönde güçlü bir argüman olarak bellek boyutu ile karşılaştırıldığında dosya boyutunu görmüyorum. Dosya boyutu ve adres alanı, özellikle de gerçekten rasgele erişim için çok güçlü bir argüman.
MvG

Benim asıl cevabım şu ve bu noktaya sahipti: "her şey hafızaya rahatça uyuyor VEYA dosyada referans yeri var". Böylece 2. nokta söylediklerinizi ele alır.
Tim Cooper

43

mmap olduğu yolu daha hızlı. Kendinizi kanıtlamak için basit bir kıyaslama yazabilirsiniz:

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

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

karşı:

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;
}

Açıkça, ayrıntıları dışarıda bırakıyorum (örneğin dosyanızın katı olmadığı durumda dosyanın sonuna ne zaman ulaşacağınızı nasıl belirleyeceğiniz gibi page_size), ancak gerçekten bundan daha karmaşık olmamalıdır .

Mümkünse, verilerinizi kısmen yerine mmap () olarak düzenlenebilen (çok daha basit) birden fazla dosyaya bölmeyi deneyebilirsiniz.

Birkaç ay önce, boost_iostreams için sürgülü pencere mmap () - ed akışı sınıfının yarı pişmiş bir uygulamam vardı, ancak kimse umursamadı ve diğer şeylerle meşgul oldum. Ne yazık ki, birkaç hafta önce bitmemiş eski projelerin arşivini sildim ve bu kurbanlardan biriydi :-(

Güncelleştirme : Microsoft'un ilk etapta mmap ile yapacağınız şeylerin çoğunu yapan şık bir dosya önbelleği uyguladığı için bu karşılaştırmanın Windows'da oldukça farklı görüneceği uyarısını da eklemeliyim. Yani, sık erişilen dosyalar için, sadece std :: ifstream.read () yapabilir ve mmap kadar hızlı olurdu, çünkü dosya önbelleği zaten sizin için bir bellek eşlemesi yapmış olurdu ve şeffaftır.

Son Güncelleme : Bakın, insanlar: OS ve standart kütüphaneler ve diskler ve bellek hiyerarşilerinin birçok farklı platform kombinasyonunda, mmapkara kutu olarak görülen sistem çağrısının her zaman her zaman çok daha hızlı olacağını kesin olarak söyleyemem daha read. Sözlerim bu şekilde yorumlanabilse bile, niyetim tam olarak bu değildi. Nihayetinde, benim açımdan, bellek eşlemeli g / Ç genellikle bayt tabanlı g / ç'den daha hızlıydı; bu hala doğrudur . Deneysel olarak ikisi arasında bir fark olmadığını görürseniz, o zaman benim için makul görünen tek açıklama, platformunuzun, kapakların altındaki bellek eşlemesini çağrıların performansına avantajlı bir şekilde uyguladığıdır.read. Bellek eşlemeli G / Ç'yi taşınabilir bir şekilde kullandığınızdan kesinlikle emin olmanın tek yolu kullanmaktır mmap. Taşınabilirliği umursamıyorsanız ve hedef platformlarınızın belirli özelliklerine güvenebiliyorsanız, readölçülebilir bir performanstan ödün vermeden kullanmak uygun olabilir.

Yanıt listesini temizlemek için düzenleyin: @jbl:

sürgülü pencere mmap ilginç geliyor. Biraz daha bahsedebilir misin?

Tabii - Git için bir C ++ kütüphanesi yazıyordum (eğer bir libgit ++, eğer) ve buna benzer bir sorunla karşılaştım: Büyük (çok büyük) dosyaları açabilmem ve performansın toplam bir köpek olmaması gerekiyordu (olduğu gibi std::fstream).

Boost::Iostreamszaten bir mapped_file Kaynağı var, ancak sorun, mmaptüm dosyalara ping atmasıydı, bu da sizi 2 ^ (wordsize) ile sınırlıyor. 32 bit makinelerde, 4GB yeterince büyük değil. .packGit'te bundan çok daha büyük dosyalar olmasını beklemek mantıksız değil , bu yüzden normal dosya g / ç'sine başvurmadan dosyayı parçalar halinde okumam gerekiyordu. Kapakları altında, ve Boost::Iostreamsarasındaki etkileşimin az çok başka bir görünümü olan bir Kaynak uyguladım . Benzer bir yaklaşımı, yalnızca bir ve benzer şekilde miras alarak miras alabilirsiniz . Doğru anlaşılması zor olan ikisi arasındaki etkileşimdir. std::streambufstd::istreamstd::filebufmapped_filebufstd::fstreama mapped_fstreamBoost::Iostreams sizin için yapılmış bazı işler var, ve aynı zamanda filtreler ve zincirler için kancalar sağlar, bu yüzden bu şekilde uygulamanın daha yararlı olacağını düşündüm.


3
RE: Windows üzerinde mmaped dosya önbelleği. Tam olarak: dosya arabelleğe alma etkinleştirildiğinde, çekirdek belleği dahili olarak okuduğunuz dosyayı eşler, bu arabelleğe okur ve tekrar işleminize kopyalar. Sanki fazladan bir kopya adımı dışında bellek eşlemiş gibi.
Chris Smith

6
Kabul edilen bir cevapla aynı fikirde değilim ama bu cevabın yanlış olduğuna inanıyorum. Öneriyi takip ettim ve kodunuzu 64bit Linux makinesinde denedim ve mmap (), STL uygulamasından daha hızlı değildi. Ayrıca teorik olarak 'mmap ()' nin daha hızlı (veya daha yavaş) olmasını beklemezdim.
Tim Cooper

3
@Tim Cooper: Bu konuyu ( markmail.org/message/… ) ilginizi çekebilir . İki şeye dikkat edin: mmap Linux'ta uygun şekilde optimize edilmemiştir ve en iyi sonuçları almak için testlerinde madvise kullanmanız gerekir.
Ben Collins

9
Sevgili Ben: Bu bağlantıyı okudum. Linux'ta 'mmap ()' daha hızlı değilse ve Windows'ta MapViewOfFile () daha hızlı değilse, "mmap çok daha hızlı" iddiasında bulunabilirsiniz? Ayrıca, teorik nedenlerden dolayı mmap () 'nin sıralı okumalar için daha hızlı olmadığına inanıyorum - aksine bir açıklamanız var mı?
Tim Cooper

11
Ben, mmap()dosyaya neden her seferinde bir sayfa zahmet ettin ? A size_t, dosyanın boyutunu tutacak kadar büyükse (büyük olasılıkla 64 bit sistemlerde), mmap()tek bir çağrıda yalnızca tüm dosya.
Steve Emmerson

39

Burada dikkat çeken noktaların çoğunu kapsayan birçok iyi cevap var, bu yüzden doğrudan yukarıda ele alınmadığını gördüğüm birkaç konuyu ekleyeceğim. Yani, bu cevap artılarını ve eksilerini kapsamlı olarak değil, buradaki diğer cevaplara bir ek olarak düşünülmelidir.

mmap sihir gibi görünüyor

Dosyanın zaten temel olarak 2 önbelleğe alındığı durumda 1 , sihir gibi görünebilir :mmap

  1. mmap tüm dosyayı (potansiyel olarak) eşlemek için yalnızca 1 sistem çağrısı gerektirir, bundan sonra daha fazla sistem çağrısı gerekmez.
  2. mmap dosya verilerinin çekirdekten kullanıcı alanına kopyalanmasını gerektirmez.
  3. mmapotomatik olarak derleme otomatik vektörleştirme, SIMD intrinsics, önceden getirme, optimize edilmiş bellekte ayrıştırma rutinleri, OpenMP, vb.

Dosyanın zaten önbellekte olması durumunda yenilmesi imkansız gibi görünüyor: sadece çekirdek sayfası önbelleğine bellek olarak doğrudan erişiyorsunuz ve bundan daha hızlı olamazsınız.

Evet, olabilir.

mmap aslında sihir değil çünkü ...

mmap hala sayfa başına çalışıyor

Birincil gizli mmapvs vs read(2)(gerçekten okuma blokları için karşılaştırılabilir işletim sistemi düzeyinde sistem çağrısıdır) ) ile olmasıdır mmapbunu tarafından gizlenmiş olabilir rağmen, kullanıcı uzayda her 4K sayfa için "bazı işler" yapmak gerekir sayfa hata mekanizması.

Bir örnek için mmap, tüm dosyanın sadece 100 GB / 4K = 25 milyon hatayı 100 GB'lık bir dosyayı okumak için hatalı olması gerekir. Şimdi bunlar küçük hatalar, ancak 25 milyar sayfa hatası hala süper hızlı olmayacak. Küçük bir hatanın maliyeti muhtemelen en iyi durumda 100'lü yıllardadır.

mmap büyük ölçüde TLB performansına güveniyor

Şimdi, geçebilir MAP_POPULATEiçin mmapdönmeden önce tüm sayfa tabloları kurmak için söylemek, bu yüzden erişirken hiçbir sayfa hataları olmamalıdır. Şimdi, bu da tüm dosyayı RAM'e okuyor, 100GB'lık bir dosyayı eşleştirmeye çalışırsanız patlayacak - ama şimdilik bunu görmezden gelelim 3 . Çekirdeğin bu sayfa tablolarını ayarlamak için sayfa başına çalışma yapması gerekir (çekirdek zamanı olarak gösterilir). Bu, mmapyaklaşımda büyük bir maliyettir ve dosya boyutu ile orantılıdır (yani, dosya boyutu büyüdükçe nispeten daha az önemli olmaz) 4 .

Son olarak, böyle bir eşlemeye kullanıcı alanına erişimde bile tam olarak ücretsiz değildir (dosya tabanlı olmayan büyük bellek arabellekleriyle karşılaştırıldığında mmap) - sayfa tabloları ayarlandıktan sonra bile, yeni bir sayfaya her erişim, kavramsal olarak bir TLB özledim. Dan berimmap sayfa önbellek ve onun 4K sayfaları kullanan bir dosya aracı ing, tekrar bir 100GB dosyası için Bu ücreti 25 milyon kez tabi.

Şimdi, bu TLB eksiklerinin gerçek maliyeti, donanımınızın en azından aşağıdaki yönlerine büyük ölçüde bağlıdır: (a) kaç adet 4K TLB'ye sahip olduğunuz ve çeviri önbelleğe alma işleminin geri kalanı nasıl performans gösterir (b) donanımın önceden getirilmesinin ne kadar iyi başa çıktığı TLB ile - ör. prefetch bir sayfa yürüyüşünü tetikleyebilir mi? (c) sayfa yürüyen donanımın ne kadar hızlı ve ne kadar paralel olduğu. Modern üst düzey x86 Intel işlemcilerde, sayfa yürüyüş donanımı genel olarak çok güçlüdür: en az 2 paralel sayfa yürüteç vardır, sürekli yürütme ile aynı anda bir sayfa yürüyüşü oluşabilir ve donanım önceden getirme sayfa yürüyüşünü tetikleyebilir. TLB'nin bir akış üzerindeki etkisi okuma yükü oldukça düşüktür ve bu tür bir yük, sayfa boyutundan bağımsız olarak genellikle benzer şekilde performans gösterir. Ancak diğer donanımlar genellikle çok daha kötüdür!

read () bu tuzaklardan kaçınır

read()Genellikle de buradadır syscall, tip çağrıları C, C ++, örneğin teklif ve diğer diller herkesin iyi farkında olduğunu bir birincil dezavantajı vardır "blok okuma":

  • Her read()N bayt çağrısı N baytını çekirdekten kullanıcı alanına kopyalamalıdır.

Öte yandan, yukarıdaki maliyetlerin çoğunu önler - 25 milyon 4K sayfada kullanıcı alanına eşlemenize gerek yoktur. Genellikle mallockullanıcı alanında tek bir arabellek küçük bir arabellek kullanabilir ve bunu tekrar tekrar tümread aramalarınız kullanabilirsiniz. Çekirdek tarafında, 4K sayfalarda veya TLB eksiklerinde neredeyse hiçbir sorun yoktur, çünkü RAM'in tamamı genellikle birkaç çok büyük sayfa (örneğin, x86'da 1 GB sayfalar) kullanılarak doğrusal olarak eşleştirilir, bu nedenle sayfa önbelleğindeki temel sayfalar kapsanır çekirdek alanında çok verimli.

Temel olarak, büyük bir dosyanın tek bir okuması için hangisinin daha hızlı olduğunu belirlemek için aşağıdaki karşılaştırmaya sahipsiniz:

Sayfa başına fazladan çalışma, mmapyaklaşımın ima ettiği dosya içeriklerinin çekirdekten kullanıcı alanına kopyalanmasıyla yapılan bayt başına çalışmalardan daha maliyetli read()mi?

Birçok sistemde, aslında yaklaşık olarak dengelidirler. Her birinin donanım ve işletim sistemi yığınının tamamen farklı nitelikleriyle ölçeklendiğini unutmayın.

Özellikle, mmapyaklaşım aşağıdaki durumlarda nispeten daha hızlı hale gelir:

  • İşletim sistemi hızlı küçük hata işleme ve özellikle hata etrafı gibi küçük hata toplu optimizasyonlarına sahiptir.
  • İşletim sistemi, MAP_POPULATEörneğin temel sayfaların fiziksel bellekte bitişik olduğu durumlarda büyük haritaları verimli bir şekilde işleyebilen iyi bir uygulamaya sahiptir.
  • Donanım, büyük TLB'ler, hızlı ikinci seviye TLB'ler, hızlı ve paralel sayfa yürüteçleri, çeviri ile iyi önceden getirme etkileşimi gibi güçlü sayfa çeviri performansına sahiptir.

... read()yaklaşım şu durumlarda nispeten daha hızlı olur:

  • Sistem read()çağrısı iyi kopyalama performansına sahiptir. Örneğin, copy_to_userçekirdek tarafında iyi performans.
  • Çekirdeğin belleği eşleştirmek için etkili bir (kullanıcı alanına göre) yolu vardır, örneğin donanım desteğine sahip yalnızca birkaç büyük sayfa kullanarak.
  • Çekirdeğin hızlı sistem çağrıları ve çekirdek TLB girişlerini sistem çağrıları arasında tutmanın bir yolu vardır.

Yukarıdaki donanım faktörleri , aynı aile içinde (örneğin, x86 nesiller ve özellikle pazar segmentleri içinde) ve kesinlikle mimariler arasında (örn., ARM vs x86 vs PPC) bile farklı platformlarda çılgınca değişir .

OS faktörleri de değişmeye devam ediyor, her iki tarafta çeşitli iyileştirmeler bir yaklaşım veya diğeri için göreceli hızda büyük bir sıçramaya neden oluyor. Yeni bir liste şunları içerir:

  • Arızaya eklenmesi, yukarıda açıklanan, gerçekten mmapolmadan da yardımcı olur MAP_POPULATE.
  • Hızlı yol copy_to_useryöntemlerinin eklenmesi arch/x86/lib/copy_user_64.S, örneğin, REP MOVQhızlı olduğunda kullanma , bu da gerçekten yardımcı olur read().

Spectre ve Meltdown'dan sonra güncelleme

Spectre ve Meltdown güvenlik açıklarının hafifletilmesi bir sistem çağrısının maliyetini önemli ölçüde artırdı. Ölçtüğüm sistemlerde, "hiçbir şey yapma" sistem çağrısının maliyeti (bu, çağrı tarafından yapılan herhangi bir gerçek işin dışında, sistem çağrısının saf ek yükünün bir tahmini), tipik olarak 100 ns'den gitti. yaklaşık 700 ns modern Linux sistemi. Ayrıca, sisteminize bağlı olarak, özellikle Meltdown için yapılan sayfa tablosu yalıtım düzeltmesi, TLB girişlerini yeniden yükleme gereği nedeniyle doğrudan sistem çağrı maliyeti dışında ek aşağı yönde etkilere neden olabilir.

Tüm bunlar, read()temel yöntemlerle karşılaştırıldığında temel yöntemler için göreceli bir dezavantajdır mmap, çünkü read()yöntemler her "tampon boyutu" değeri için bir sistem çağrısı yapmalıdır. L1 boyutunu aştığınız ve bu nedenle sürekli olarak önbellek özledikleri için büyük arabelleklerin kullanılması genellikle daha kötü performans gösterdiğinden, bu maliyeti amorti etmek için arabellek boyutunu keyfi olarak artıramazsınız.

Öte yandan, ile mmap, MAP_POPULATEtek bir sistem çağrısı pahasına geniş bir bellek bölgesinde haritaya ve ona verimli bir şekilde erişebilirsiniz.


1 Bu az ya da çok, dosyanın başlaması için tam olarak önbelleğe alınmadığı, ancak işletim sisteminin önceden okunmasını sağlayacak kadar iyi olduğu durumu da içerir (yani, sayfa genellikle istiyor). Bununla birlikte, bu, ince bir konudur, çünkü ileri okuma çalışma şekilleri mmapve readçağrılar arasında genellikle oldukça farklıdır ve 2'de açıklandığı gibi "tavsiye" çağrıları ile daha da ayarlanabilir .

2 ... dosya halinde çünkü değil önbelleğe, davranışların tamamen erişim deseni temel donanım için ne kadar sempatik dahil IO kaygıları, hakim olacak - ve böyle bir erişim sağlanması olmalıdır tüm çaba olarak sempatik gibidir örneğin, madviseveya fadviseçağrıları kullanarak (ve erişim kalıplarını iyileştirmek için ne tür uygulama düzeyi değişiklikleri yaparsanız).

3 Örneğin mmap100 MB gibi daha küçük boyutlu pencerelere sırayla girerek bunun üstesinden gelebilirsiniz .

4 Aslında, MAP_POPULATEyaklaşım, çekirdeğin hatalı kullanıldığından, muhtemelen kullanılmayandan biraz daha hızlı olduğu (en azından bir donanım / işletim sistemi kombinasyonu) olduğu anlaşılmaktadır - bu nedenle gerçek küçük hata sayısı 16 kat azaltılmıştır. ya da öylesine.


4
Bu karmaşık soruna daha nüanslı bir cevap verdiğiniz için teşekkür ederiz. Çoğu insan için mmap'ın daha hızlı olduğu açıktır, gerçekte durum böyle değildir. Deneylerimde, milyonlarca erişimin her biri için bir arabelleği yanlış yerleştirmiş olmama rağmen, bellek içi bir dizine sahip 100 GB'lık büyük bir veritabanına rasgele erişmenin pread () ile daha hızlı olduğu ortaya çıktı. Sanki sektördeki bir sürü insan da aynı şeyi gözlemlemiş gibi görünüyor .
Caetano Sauer

5
Evet, bu senaryoya büyük ölçüde bağlı. Okumalar yeterince küçükse ve zamanla aynı baytları tekrar tekrar okuma eğilimindeyseniz mmap, sabit çekirdek çağrısı yükünü önlediği için aşılmaz bir avantajı olacaktır. Öte yandan, mmapTLB basıncını da arttırır ve baytların geçerli işlemde (hala sayfa sayfasında olmasına rağmen) ilk kez okunduğu "ısınma" aşaması için daha yavaş olmasını sağlar, çünkü readörneğin "hata etrafındaki" bitişik sayfalara göre daha fazla çalışma ... ve aynı uygulamalar için "ısınma" önemlidir! @CaetanoSauer
BeeOnRope

Bence "... ama 25 milyar sayfa hatası hala çok hızlı olmayacak ..." okumalı "... ama 25 milyon sayfa hatası hala çok hızlı olmayacak ..." . % 100 olumlu değilim, bu yüzden doğrudan düzenleme yapmıyorum.
Ton van den Heuvel

7

Üzgünüm Ben Collins sürgülü pencereler mmap kaynak kodunu kaybetti. Boost'da olması güzel olurdu.

Evet, dosyayı eşlemek çok daha hızlı. Belleği-diski ilişkilendirmek için işletim sistemi sanal bellek alt sistemini kullanıyorsunuz. Bunu şu şekilde düşünün: Eğer OS çekirdek geliştiricileri bunu daha hızlı yapabilirlerse. Çünkü bunu yapmak her şeyi daha hızlı hale getirir: veritabanları, önyükleme süreleri, program yükleme süreleri, vb.

Kayan pencere yaklaşımı gerçekten çok zor değil, çünkü birden fazla koşullu sayfa aynı anda eşlenebilir. Dolayısıyla, herhangi bir kaydın en büyüğü belleğe sığacağı sürece kaydın boyutu önemli değildir. Önemli olan defter tutmayı yönetmektir.

Bir kayıt getpagesize () sınırında başlamazsa, eşlemenizin bir önceki sayfada başlaması gerekir. Eşlenen bölgenin uzunluğu, kaydın ilk baytından (gerekirse getpagesize () en yakın katına yuvarlanır) kaydın son baytına (getpagesize () en yakın katına yuvarlanır) kadar uzanır. Bir kaydı işlemeyi bitirdiğinizde, haritanın eşlemesini kaldırabilir () ve bir sonrakine geçebilirsiniz.

Tüm bu Windows altında da System_INFO.dwPageSize değil SYSTEM_INFO.dwAllocationGranularity --- almak için CreateFileMapping () ve MapViewOfFile () (ve GetSystemInfo () kullanarak gayet iyi çalışır.


Ben sadece googled ve dwAllocationGranularity hakkında bu küçük snippet'i buldum - dwPageSize kullanıyordum ve her şey kopuyordu. Teşekkürler!
wickedchicken

4

mmap daha hızlı olmalı, ama ne kadar olduğunu bilmiyorum. Kodunuza çok bağlı. Mmap kullanıyorsanız, tüm dosyayı bir kerede mmap etmek en iyisidir, bu da hayatı çok daha kolay hale getirir. Potansiyel bir sorun, dosyanız 4GB'den büyükse (veya pratikte sınır daha düşükse, genellikle 2GB) 64 bit mimariye ihtiyacınız olacaktır. 32 ortam kullanıyorsanız, muhtemelen bunu kullanmak istemezsiniz.

Bunu söyledikten sonra, performansı artırmak için daha iyi bir yol olabilir. Dedin girdi dosyası defalarca taranan alır onunla yapılması daha sonra bir geçişte dışarı okumak ve eğer o potansiyel olarak çok daha hızlı olabilir.


3

Belki de dosyaları önceden işlemelisiniz, bu nedenle her kayıt ayrı bir dosyadadır (veya en azından her dosya mmap özellikli bir boyuttadır).

Ayrıca, bir sonrakine geçmeden önce her kayıt için tüm işlem adımlarını yapabilir misiniz? Belki bu IO yükünün bir kısmını önler?


3

Mmap'd dosya G / Ç'nin daha hızlı olacağına katılıyorum, ancak kodu kıyaslarken karşı örnek biraz optimize edilmemeli mi?

Ben Collins şunu yazdı:

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

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

Ayrıca denemenizi öneririz:

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

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

Bunun ötesinde, 0x1000'in makinenizdeki bir sanal bellek sayfasının boyutu olmaması durumunda arabellek boyutunu bir sanal bellek sayfasıyla aynı boyutta yapmayı deneyebilirsiniz ... IMHO mmap'd dosyası G / Ç hala kazanır, ama bu işleri daha da yakınlaştırmalıdır.


2

Aklımda, mmap () "just" kullanmak, geliştiricinin kendi önbellek kodunu yazmak zorunda kalmasını engeller. Basit bir "eactly kez okumak" durumda, bu zor olmayacak (her ne kadar mlbrock hala bellek kopyasını işlem alanına kaydettiğinizi gösterir), ancak dosyada ileri geri gidiyorsanız veya Uçları atlamak vb., çekirdek geliştiricilerinin muhtemelen önbelleğe alma işlemini yapabileceğimden daha iyi bir iş yaptığını düşünüyorum ...


1
Büyük olasılıkla, uygulamaya özel verilerinizi önbelleğe alma işini, sayfa boyutunda parçalar üzerinde çok kör bir şekilde çalışan çekirdeğe göre daha iyi yapabilirsiniz (örneğin, hangi sayfaların tahliye edileceğine karar vermek için yalnızca basit bir sözde-LRU şeması kullanır ) - doğru önbellek ayrıntı düzeyi hakkında çok şey biliyor ve gelecekteki erişim kalıpları hakkında iyi bir fikriniz olabilir. mmapÖnbelleğe almanın gerçek yararı , zaten orada olacak olan mevcut sayfa önbelleğini yeniden kullanmanızdır , böylece bu belleği ücretsiz olarak alırsınız ve işlemler arasında da paylaşılabilir.
BeeOnRope

2

Yıllar önce bir ağaç yapısı içeren büyük bir dosyayı hafızaya eşlediğimi hatırlıyorum. Ağaç düğümleri tahsis etmek ve işaretçiler ayarlamak gibi bellekte çok fazla iş içeren normal serileştirmeye kıyasla hız beni şaşırttı. Aslında mmap'e (veya Windows'daki karşılığı) tek bir çağrıyı operatörün yeni ve yapıcı çağrılarına yönelik birçok (MANY) çağrıyla karşılaştırıyordum. Bu tür bir görev için, mmap serileştirme işlemine kıyasla rakipsizdir. Tabii ki bunun için yeniden yerleştirilebilir işaretçiyi güçlendirir.


Bu daha çok bir felaket tarifi gibi geliyor. Nesne düzeni değişirse ne yaparsınız? Sanal işlevleriniz varsa, tüm vftbl işaretçileri muhtemelen yanlış olacaktır. Dosyanın nereye eşlendiğini nasıl kontrol edersiniz? Bir adres verebilirsiniz, ancak bu sadece bir ipucu ve çekirdek başka bir temel adres seçebilir.
Jens

Bu, kararlı ve açıkça tanımlanmış bir ağaç düzenine sahip olduğunuzda mükemmel çalışır. Daha sonra her şeyi ilgili yapılarınıza yayınlayabilir ve her seferinde bir "mmap başlangıç ​​adresi" dengesi ekleyerek dahili dosya işaretlerini takip edebilirsiniz. Bu, inode ve dizin ağaçları kullanan dosya sistemlerine çok benzer
Mike76

1

Bu, çoklu iş parçacığı için iyi bir kullanım durumu gibi görünüyor ... Bir iş parçacığını, verileri işlerken okumak için oldukça kolay bir şekilde ayarlayabileceğinizi düşünüyorum. Bu, algılanan performansı önemli ölçüde artırmanın bir yolu olabilir. Sadece bir düşünce.


Evet. Bunu düşünüyorum ve muhtemelen daha sonraki bir sürümde deneyeceğim. Sahip olduğum tek rezervasyon, işlemin G / Ç gecikmesinden çok daha kısa olması, bu yüzden çok fazla fayda sağlamayabilir.
jbl

1

Ben mmap hakkında en büyük şey ile asenkron okuma potansiyeli olduğunu düşünüyorum:

    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);

Sorun şu ki bu bellek en kısa sürede dosyadan senkronize edilmesi gerektiğini bir ipucu vermek için doğru MAP_FLAGS bulamıyorum. MAP_POPULATE, mmap için doğru ipucu verdiğini umuyoruz (yani çağrıdan dönmeden önce tüm içeriği yüklemeye çalışmaz, ancak bunu feed_data ile zaman uyumsuz olarak yapar). En azından el ile 2.6.23'ten bu yana MAP_PRIVATE olmadan hiçbir şey yapmadığını belirtse bile bu bayrakla daha iyi sonuçlar verir.


2
Sen istemek posix_madviseileWILLNEED önceden doldurmak için tembel ipuçları için bayrak.
ShadowRanger

@ShadowRanger, makul görünüyor. Ben posix_madviseasync çağrı olduğunu açıkça belirtmek için adam sayfasını güncellemek rağmen . Ayrıca, mlocktüm bellek bölgesi sayfa hataları olmadan kullanılabilir hale gelinceye kadar beklemek isteyenler için referans olması iyi olur.
ony
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.