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

185

Я работаю над программой, которая будет обрабатывать файлы, которые могут иметь размер 100 ГБ или более. Файлы содержат наборы записей переменной длины. Я запустил и запустил первую реализацию и теперь смотрю на повышение производительности, особенно на более эффективный ввод-вывод, поскольку входной файл сканируется много раз.

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

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

Как я могу выбрать между этими двумя вариантами, не написав сначала полную реализацию? Практические правила (например, mmap()в 2 раза быстрее) или простые тесты?

JBL
источник
1
Это интересно читать: medium.com/@sasha_f/… В экспериментах mmap()это в 2-6 раз быстрее, чем при использовании системных вызовов, например read().
mplattner

Ответы:

208

Я пытался найти последнее слово о производительности mmap / read в Linux и наткнулся на хороший пост ( ссылку ) в списке рассылки ядра Linux. Это с 2000 года, так что с тех пор было много улучшений ввода-вывода и виртуальной памяти в ядре, но это хорошо объясняет причину, почему mmapили readможет быть быстрее или медленнее.

  • Вызов to mmapимеет больше накладных расходов, чем read(точно так же, как epollимеет больше накладных расходов, чем poll, который имеет больше накладных расходов, чем read). Изменение отображений виртуальной памяти является довольно дорогой операцией на некоторых процессорах по тем же причинам, по которым переключение между различными процессами стоит дорого.
  • Система ввода-вывода уже может использовать дисковый кеш, поэтому, если вы прочитаете файл, вы попадете в кеш или пропустите его независимо от того, какой метод вы используете.

Тем не мение,

  • Карты памяти, как правило, быстрее для произвольного доступа, особенно если ваши шаблоны доступа редки и непредсказуемы.
  • Карты памяти позволяют вам продолжать использовать страницы из кэша, пока вы не закончите. Это означает, что если вы интенсивно используете файл в течение длительного периода времени, затем закрываете его и снова открываете, страницы все равно будут кэшироваться. С read, ваш файл может быть сброшен из века кэша назад. Это не относится, если вы используете файл и сразу же удаляете его. (Если вы пытаетесь использовать mlockстраницы только для того, чтобы сохранить их в кеше, вы пытаетесь перехитрить кеш диска, и этот вид дураков редко помогает производительности системы).
  • Чтение файла напрямую очень просто и быстро.

Обсуждение mmap / read напоминает мне о двух других обсуждениях производительности:

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

  • Некоторые другие сетевые программисты были потрясены, узнав, что epollэто часто медленнее, чем poll, что имеет смысл, если вы знаете, что управление epollтребует больше системных вызовов.

Вывод: используйте карты памяти, если вы обращаетесь к данным случайным образом, сохраняете их в течение длительного времени или если вы знаете, что можете поделиться ими с другими процессами ( MAP_SHAREDне очень интересно, если нет фактического обмена). Читайте файлы нормально, если вы обращаетесь к данным последовательно или отбрасываете их после чтения. И если любой из этих методов делает вашу программу менее сложной, сделайте это . Для многих реальных случаев не существует надежного способа показать, что кто-то работает быстрее, без тестирования вашего реального приложения, а НЕ теста.

(Извините за этот вопрос, но я искал ответ, и этот вопрос продолжал подниматься в верхней части результатов Google.)

