mmap () проти блоків читання


185

Я працюю над програмою, яка буде обробляти файли, які потенційно можуть бути розміром 100 Гб або більше. Файли містять набори записів змінної довжини. У мене з'явилася перша реалізація та запущена, і зараз я прагну до підвищення продуктивності, особливо це стосується ефективнішого введення / виводу, оскільки вхідний файл багато разів сканується.

Чи існує правило роботи із використанням mmap()читання в блоках через fstreamбібліотеку C ++ ? Що я хотів би зробити, це читати великі блоки з диска в буфер, обробляти повні записи з буфера, а потім читати більше.

mmap()Код потенційно може отримати дуже брудний , оскільки mmap«г блоки повинні лежать на сторінці розміру кордону (моє розуміння) і записи потенційно як через кордони сторінки. За допомогою fstreams я можу просто прагнути до початку запису та почати читати знову, оскільки ми не обмежуємось блоками читання, що лежать на межах розміру сторінки.

Як я можу вирішити між цими двома варіантами, не фактично спочатку записуючи повну реалізацію? Будь-які правила (наприклад, mmap()2 рази швидше) чи прості тести?


1
Це цікаве прочитання: medium.com/@sasha_f/… В експериментах mmap()це в 2-6 разів швидше, ніж використання системних дзвінків, наприклад read().
mplattner

Відповіді:


208

Я намагався знайти остаточне слово в продуктивності mmap / read в Linux, і мені трапилось приємне повідомлення ( посилання ) у списку розсилки ядра Linux. Це з 2000 року, так що було багато покращень в IO і віртуальної пам'яті в ядрі з тих пір, але це прекрасно пояснює причину mmapабо readможе бути швидше або повільніше.

  • Заклик на те, що mmapмає більше накладних витрат read(так само, як epollі більше накладних витрат, ніж pollбільше, ніж накладних read). Зміна відображень віртуальної пам'яті - досить дорога операція на деяких процесорах з тих же причин, що перемикання між різними процесами дорого.
  • Система вводу-виводу вже може використовувати кеш диска, тому якщо ви прочитаєте файл, ви потрапите в кеш або пропустите його незалежно від того, яким методом ви користуєтесь.

Однак,

  • Карти пам'яті, як правило, швидші для випадкового доступу, особливо якщо ваші шаблони доступу рідкі і непередбачувані.
  • Карти пам'яті дозволяють вам продовжувати користуватися сторінками з кешу, поки не закінчите. Це означає, що якщо ви активно використовуєте файл протягом тривалого періоду часу, а потім закрийте його і повторно відкрийте, сторінки все одно будуть кешовані. З read, ваш файл, можливо, був видалений з кешу століття тому. Це не застосовується, якщо ви використовуєте файл і негайно його відкидаєте. (Якщо ви намагаєтесь mlockсторінки просто зберегти їх у кеші, ви намагаєтесь перехитрити кеш диска, і такий вид дуріння рідко допомагає продуктивності системи).
  • Читання файлу безпосередньо дуже просто та швидко.

Обговорення mmap / read нагадує мені ще два обговорення продуктивності:

  • Деякі програмісти Java були шоковані, коли виявили, що розблокування вводу-виводу часто повільніше, ніж блокування вводу-виводу, що мало ідеальний сенс, якщо ви знаєте, що для неблокування вводу-виводу потрібно робити більше системних викликів.

  • Деякі інші мережеві програмісти були шоковані тим, що дізнаються, що epollце часто повільніше poll, що має ідеальний сенс, якщо ви знаєте, що управління epollвимагає робити більше системних викликів.

Висновок: використовуйте карти пам’яті, якщо ви отримуєте доступ до даних випадковим чином, зберігайте їх довгий час або якщо ви знаєте, що можете ділитися ними з іншими процесами ( MAP_SHAREDне дуже цікаво, якщо фактичного обміну немає). Як правило, читайте файли, якщо ви отримуєте доступ до даних послідовно або відкидаєте їх після читання. І якщо будь-який метод робить вашу програму менш складною, зробіть це . У багатьох випадках реального світу немає впевненого способу показати його швидше, не тестуючи фактичну заявку, а НЕ еталоном.