Дитрих Эпп
источник
Имейте в виду, что использование любого совета, основанного на аппаратном и программном обеспечении 2000-х годов, без его тестирования сегодня было бы весьма подозрительным подходом. Кроме того, хотя многие из фактов о mmapпротивостоянии read()в этом потоке по-прежнему верны, как и в прошлом, общая производительность не может быть определена путем суммирования плюсов и минусов, а только путем тестирования конкретной аппаратной конфигурации. Например, это спорно , что «Вызов ттар имеет больше накладных расходов , чем чтение» - да mmapдолжен добавить отображения в таблице страниц процесса, но readдолжен копировать все чтения байтов из ядра в пространство пользователя.
BeeOnRope
В результате, на моем (современном Intel, около 2018 года) аппаратном обеспечении mmapменьше служебных данных, чем readпри чтении размером больше страницы (4 КиБ). Теперь очень верно, что если вы хотите получить доступ к данным редко и случайно, mmapэто действительно очень хорошо, но обратное не обязательно верно: mmapможет все же быть лучшим для последовательного доступа.
BeeOnRope
1
@BeeOnRope: Вы можете скептически относиться к советам, основанным на аппаратном и программном обеспечении 2000-х годов, но я еще более скептически отношусь к тестам, которые не предоставляют методологию и данные. Если вы хотите сделать случай mmapболее быстрым, я бы ожидал увидеть как минимум весь аппарат тестирования (исходный код) с табличными результатами и номером модели процессора.
Дитрих Эпп
@BeeOnRope: также имейте в виду, что при тестировании битов системы памяти, подобных этой, микробенчмарки могут быть очень обманчивыми, поскольку сброс TLB может негативно повлиять на производительность остальной части вашей программы, и это влияние не проявится, если Вы только измеряете сам mmap.
Дитрих Эпп
2
@DietrichEpp - да, я хорошо разбираюсь в эффектах TLB. Обратите внимание, что mmapне очищает TLB, за исключением необычных обстоятельств (но munmapможет). Мои тесты включали в себя как микробенчмарки (в том числе munmap), так и «в приложении», работающие в реальных условиях использования. Конечно, мое приложение не совпадает с вашим приложением, поэтому люди должны тестировать локально. Даже неясно, mmapчему способствует микропроцессор: он read()также значительно ускоряется, поскольку целевой буфер на стороне пользователя обычно остается на уровне L1, что может не произойти в больших приложениях. Так что да, "это сложно".
BeeOnRope
47

Основной ценой производительности будет дисковый ввод-вывод. «mmap ()», конечно, быстрее, чем istream, но разница может быть не заметна, потому что дисковый ввод-вывод будет доминировать во время выполнения.

Я попробовал фрагмент кода Бена Коллинза (см. Выше / ниже), чтобы проверить его утверждение, что «mmap () намного быстрее», и не нашел измеримых различий. Смотрите мои комментарии на его ответ.

Я бы, конечно, не рекомендовал по отдельности mmap'ить каждую запись по очереди, если только ваши «записи» не огромны - это будет ужасно медленно, требуя 2 системных вызова для каждой записи и, возможно, потерю страницы из кеша дисковой памяти .... ,

В вашем случае я думаю, что вызовы mmap (), istream и низкоуровневые open () / read () будут примерно одинаковыми. Я бы порекомендовал mmap () в этих случаях:

  1. В файле есть произвольный доступ (не последовательный), И
  2. все это удобно помещается в памяти, ИЛИ в файле есть локальная ссылка, так что определенные страницы могут быть отображены и другие страницы отображены. Таким образом, операционная система использует доступную оперативную память для максимальной выгоды.
  3. ИЛИ если несколько процессов читают / работают с одним и тем же файлом, то mmap () - это просто фантастика, потому что все процессы используют одни и те же физические страницы.

(кстати - я люблю mmap () / MapViewOfFile ()).

Тим Купер
источник
Хорошее замечание о произвольном доступе: это может быть одной из причин моего восприятия.
Бен Коллинз
1
Я бы не сказал, что файл должен удобно вписываться в память, только в адресное пространство. Поэтому в 64-битных системах не должно быть никаких причин не отображать огромные файлы. ОС знает, как с этим справиться; это та же логика, которая используется для подкачки, но в этом случае не требуется дополнительного пространства подкачки на диске.
MvG
@MvG: ты понимаешь смысл дискового ввода / вывода? Если файл помещается в адресное пространство, но не в память, и у вас есть произвольный доступ, у вас может быть каждый доступ к записи, требующий перемещения и поиска головки диска или операции на странице SSD, что может привести к снижению производительности.
Тим Купер
3
Аспект дискового ввода-вывода должен быть независимым от метода доступа. Если у вас есть действительно произвольный доступ к файлам, превышающим объем ОЗУ, то и mmap, и seek + read строго связаны с диском. В противном случае оба извлекут выгоду из кэшей. Я не вижу размер файла по сравнению с объемом памяти в качестве сильного аргумента в любом направлении. Размер файла в сравнении с адресным пространством, с другой стороны, является очень сильным аргументом, особенно для действительно произвольного доступа.
MvG
Мой первоначальный ответ имел и имеет следующий смысл: «все это удобно помещается в памяти, ИЛИ в файле есть локальная ссылка». Таким образом, второй пункт касается того, что вы говорите.
Тим Купер
43