(Вибачте за те, що не вирішував це запитання, але я шукав відповідь, і це запитання постійно з’являлося у верхній частині результатів Google.)


Майте на увазі, що використання будь-яких порад, заснованих на апаратному та програмному забезпеченні 2000-х років, не тестуючи їх сьогодні, було б дуже підозрілим підходом. Крім того, хоча багато фактів про mmapvs read()у цій темі все ще вірні, як це було в минулому, загальну продуктивність насправді не можна визначити, додаючи плюси і мінуси, а лише тестуючи певну конфігурацію обладнання. Наприклад, дискусійним є те, що "Заклик до mmap має більше накладних витрат, ніж читання" - так mmap, потрібно додати відображення до таблиці сторінок процесу, але readмає скопіювати всі прочитані байти з ядра в простір користувача.
BeeOnRope

Підсумок полягає в тому, що на моєму (сучасний Intel, близько 2018 року) апаратному забезпеченні mmapє нижчі накладні витрати, ніж readу зчитування з більшим розміром сторінки (4 KiB). Зараз дуже вірно, що якщо ви хочете отримати доступ до даних рідко і випадковим чином, mmapце дійсно дуже добре - але навпаки це не обов'язково: правда, mmapвсе одно може бути найкращим і для послідовного доступу.
BeeOnRope

1
@BeeOnRope: Можливо, ви скептично ставитесь до порад, заснованих на апаратному та програмному забезпеченні 2000-х років, але я ще більше скептично ставляться до орієнтирів, які не містять методології та даних. Якщо ви хочете зробити mmapшвидший випадок , я би сподівався побачити як мінімум весь тестовий апарат (вихідний код) з табличними результатами та номером моделі процесора.
Дітріх Епп

@BeeOnRope: Також майте на увазі, що при тестуванні бітів системи пам'яті, як це, мікроблоки можуть бути вкрай оманливими, оскільки помилка TLB може негативно вплинути на продуктивність решти вашої програми, і цей вплив не з’явиться, якщо ви вимірюєте лише саму mmap.
Дітріх Епп

2
@DietrichEpp - так, я добре розбираюся в ефектах TLB. Зверніть увагу, що mmapTLB не змиває, за винятком незвичних обставин (але munmapможе). Мої тести включали як мікро-показники (у тому числі munmap), так і «в застосуванні», що працюють у реальному випадку використання. Звичайно, моя заява не збігається з вашою заявкою, тому люди повинні перевірятись на місцевому рівні. Навіть не ясно, що mmapсприяє мікро-орієнтир: read()також отримує великий імпульс, оскільки буфер призначення на стороні користувача, як правило, залишається в L1, що може не статися в більшій програмі. Так що так, "це складно".
BeeOnRope

47

Основна вартість продуктивності буде дисковим введенням. "mmap ()", безумовно, швидше, ніж istream, але різниця може бути не помітна, тому що введення / виведення диска буде домінувати у вашому виконанні.

Я спробував фрагмент коду Бена Коллінза (див. Вище / нижче), щоб перевірити його твердження, що "mmap () є набагато швидшим" ​​і не знайшов різної різниці. Дивіться мої коментарі до його відповіді.

Я, звичайно, не рекомендував би поодиноко картати кожен запис по черзі, якщо ваші "записи" не є величезними - це було б жахливо повільно, вимагаючи 2 системних дзвінків на кожен запис і, можливо, втрачайте сторінку з кеш-пам'яті диска .... .

У вашому випадку я думаю, що mmap (), istream та низькі рівні відкритих () / read () дзвінків будуть приблизно однакові. Я рекомендую mmap () у таких випадках:

  1. У файлі є випадковий доступ (а не послідовний) AND
  2. все це зручно вписується в пам'ять АБО є файл-орієнтир у файлі, так що певні сторінки можна відображати на інші та відображати інші сторінки. Таким чином операційна система використовує доступну оперативну пам’ять з максимальною користю.
  3. АБО якщо кілька процесів читають / працюють на одному файлі, то mmap () є фантастичним, оскільки всі процеси мають однакові фізичні сторінки.

(btw - я люблю mmap () / MapViewOfFile ()).


Хороший момент щодо випадкового доступу: це може бути однією з речей, що рухають моє сприйняття.
Бен Коллінз

1
Я б не сказав, що файл повинен зручно вписуватися в пам'ять, лише в адресний простір. Тож у 64-бітових системах не повинно бути причин не відображати величезні файли. ОС знає, як з цим впоратися; це та сама логіка, що використовується для заміни, але в цьому випадку не потрібно додаткового місця для заміни на диску.
MvG

@MvG: Ви розумієте, що стосується вводу / виводу диска? Якщо файл вписується в адресний простір, але не в пам'ять, і у вас є випадковий доступ, то у вас може бути доступ до кожного запису, який вимагає переміщення і пошуку головки диска, або операції на сторінці SSD, що буде катастрофою для продуктивності.
Тім Купер

3
Аспект вводу / виводу диска повинен бути незалежним від методу доступу. Якщо у вас справді випадковий доступ до файлів, що перевищують RAM, і mmap, і search + read суворо пов'язані з диском. В іншому випадку обидва виграють кеші. Я не бачу розмір файлу порівняно з розміром пам'яті як вагомий аргумент в будь-якому напрямку. З іншого боку, розмір файлу та адресний простір - дуже сильний аргумент, особливо для справді випадкового доступу.
MvG

У моїй оригінальній відповіді був такий пункт: "вся справа зручно вписується в пам'ять АБО у файлі є місцепосилання". Отже, другий пункт стосується того, що ви говорите.
Тім Купер

43

ММАП це спосіб швидше. Ви можете написати простий орієнтир, щоб довести це:

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

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

проти:

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

Ясно, що я залишаю деталі (наприклад, як визначити, коли ви досягнете кінця файлу у випадку, якщо, наприклад, ваш файл не кратний page_size), але він дійсно не повинен бути набагато складнішим за це .

Якщо ви можете, ви можете спробувати розбити свої дані на кілька файлів, які можна mmap () редагувати в цілому, а не частково (набагато простіше).