Mmap намного быстрее. Вы можете написать простой тест, чтобы доказать это себе:

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 () - ed потока со скользящим окном для boost_iostreams, но никто не заботился, и я занялся другими вещами. К сожалению, я удалил архив старых незавершенных проектов несколько недель назад, и это была одна из жертв :-(

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

Финальное обновление : посмотрите, люди: во многих различных сочетаниях платформ ОС и стандартных библиотек, дисков и иерархий памяти я не могу с уверенностью сказать, что системный вызов mmap, рассматриваемый как черный ящик, всегда всегда будет значительно быстрее чем read. Это не было моим намерением, даже если мои слова могли быть истолкованы таким образом. В конечном счете, моя точка зрения заключалась в том, что отображаемый в память ввод-вывод, как правило, выполняется быстрее, чем байт-ориентированный ввод-вывод; это все еще правда . Если вы обнаружите экспериментально, что между этими двумя понятиями нет никакой разницы, то единственное объяснение, которое мне кажется разумным, заключается в том, что ваша платформа реализует отображение памяти под прикрытием таким образом, чтобы это было выгодно для производительности обращений кread, Единственный способ быть абсолютно уверенным, что вы используете переносимый ввод-вывод с отображением в памяти, это использовать mmap. Если вас не волнует переносимость, и вы можете полагаться на конкретные характеристики своих целевых платформ, тогда использование readможет подойти, не жертвуя при этом какой-либо производительностью.

Изменить, чтобы очистить список ответов: @jbl:

Скользящее окно Mmap звучит интересно. Можете ли вы сказать немного больше об этом?

Конечно, я писал C ++ библиотеку для Git (libgit ++, если хотите), и столкнулся с подобной проблемой: мне нужно было иметь возможность открывать большие (очень большие) файлы и не иметь производительности, чтобы быть полной собакой (как было бы с std::fstream).

Boost::Iostreamsуже есть источник mapped_file, но проблема заключалась в том, что он проверял mmapфайлы целиком, что ограничивает вас до 2 ^ (wordsize). На 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
@ Тим Купер: вы можете найти эту ветку ( markmail.org/message/… ) интересной. Обратите внимание на две вещи: mmap не оптимизирован должным образом в Linux, и для достижения наилучших результатов нужно также использовать madvise в их тесте.
Бен Коллинз
9
Дорогой Бен, я прочитал эту ссылку. Если mmap () не быстрее в Linux и MapViewOfFile () не быстрее в Windows, то можете ли вы утверждать, что «mmap намного быстрее»? Кроме того, по теоретическим причинам я считаю, что mmap () не работает быстрее при последовательном чтении - есть ли у вас объяснения обратного?
Тим Купер
11
Бен, зачем беспокоиться mmap()о файле страницы за раз? Если a size_tдостаточно вместителен для хранения размера файла (очень вероятно, в 64-разрядных системах), то только mmap()весь файл за один вызов.
Стив Эммерсон
39

Здесь уже есть много хороших ответов, которые охватывают многие существенные моменты, поэтому я просто добавлю пару вопросов, которые я не видел, рассмотренных непосредственно выше. Таким образом, этот ответ не следует считать исчерпывающим из плюсов и минусов, а скорее дополнением к другим ответам здесь.

ММАП кажется волшебством

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

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

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

Ну, это может.

Mmap на самом деле не волшебство, потому что ...

mmap по-прежнему работает на странице

Основная скрытая стоимость mmapvs read(2)(которая на самом деле сопоставима с системным вызовом на уровне ОС для блоков чтения ) заключается в том, что mmapвам придется выполнять «некоторую работу» для каждой страницы 4K в пользовательском пространстве, даже если она может быть скрыта механизм сбоя страницы.

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

mmap сильно зависит от производительности TLB

Теперь вы можете перейти MAP_POPULATEк mmapнему, чтобы настроить все таблицы страниц перед возвратом, чтобы при обращении к нему не было ошибок страниц. Теперь в этом есть небольшая проблема, заключающаяся в том, что он также считывает весь файл в ОЗУ, который взорвется, если вы попытаетесь отобразить файл размером 100 ГБ, но давайте пока проигнорируем это 3 . Ядру необходимо выполнить постраничную работу для настройки этих таблиц страниц (отображается как время ядра). Это приводит к значительным затратам в mmapподходе и пропорционально размеру файла (т. Е. Он не становится относительно менее важным с ростом размера файла) 4 .

Наконец, даже при доступе к пользовательскому пространству такое отображение не является абсолютно бесплатным (по сравнению с большими буферами памяти, не основанными на файлах mmap) - даже после настройки таблиц страниц каждый доступ к новой странице будет концептуально, понести TLB пропустить. Поскольку mmapиспользование файла означает использование кеша страниц и его страниц размером 4 Кбайт, вы снова понесете эту стоимость в 25 миллионов раз за файл объемом 100 ГБ.

Теперь фактическая стоимость этих промахов TLB сильно зависит по крайней мере от следующих аспектов вашего оборудования: (a) сколько 4K TLB у вас есть и как работает остальная часть кэширования перевода (b) насколько хорошо справляется аппаратная предварительная выборка с TLB - например, может ли предварительная выборка вызвать просмотр страницы? (c) насколько быстро и параллельно работает оборудование для перемещения по страницам. На современных высокопроизводительных процессорах Intel 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 по системным вызовам.

Аппаратные факторы выше варьируются дико на различных платформах, даже в пределах одной и той же семье (например, в пределах х86 поколений и особенно рыночных сегментов) и , безусловно , через архитектур (например, ARM против x86 против PPC).

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

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

Обновление после Призрак и Обвал

Снижение уязвимостей Spectre и Meltdown значительно увеличило стоимость системного вызова. В системах, которые я измерил, стоимость системного вызова «ничего не делать» (который является оценкой чистой служебной нагрузки системного вызова, помимо любой фактической работы, выполняемой этим вызовом) выросла с примерно 100 нс на типичном современная система Linux примерно до 700 нс. Кроме того, в зависимости от вашей системы, исправление изоляции таблицы страниц специально для Meltdown может иметь дополнительные нисходящие эффекты помимо прямой стоимости системных вызовов из-за необходимости перезагрузки записей TLB.

Все это является относительным недостатком read()основанных методов по сравнению с mmapоснованными методами, поскольку read()методы должны выполнять один системный вызов для каждого значения «размера буфера» данных. Вы не можете произвольно увеличить размер буфера, чтобы амортизировать эту стоимость, поскольку использование больших буферов обычно работает хуже, так как вы превышаете размер L1 и, следовательно, постоянно испытываете потери в кеше.

С другой стороны, с mmap, вы можете отобразить в большой области памяти MAP_POPULATEи эффективно обращаться к ней, за счет одного системного вызова.


1 Это более или менее относится и к случаю, когда файл не был полностью кэширован для начала, но когда упреждающее чтение ОС достаточно, чтобы оно выглядело так (т.е. страница обычно кэшируется к тому времени, когда вы хочу это). Это небольшая проблема, потому что способ упреждающего чтения часто сильно отличается между вызовами mmapи readвызовами и может быть дополнительно отрегулирован вызовами «посоветовать», как описано в 2 .

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

3 Вы можете обойти это, например, последовательно mmapвводя окна меньшего размера, скажем, 100 МБ.

4 На самом деле, оказывается, что MAP_POPULATEподход (по крайней мере , один некоторые аппаратные / комбинация OS) лишь немного быстрее , чем он не используется, вероятно , потому , что ядро использует faultaround - так что фактическое число мелких дефектов уменьшается в 16 раз или так.

BeeOnRope
источник
4
Спасибо за более подробный ответ на этот сложный вопрос. Для большинства людей очевидно, что mmap работает быстрее, хотя в действительности это часто не так. В моих экспериментах случайный доступ к большой базе данных объемом 100 ГБ с индексом в памяти оказался более быстрым с помощью pread (), даже несмотря на то, что я неправильно назначал буфер для каждого из миллионов обращений. И кажется, что люди в отрасли наблюдали то же самое .
Каэтано Зауэр
5
Да, это зависит от сценария. Если вы читаете достаточно мало и со временем вы склонны многократно читать одни и те же байты, это mmapбудет иметь непреодолимое преимущество, поскольку позволяет избежать фиксированных накладных расходов на вызовы ядра. С другой стороны, mmapтакже увеличивает давление TLB и фактически замедляет фазу «прогрева», когда байты читаются впервые в текущем процессе (хотя они все еще находятся на странице страницы), так как это может сделать больше работы, чем read, например, для «разборки» соседних страниц ... и для тех же приложений «разогрев» - это все, что имеет значение! @CaetanoSauer
BeeOnRope
Я думаю, где вы говорите «... но 25 миллиардов страниц не будут слишком быстрыми ...», это должно читаться как «... но 25 миллионов страниц все равно не будут супер быстрыми ...» , Я не уверен на 100%, поэтому я не редактирую напрямую.
Тон ван ден Хевел
7

Мне жаль, что Бен Коллинз потерял свой исходный код mmap для скользящих окон. Это было бы неплохо иметь в Boost.

Да, отображение файла намного быстрее. По сути, вы используете подсистему виртуальной памяти ОС для связи памяти с диском и наоборот. Подумайте об этом так: если бы разработчики ядра ОС могли сделать это быстрее, они бы это сделали. Потому что это делает все быстрее: базы данных, время загрузки, время загрузки программы и так далее.

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

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

Все это прекрасно работает и в Windows, используя CreateFileMapping () и MapViewOfFile () (и GetSystemInfo (), чтобы получить SYSTEM_INFO.dwAllocationGranularity --- не SYSTEM_INFO.dwPageSize).

mlbrock
источник
Я просто погуглил и нашел этот небольшой фрагмент о 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 файл ввода-вывода до сих пор побеждает, но это должно сближать.

paxos1977
источник
2

На мой взгляд, использование mmap () «просто» освобождает разработчика от необходимости писать собственный кеширующий код. В простом случае «прочитать файл точно один раз» это не будет трудным (хотя, как указывает mlbrock, вы все равно сохраняете копию памяти в пространстве процесса), но если вы идете туда-сюда в файле или пропуская биты и так далее, я считаю, что разработчики ядра, вероятно, проделали лучшую работу по внедрению кэширования, чем я могу ...

Майк
источник
1
Скорее всего, вы лучше справитесь с кэшированием данных, относящихся к конкретному приложению, чем ядро, которое очень слепо работает с блоками размера страницы (например, оно использует только простую схему псевдо-LRU, чтобы решить, какие страницы удалять). ) - хотя вы можете много знать о правильной детализации кэширования, а также иметь представление о будущих моделях доступа. Реальное преимущество mmapкеширования состоит в том, что вы просто повторно используете существующий кеш страниц, который уже будет там, так что вы получаете эту память бесплатно, и она может быть разделена между процессами.
BeeOnRope
2