Пару місяців тому у мене була напівзапечена реалізація mmap з розсувним вікном () - клас потоку для boost_iostreams, але ніхто не хвилювався, і я зайнявся іншими речами. На жаль, я видалив архів старих незавершених проектів кілька тижнів тому, і це було однією з жертв :-(

Оновлення : Я також повинен додати застереження, що цей орієнтир виглядав би зовсім інакше в Windows, оскільки Microsoft реалізував чудовий кеш файлів, який робить більшість того, що ви робите з mmap в першу чергу. Тобто для файлів, які часто отримують доступ, ви можете просто зробити std :: ifstream.read (), і це буде так само швидко, як mmap, тому що кеш файлів уже зробив би для вас карту пам'яті, і він прозорий.

Підсумкове оновлення : Подивіться, люди: у багатьох різних платформних комбінаціях ОС та стандартних бібліотек, ієрархій дисків та пам'яті, я не можу сказати напевно, що системний виклик mmap, який розглядається як чорний ящик, завжди завжди буде суттєво швидшим ніж read. Це був не зовсім мій намір, навіть якщо мої слова можна було б так трактувати. Зрештою, моя думка полягала в тому, що введення / виведення з картою пам'яті, як правило, швидше, ніж введення / виведення на байті; це все-таки вірно . Якщо ви експериментально встановите, що різниці між ними немає, то єдиним поясненням, яке мені здається розумним, є те, що ваша платформа реалізує відображення пам’яті під обкладинками таким чином, що є вигідним для виконання дзвінків доread. Єдиний спосіб бути абсолютно впевненим у тому, що ви використовуєте вбудований введення / виведення переносним способом - це використовувати mmap. Якщо ви не переймаєтесь портативністю, і ви можете розраховувати на конкретні характеристики вашої цільової платформи, то використання readможе бути доречним, не пошкоджуючи при цьому вимірної якості.

Редагувати, щоб очистити список відповідей: @jbl:

mmap розсувного вікна звучить цікаво. Ви можете сказати трохи більше про це?

Звичайно - я писав бібліотеку C ++ для Git (якщо ви хочете), і я зіткнувся з подібною проблемою: мені потрібно було вміти відкривати великі (дуже великі) файли і не мати продуктивності. (як би було з std::fstream).

Boost::Iostreamsвже має mapped_file Джерело, але проблема полягала в тому, що він писав mmapцілі файли, що обмежує вас 2 ^ (розмір слів). На 32-бітних машинах 4 ГБ недостатньо великі. Нерозумно розраховувати, що .packв Git файли стануть набагато більшими за них, тому мені потрібно було читати файл шматками, не вдаючись до звичайного вводу-виводу файлів. Під обкладинками Boost::Iostreamsя реалізував Джерело, яке представляє більш-менш інший погляд на взаємодію між std::streambufта std::istream. Можна також спробувати аналогічний підхід, тільки наслідуючи std::filebufв mapped_filebufі так само, наслідуючи std::fstreamв a mapped_fstream. Це взаємодія між цими двома складно виправити. Boost::Iostreams дещо виконано для вас, і воно також забезпечує гачки для фільтрів і ланцюгів, тому я подумав, що було б корисніше здійснити це таким чином.


3
RE: mmaped кеш файлів у Windows. Саме так: коли включена буферизація файлів, пам’ять ядра відображає файл, який ви читаєте внутрішньо, зчитується в цей буфер і копіює його назад у ваш процес. Це так, як якщо б ви пам'яті відобразили його самостійно, за винятком додаткового кроку копіювання.
Кріс Сміт

6
Я не згоден з прийнятою відповіддю, але вважаю, що ця відповідь є неправильною. Я дотримувався вашої пропозиції і спробував ваш код на 64-бітній машині Linux, і mmap () не швидше, ніж реалізація STL. Крім того, теоретично я б не очікував, що "mmap ()" буде швидшим (або повільнішим).
Тім Купер

3
@Tim Cooper: ви можете знайти цю цікаву тему ( markmail.org/message/… ). Зверніть увагу на дві речі: mmap не належним чином оптимізовано в Linux, а також потрібно використовувати madvise в своєму тесті, щоб отримати найкращі результати.
Бен Коллінз

9
Шановний Бен: Я прочитав це посилання. Якщо 'mmap ()' не швидше в Linux, а MapViewOfFile () не швидше в Windows, то чи можете ви стверджувати, що "mmap - це швидше"? Крім того, з теоретичних причин я вважаю, що mmap () не є швидшим для послідовного читання - чи є у вас пояснення протилежного?
Тім Купер

11
Бен, навіщо займатись тим mmap()самим файлом на сторінці? Якщо a size_tдостатньо місткий для розміру файлу (дуже ймовірно, для 64-бітних систем), то просто mmap()весь файл за один виклик.
Стів Еммерсон

39

Тут вже є багато хороших відповідей, які охоплюють багато важливих моментів, тому я просто додам пару питань, яких я не бачив безпосередньо вгорі. Тобто ця відповідь не повинна вважатися вичерпною плюсами і мінусами, а скоріше доповненням до інших відповідей.

mmap здається магією

Прийняття випадку, коли файл уже повністю кешований 1 як базовий рівень 2 , mmapможе здатися схожим на магію :

  1. mmap потрібно лише 1 системний виклик для (потенційно) відображення всього файлу, після чого більше системних викликів не потрібно.
  2. mmap не вимагає копіювання даних файлу з ядра в простір користувача.
  3. mmapдозволяє отримати доступ до файлу "як пам'ять", включаючи обробку його будь-якими сучасними підказками, які ви можете зробити проти пам'яті, такими як автоматична векторизація компілятора, внутрішні символи SIMD , попереднє завантаження, оптимізовані процедури розбору пам'яті, OpenMP тощо.

У випадку, якщо файл уже знаходиться в кеші, перемогти його здається неможливо: ви просто отримуєте доступ до кешу сторінки ядра як пам'ять, і він не може отримати швидше, ніж це.

Ну, може.

mmap насправді не магія, тому що ...

mmap як і раніше працює на сторінці

Основна прихована вартість mmapvs read(2)(яка справді є порівнянною системою виклику на ОС для блоків читання ) полягає в тому, що mmapвам потрібно буде виконати "деяку роботу" для кожної сторінки 4K в просторі користувача, навіть якщо вона може бути прихована механізм помилок сторінки.

Наприклад, типова реалізація, для якої лише mmapвесь файл потребує помилок, тому 100 ГБ / 4 К = 25 мільйонів помилок для зчитування файлу в 100 ГБ. Тепер це будуть незначні помилки , але 25 мільярдів помилок на сторінці все ще не будуть надшвидкими. Вартість незначної помилки, мабуть, у 100-х нано-кращому випадку.

mmap значною мірою покладається на продуктивність TLB

Тепер ви можете перейти MAP_POPULATEдо того, mmapщоб сказати йому, щоб створити всі таблиці сторінок перед поверненням, тому під час доступу до нього не повинно бути помилок сторінки. Тепер у цьому є маленька проблема, що він також зчитує весь файл в оперативній пам’яті, який вибухне, якщо ви спробуєте зіставити файл 100 Гб, але давайте ігноруємо це поки що 3 . Ядро потрібно виконати роботу на одній сторінці, щоб створити ці таблиці сторінок (відображається як час ядра). Це в кінцевому підсумку є основною вартістю mmapпідходу, і вона пропорційна розміру файлу (тобто він не стає відносно менш важливим у міру збільшення розміру файлу) 4 .

Нарешті, навіть у користувальницькому просторі доступ до такого відображення не зовсім вільний (порівняно з великими буферами пам’яті, що не походять з файлових файлів mmap) - навіть після створення таблиць сторінок кожен доступ до нової сторінки збирається, концептуально, нести пропусник TLB. Оскільки mmapфайл файлу означає використання кешу сторінки та його 4K-сторінок, ви знову зазнаєте цю вартість в 25 мільйонів разів за 100 Гб файл.

Тепер фактична вартість цих пропусків TLB сильно залежить, принаймні, від наступних аспектів вашого обладнання: (a) скільки у вас є 4K TLB enties та як працює решта кешування трансляції (b) наскільки добре працює попередній вибір програмного забезпечення за допомогою TLB - наприклад, чи може попередній вибір викликати прогулянку сторінки? (c) як швидко і наскільки паралельно апаратне обладнання для ходьби сторінок. У сучасних процесорах високого класу x86 Intel апаратне забезпечення ходіння сторінок в цілому дуже сильне: є щонайменше 2 паралельних ходунки сторінок, ходіння сторінок може відбуватися одночасно з продовженням виконання, а апаратне попереднє завантаження може викликати прогулянку сторінки. Таким чином, вплив TLB на завантаження зчитування потокового потоку є досить низьким - і таке завантаження часто буде виконуватись аналогічно незалежно від розміру сторінки. Інше обладнання, як правило, набагато гірше!

read () уникає цих підводних каменів

read()Системний виклик, який є те , що зазвичай лежить в основі «блок читання» виклики типу пропонуються , наприклад, в C, C ++ і інші мови мають один основний недолік , що все добре знає:

  • Кожен read()виклик N байтів повинен копіювати N байтів з ядра в користувальний простір.

З іншого боку, це дозволяє уникнути більшості вищезазначених витрат - не потрібно розміщувати 25 мільйонів 4K сторінок у просторі користувача. Зазвичай ви можете mallocвикористовувати один невеликий буфер в просторі користувача та повторно використовувати його для всіх своїх readдзвінків. З боку ядра майже не виникає проблем зі сторінками 4K або TLB не вистачає, оскільки вся ОЗУ зазвичай лінійно відображається за допомогою декількох дуже великих сторінок (наприклад, 1 Гб сторінок на x86), тому базові сторінки в кеші сторінок покриті дуже ефективно в просторі ядра.

Таким чином, ви маєте таке порівняння, щоб визначити, що швидше для одного читання великого файлу:

Хіба додаткова робота на сторінці, що має на увазі mmapпідхід, дорожче, ніж робота за байтом копіювання вмісту файлу з ядра в користувальний простір, мається на увазі за допомогою використання read()?

У багатьох системах вони фактично приблизно збалансовані. Зауважте, що кожен масштабує з абсолютно різними атрибутами обладнання та стека ОС.

Зокрема, mmapпідхід стає відносно швидшим, коли:

  • Операційна система має швидку обробку з незначними помилками і особливо оптимізацію об'ємних помилок, таких як неполадки.
  • В ОС є хороша MAP_POPULATEреалізація, яка дозволяє ефективно обробляти великі карти у випадках, коли, наприклад, базові сторінки є суміжними у фізичній пам'яті.
  • Обладнання має високу ефективність перекладу сторінок, такі як великі TLB, швидкі TLB другого рівня, швидкі та паралельні прогулянки сторінок, хороша взаємодія попереднього вибору з перекладом тощо.

... в той час як read()підхід стає відносно швидшим, коли:

  • Система read()виклику має хороші показники копіювання. Наприклад, хороші copy_to_userпоказники на стороні ядра.
  • Ядро має ефективний (по відношенню до країни користувача) спосіб відображення пам'яті, наприклад, використовуючи лише кілька великих сторінок з апаратною підтримкою.
  • Ядро має швидкі системні виклики та спосіб зберігати записи TLB ядра навколо всіх системних дзвінків.

Вищевказані апаратні фактори різко різняться на різних платформах, навіть у межах одного сімейства (наприклад, в межах поколінь x86 і особливо на сегментах ринку) і, безумовно, в різних архітектурах (наприклад, ARM vs x86 проти PPC).

Фактори ОС також змінюються, тому що різні вдосконалення з обох сторін викликають великий стрибок відносної швидкості для одного або іншого підходу. Останній список включає:

  • Додавання виправдання, описане вище, що справді допомагає mmapбез випадків MAP_POPULATE.
  • Додавання copy_to_userметодів швидкого шляху arch/x86/lib/copy_user_64.S, наприклад, використання, REP MOVQколи він швидкий, що справді допомагає цьому read()випадку.

Оновлення після Spectre та Meltdown

Пом'якшення вразливості Spectre та Meltdown значно підвищило вартість системного дзвінка. У системах, які я виміряв, вартість системного дзвінка "нічого не робити" (що є оцінкою чистого накладного витрат системного виклику, крім будь-якої фактичної роботи, виконаної викликом) склала приблизно від 100 нс на типовий сучасна система Linux приблизно до 700 нс. Крім того, залежно від вашої системи, виправлення ізоляції таблиці сторінок, спеціально для Meltdown, може мати додаткові ефекти нижче за потоком, окрім вартості прямого системного виклику через необхідність перезавантаження записів TLB.

Все це є відносним недоліком для read()методів, що базуються на основі, порівняно з mmapметодами на основі, оскільки read()методи повинні робити один системний виклик для кожного вартості даних "розміром буфера". Ви не можете довільно збільшити розмір буфера для амортизації цієї вартості, оскільки використання великих буферів зазвичай працює гірше, оскільки ви перевищуєте розмір L1 і, отже, постійно зазнаєте пропусків кешу.

З іншого боку, за допомогою mmap, ви можете картографувати велику область пам'яті MAP_POPULATEта ефективно отримувати доступ до неї за рахунок лише одного системного дзвінка.


1 Це більш-менш також включає випадок, коли файл не був повністю кешований для початку, але де заздалегідь читання ОС досить добре, щоб воно виглядало таким чином (тобто, сторінка зазвичай кешується до того часу, коли ви хочу це). Це тонка проблема, однак тому, що спосіб читання вперед читач часто сильно відрізняється між дзвінками mmapта readдзвінками, і його можна додатково коригувати за допомогою дзвінків "консультувати", як описано в 2 .

2 ... тому що, якщо файл не є кешованим, у вашій поведінці будуть повністю переважати проблеми IO, включаючи, наскільки симпатична ваша схема доступу до базового обладнання - і всі ваші зусилля повинні бути в тому, щоб такий доступ був настільки симпатичним, як можливо, наприклад, за допомогою використання madviseабо fadviseдзвінків (і будь-які зміни рівня програми, які ви можете зробити для покращення моделей доступу).

3 Ви могли б обійти це, наприклад, послідовно mmapвшиваючись у вікна меншого розміру, скажімо, 100 Мб.

4 Насправді, виявляється, що MAP_POPULATEпідхід (принаймні одна комбінація апаратних засобів / ОС) лише трохи швидший, ніж його не використовувати, можливо, тому, що ядро ​​використовує помилку - тому фактична кількість незначних несправностей зменшується в 16 разів. або так.


4
Дякуємо, що надали більш тонку відповідь на це складне питання. Для більшості людей здається очевидним, що mmap швидше, коли насправді це часто не так. У моїх експериментах випадковий доступ до великої бази даних 100 ГБ з індексом пам’яті виявився більш швидким за допомогою pread (), навіть не дивлячись на буфер для кожного з мільйонів доступу. І, схоже, низка людей в галузі спостерігала те саме .
Caetano Sauer

5
Так, багато що залежить від сценарію. Якщо ви читаєте досить малі і з часом ви, як правило, неодноразово читаєте одні й ті ж байти, mmapматиме непереборну перевагу, оскільки це дозволяє уникнути фіксованого виклику ядра. З іншого боку, mmapтакож збільшується тиск TLB і фактично роблять повільнішим для фази "прогрівання", коли байти читаються вперше в поточному процесі (хоча вони все ще знаходяться на сторінці сторінки), оскільки це може зробити більше роботи, ніж read, наприклад, для "неполадок" сусідніх сторінок ... і для тих же додатків "розминка" - це все, що має значення! @CaetanoSauer
BeeOnRope

Я думаю, де ви говорите "... але 25 мільярдів помилок сторінки все ще не буде надто швидким ..." слід прочитати "... але 25 мільйонів помилок сторінки все ще не буде надшвидким ..." . Я не на 100% позитивний, тому не редагую безпосередньо.
Ton van den Heuvel

7

Вибачте, Бен Коллінз втратив вихідний код з розсувними вікнами mmap. Це було б непогано мати у Boost.

Так, відображення файлу відбувається набагато швидше. Ви по суті використовуєте підсистему віртуальної пам'яті ОС для асоціації пам'яті на диск і навпаки. Подумайте про це так: якби розробники ядра ОС могли зробити це швидше, вони би. Тому що це робить майже все швидше: бази даних, час завантаження, час завантаження програми тощо.

Підхід розсувного вікна насправді не такий вже й складний, оскільки можна відобразити відразу декілька невідкладних сторінок. Отже, розмір запису не має значення, поки найбільший з усіх записів поміститься в пам'яті. Важливим є управління бухгалтерією.

Якщо запис не починається на межі getpagesize (), ваше відображення має починатися на попередній сторінці. Довжина відображеної області поширюється від першого байту запису (округлення в разі необхідності до найближчого кратного getpagesize ()) до останнього байту запису (округленого до найближчого кратного getpagesize ()). Закінчивши обробку запису, ви можете скасувати його () та перейти до наступного.

Це все добре працює і в Windows, використовуючи CreateFileMapping () і MapViewOfFile () (і GetSystemInfo (), щоб отримати SYSTEM_INFO.dwAllocationGranularity --- не SYSTEM_INFO.dwPageSize).


Я просто гуглив і знайшов цей маленький фрагмент про dwAllocationGranularity - я використовував dwPageSize, і все ламалося. Дякую!
wickedchicken

4

mmap має бути швидшим, але я не знаю скільки. Це дуже залежить від вашого коду. Якщо ви використовуєте mmap, найкраще відразу скласти весь файл, це полегшить вам життя. Одна з потенційних проблем полягає в тому, що якщо ваш файл перевищує 4 Гб (або на практиці ліміт нижчий, часто 2 Гб), вам знадобиться 64-бітова архітектура. Отже, якщо ви використовуєте середовище 32, ви, ймовірно, не хочете ним користуватися.

Сказавши це, може бути кращий шлях до підвищення продуктивності. Ви сказали, що вхідний файл сканується багато разів , якщо ви зможете прочитати його за один прохід, а потім зробити це з ним, що потенційно може бути набагато швидшим.


3

Можливо, вам слід попередньо обробити файли, тому кожен запис знаходиться в окремому файлі (або принаймні, що кожен файл має розмір mmap).

Ви також можете зробити всі етапи обробки для кожного запису, перш ніж перейти до наступного? Може, це дозволить уникнути деяких накладних витрат?


3

Я згоден , що mmap'd файл I / O буде швидше, але в той час як ваш бенчмаркінг код, повинен не контрприклад бути кілька оптимізовані?

Бен Коллінз писав:

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

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

Я б запропонував також спробувати:

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

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

І крім цього, ви можете також спробувати зробити розмір буфера такого ж розміру, як одна сторінка віртуальної пам'яті, якщо 0x1000 не є розміром однієї сторінки віртуальної пам'яті на вашій машині ... IMHO mmap'd файл вводу / виводу все ще виграє, але це повинно зробити справи ближчими.


2

На мій погляд, використання mmap () "просто" обтяжує розробника від необхідності писати власний код кешування. У простому випадку "прочитати файл відразу один раз" це не буде складно (хоча, як вказує mlbrock, ви все-таки зберігаєте копію пам'яті в процесному просторі), але якщо ви збираєтесь назад і назад у файлі або пропускаючи біти і так далі, я вважаю, що розробники ядра, мабуть, зробили кращу роботу, застосовуючи кешування, ніж я можу ...


1
Швидше за все, ви можете зробити кращу роботу з кешування даних, пов’язаних із додатком, ніж ядро, яке працює на фрагментах сторінок дуже сліпо (наприклад, для вирішення, які сторінки вилучати, використовується лише проста схема псевдо-LRU) ) - хоча ви, можливо, знаєте багато про правильну деталізацію кешування, а також добре розумієте майбутні схеми доступу. Справжня перевага mmapкешування полягає в тому, що ви просто повторно використовуєте наявний кеш сторінок, який вже буде там, тому ви отримуєте цю пам’ять безкоштовно, і її також можна ділитися між процесами.
BeeOnRope

2

Я пам’ятаю, як картографував величезний файл, що містить структуру дерева, у пам’ять років тому. Мене вразила швидкість порівняно із звичайною десеріалізацією, яка передбачає багато роботи в пам'яті, як, наприклад, розподілення деревних вузлів та встановлення покажчиків. Тож насправді я порівнював один виклик mmap (або його аналог у Windows) з багатьма (МНОГО) викликами з новими операторами та викликами конструктора. Для такого роду завдань mmap є неперевершеним порівняно з де-серіалізацією. Зрозуміло, для цього слід вивчити пересувний покажчик.


Це звучить більше як рецепт катастрофи. Що ви робите, якщо розташування об'єкта змінюється? Якщо у вас є віртуальні функції, всі покажчики vftbl, ймовірно, помиляються. Як ви керуєте, де файл відображається? Ви можете надати йому адресу, але це лише натяк, і ядро ​​може вибрати іншу базову адресу.
Єнс

Це прекрасно спрацьовує, коли у вас є стабільна і чітко визначена компонування дерева. Потім ви можете передавати все вашим відповідним структурам і слідувати внутрішнім покажчикам файлів, додаючи зміщення "mmap start address" щоразу. Це дуже схоже на файлові системи, що використовують дерева inodes та дерев каталогів
Mike76,

1

Це здається гарним випадком використання для багатопотокової передачі ... Я б подумав, що ви можете досить легко встановити один потік для читання даних, а інші обробляють його. Це може бути способом різко збільшити сприйняті результати. Просто думка.


Так. Я думав над цим і, ймовірно, спробую це в наступному випуску. Єдине застереження, яке я маю, - обробка набагато коротша, ніж затримка вводу / виводу, тому користь може не бути великою.
jbl

1

Я думаю, що найбільше в mmap - це потенціал для асинхронного читання за допомогою:

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

Проблема полягає в тому, що я не можу знайти правильний MAP_FLAGS, щоб натякнути, що цю пам'ять потрібно синхронізувати з файлу якнайшвидше. Я сподіваюся, що MAP_POPULATE дає правильну підказку для mmap (тобто він не намагатиметься завантажити весь вміст перед поверненням з виклику, але зробить це в async. З feed_data). Принаймні, це дає кращі результати з цим прапором, навіть у тому посібнику, що він не робить нічого без MAP_PRIVATE з 2.6.23.


2
Ви хочете, posix_madviseщобWILLNEED прапор для ледачих натяків наростати.
ShadowRanger

@ShadowRanger, звучить розумно. Хоча я б оновив сторінку man, щоб чітко вказати, що posix_madviseце виклик асинхронізації. Також було б приємно посилатися mlockна тих, хто хоче дочекатися, поки весь регіон пам'яті стане доступним без помилок сторінки.
они
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.