Я помню отображение большого файла, содержащего древовидную структуру, в память много лет назад. Я был поражен скоростью по сравнению с обычной десериализацией, которая включает в себя большую работу в памяти, такую ​​как выделение узлов дерева и установка указателей. Так что на самом деле я сравнивал один вызов mmap (или его аналога в Windows) со многими (MANY) вызовами оператора new и вызовов конструктора. Для такого рода задач mmap непобедим по сравнению с десериализацией. Конечно, для этого нужно изучить повышающий перемещаемый указатель.


источник
Это больше похоже на рецепт катастрофы. Что вы делаете, если изменяется расположение объекта? Если у вас есть виртуальные функции, все указатели vftbl, вероятно, будут неправильными. Как вы контролируете, где файл отображается? Вы можете дать ему адрес, но это только подсказка, и ядро ​​может выбрать другой базовый адрес.
Йенс
Это прекрасно работает, когда у вас есть стабильная и четко определенная схема дерева. Затем вы можете привести все к соответствующим структурам и следовать указателям на внутренние файлы, каждый раз добавляя смещение «начальный адрес mmap». Это очень похоже на файловые системы, использующие inode и деревья каталогов
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 (то есть он не будет пытаться загрузить все содержимое до возврата из вызова, но сделает это в асинхронном режиме с feed_data). По крайней мере, это дает лучшие результаты с этим флагом, даже если руководство заявляет, что ничего не делает без MAP_PRIVATE с 2.6.23.

оны
источник
2
Вы хотите posix_madviseсWILLNEED флагом для ленивых подсказок предварительно заполнить.
ShadowRanger
@ShadowRanger, звучит разумно. Хотя я бы обновил man-страницу, чтобы четко указать, что posix_madviseэто асинхронный вызов. Также было бы неплохо обратиться mlockк тем, кто хочет подождать, пока вся область памяти не станет доступной без ошибок страницы.
оны