Я работаю над программой, которая будет обрабатывать файлы, которые могут иметь размер 100 ГБ или более. Файлы содержат наборы записей переменной длины. Я запустил и запустил первую реализацию и теперь смотрю на повышение производительности, особенно на более эффективный ввод-вывод, поскольку входной файл сканируется много раз.
Есть ли практическое правило для использования по mmap()
сравнению с чтением в блоках через библиотеку C ++ fstream
? Что я хотел бы сделать, это прочитать большие блоки с диска в буфер, обработать полные записи из буфера, а затем прочитать больше.
mmap()
Код потенциально может получить очень грязный , поскольку mmap
«г блоки должны лежат на странице размера границы (мое понимание) и записи потенциально как через границы страницы. С помощью fstream
s я могу просто перейти к началу записи и начать чтение снова, поскольку мы не ограничены чтением блоков, которые лежат на границах размера страницы.
Как я могу выбрать между этими двумя вариантами, не написав сначала полную реализацию? Практические правила (например, mmap()
в 2 раза быстрее) или простые тесты?
mmap()
это в 2-6 раз быстрее, чем при использовании системных вызовов, напримерread()
.Ответы:
Я пытался найти последнее слово о производительности mmap / read в Linux и наткнулся на хороший пост ( ссылку ) в списке рассылки ядра Linux. Это с 2000 года, так что с тех пор было много улучшений ввода-вывода и виртуальной памяти в ядре, но это хорошо объясняет причину, почему
mmap
илиread
может быть быстрее или медленнее.mmap
имеет больше накладных расходов, чемread
(точно так же, какepoll
имеет больше накладных расходов, чемpoll
, который имеет больше накладных расходов, чемread
). Изменение отображений виртуальной памяти является довольно дорогой операцией на некоторых процессорах по тем же причинам, по которым переключение между различными процессами стоит дорого.Тем не мение,
read
, ваш файл может быть сброшен из века кэша назад. Это не относится, если вы используете файл и сразу же удаляете его. (Если вы пытаетесь использоватьmlock
страницы только для того, чтобы сохранить их в кеше, вы пытаетесь перехитрить кеш диска, и этот вид дураков редко помогает производительности системы).Обсуждение mmap / read напоминает мне о двух других обсуждениях производительности:
Некоторые Java-программисты были потрясены, обнаружив, что неблокирующий ввод-вывод часто медленнее, чем блокирующий ввод-вывод, что имело смысл, если вы знаете, что неблокирующий ввод-вывод требует выполнения большего количества системных вызовов.
Некоторые другие сетевые программисты были потрясены, узнав, что
epoll
это часто медленнее, чемpoll
, что имеет смысл, если вы знаете, что управлениеepoll
требует больше системных вызовов.Вывод: используйте карты памяти, если вы обращаетесь к данным случайным образом, сохраняете их в течение длительного времени или если вы знаете, что можете поделиться ими с другими процессами (
MAP_SHARED
не очень интересно, если нет фактического обмена). Читайте файлы нормально, если вы обращаетесь к данным последовательно или отбрасываете их после чтения. И если любой из этих методов делает вашу программу менее сложной, сделайте это . Для многих реальных случаев не существует надежного способа показать, что кто-то работает быстрее, без тестирования вашего реального приложения, а НЕ теста.(Извините за этот вопрос, но я искал ответ, и этот вопрос продолжал подниматься в верхней части результатов Google.)
источник
mmap
противостоянииread()
в этом потоке по-прежнему верны, как и в прошлом, общая производительность не может быть определена путем суммирования плюсов и минусов, а только путем тестирования конкретной аппаратной конфигурации. Например, это спорно , что «Вызов ттар имеет больше накладных расходов , чем чтение» - даmmap
должен добавить отображения в таблице страниц процесса, ноread
должен копировать все чтения байтов из ядра в пространство пользователя.mmap
меньше служебных данных, чемread
при чтении размером больше страницы (4 КиБ). Теперь очень верно, что если вы хотите получить доступ к данным редко и случайно,mmap
это действительно очень хорошо, но обратное не обязательно верно:mmap
может все же быть лучшим для последовательного доступа.mmap
более быстрым, я бы ожидал увидеть как минимум весь аппарат тестирования (исходный код) с табличными результатами и номером модели процессора.mmap
не очищает TLB, за исключением необычных обстоятельств (ноmunmap
может). Мои тесты включали в себя как микробенчмарки (в том числеmunmap
), так и «в приложении», работающие в реальных условиях использования. Конечно, мое приложение не совпадает с вашим приложением, поэтому люди должны тестировать локально. Даже неясно,mmap
чему способствует микропроцессор: онread()
также значительно ускоряется, поскольку целевой буфер на стороне пользователя обычно остается на уровне L1, что может не произойти в больших приложениях. Так что да, "это сложно".Основной ценой производительности будет дисковый ввод-вывод. «mmap ()», конечно, быстрее, чем istream, но разница может быть не заметна, потому что дисковый ввод-вывод будет доминировать во время выполнения.
Я попробовал фрагмент кода Бена Коллинза (см. Выше / ниже), чтобы проверить его утверждение, что «mmap () намного быстрее», и не нашел измеримых различий. Смотрите мои комментарии на его ответ.
Я бы, конечно, не рекомендовал по отдельности mmap'ить каждую запись по очереди, если только ваши «записи» не огромны - это будет ужасно медленно, требуя 2 системных вызова для каждой записи и, возможно, потерю страницы из кеша дисковой памяти .... ,
В вашем случае я думаю, что вызовы mmap (), istream и низкоуровневые open () / read () будут примерно одинаковыми. Я бы порекомендовал mmap () в этих случаях:
(кстати - я люблю mmap () / MapViewOfFile ()).
источник
Mmap намного быстрее. Вы можете написать простой тест, чтобы доказать это себе:
против:
Понятно, что я опускаю детали (например, как определить, когда вы достигнете конца файла, например, если ваш файл не кратен
page_size
), но на самом деле он не должен быть намного сложнее, чем этот. ,Если вы можете, вы можете попытаться разбить ваши данные на несколько файлов, которые могут быть mmap () - полностью, а не частично (намного проще).
Пару месяцев назад у меня была полуиспечённая реализация класса mmap () - ed потока со скользящим окном для boost_iostreams, но никто не заботился, и я занялся другими вещами. К сожалению, я удалил архив старых незавершенных проектов несколько недель назад, и это была одна из жертв :-(
Обновление : я также должен добавить предостережение о том, что этот тест будет выглядеть совсем иначе в Windows, потому что Microsoft внедрила изящный файловый кеш, который в основном выполняет то, что вы делаете с mmap. Т.е. для часто используемых файлов вы могли бы просто выполнить std :: ifstream.read (), и это было бы так же быстро, как и mmap, потому что файловый кеш уже сделал бы отображение памяти для вас, и он прозрачен.
Финальное обновление : посмотрите, люди: во многих различных сочетаниях платформ ОС и стандартных библиотек, дисков и иерархий памяти я не могу с уверенностью сказать, что системный вызов
mmap
, рассматриваемый как черный ящик, всегда всегда будет значительно быстрее чемread
. Это не было моим намерением, даже если мои слова могли быть истолкованы таким образом. В конечном счете, моя точка зрения заключалась в том, что отображаемый в память ввод-вывод, как правило, выполняется быстрее, чем байт-ориентированный ввод-вывод; это все еще правда . Если вы обнаружите экспериментально, что между этими двумя понятиями нет никакой разницы, то единственное объяснение, которое мне кажется разумным, заключается в том, что ваша платформа реализует отображение памяти под прикрытием таким образом, чтобы это было выгодно для производительности обращений кread
, Единственный способ быть абсолютно уверенным, что вы используете переносимый ввод-вывод с отображением в памяти, это использоватьmmap
. Если вас не волнует переносимость, и вы можете полагаться на конкретные характеристики своих целевых платформ, тогда использованиеread
может подойти, не жертвуя при этом какой-либо производительностью.Изменить, чтобы очистить список ответов: @jbl:
Конечно, я писал 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
Я выполнил часть работы за вас, а также предоставляет хуки для фильтров и цепочек, поэтому я подумал, что было бы более полезно реализовать это таким образом.источник
mmap()
о файле страницы за раз? Если asize_t
достаточно вместителен для хранения размера файла (очень вероятно, в 64-разрядных системах), то толькоmmap()
весь файл за один вызов.Здесь уже есть много хороших ответов, которые охватывают многие существенные моменты, поэтому я просто добавлю пару вопросов, которые я не видел, рассмотренных непосредственно выше. Таким образом, этот ответ не следует считать исчерпывающим из плюсов и минусов, а скорее дополнением к другим ответам здесь.
ММАП кажется волшебством
Если взять в качестве базового 2 случай, когда файл уже полностью кэширован 1 , это может показаться очень похожим на магию :
mmap
mmap
требуется только 1 системный вызов (потенциально) отобразить весь файл, после чего системные вызовы больше не нужны.mmap
не требует копирования данных файла из ядра в пространство пользователя.mmap
позволяет получить доступ к файлу «как к памяти», в том числе обрабатывать его любыми дополнительными приемами, которые вы можете сделать с памятью, такими как автоматическая векторизация компилятора, встроенные функции SIMD , предварительная выборка, оптимизированные процедуры синтаксического анализа в памяти, OpenMP и т. д.В случае, если файл уже находится в кеше, кажется, что его невозможно превзойти: вы просто получаете прямой доступ к кешу страницы ядра как к памяти, и он не может работать быстрее, чем это.
Ну, это может.
Mmap на самом деле не волшебство, потому что ...
mmap по-прежнему работает на странице
Основная скрытая стоимость
mmap
vsread(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
реализацию, которая может эффективно обрабатывать большие карты в тех случаях, когда, например, нижележащие страницы находятся в смежной физической памяти.... в то время как
read()
подход становится относительно быстрее, когда:read()
Системный вызов имеет хорошую производительность копирования. Например, хорошаяcopy_to_user
производительность на стороне ядра.Аппаратные факторы выше варьируются дико на различных платформах, даже в пределах одной и той же семье (например, в пределах х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 раз или так.источник
mmap
будет иметь непреодолимое преимущество, поскольку позволяет избежать фиксированных накладных расходов на вызовы ядра. С другой стороны,mmap
также увеличивает давление TLB и фактически замедляет фазу «прогрева», когда байты читаются впервые в текущем процессе (хотя они все еще находятся на странице страницы), так как это может сделать больше работы, чемread
, например, для «разборки» соседних страниц ... и для тех же приложений «разогрев» - это все, что имеет значение! @CaetanoSauerМне жаль, что Бен Коллинз потерял свой исходный код mmap для скользящих окон. Это было бы неплохо иметь в Boost.
Да, отображение файла намного быстрее. По сути, вы используете подсистему виртуальной памяти ОС для связи памяти с диском и наоборот. Подумайте об этом так: если бы разработчики ядра ОС могли сделать это быстрее, они бы это сделали. Потому что это делает все быстрее: базы данных, время загрузки, время загрузки программы и так далее.
Подход со скользящим окном на самом деле не так уж и сложен, поскольку одновременно можно отобразить несколько непрерывных страниц. Таким образом, размер записи не имеет значения, если в память помещается самая большая из всех записей. Важным моментом является ведение бухгалтерского учета.
Если запись не начинается на границе getpagesize (), ваше отображение должно начинаться на предыдущей странице. Длина отображаемой области простирается от первого байта записи (при необходимости округляется до ближайшего кратного значения getpagesize ()) до последнего байта записи (округляется до ближайшего кратного значения getpagesize ()). Когда вы закончите обработку записи, вы можете отменить ее отображение и перейти к следующей.
Все это прекрасно работает и в Windows, используя CreateFileMapping () и MapViewOfFile () (и GetSystemInfo (), чтобы получить SYSTEM_INFO.dwAllocationGranularity --- не SYSTEM_INFO.dwPageSize).
источник
Mmap должен быть быстрее, но я не знаю, сколько. Это очень сильно зависит от вашего кода. Если вы используете mmap, то лучше всего отобразить весь файл сразу, это сделает вашу жизнь намного проще. Одна потенциальная проблема заключается в том, что если размер вашего файла превышает 4 ГБ (или на практике ограничение ниже, часто 2 ГБ), вам потребуется 64-разрядная архитектура. Так что, если вы используете среду 32, вы, вероятно, не хотите ее использовать.
Сказав это, может быть лучший путь к повышению производительности. Вы сказали, что входной файл сканируется много раз , если вы можете прочитать его за один проход, а затем покончить с этим, это потенциально может быть намного быстрее.
источник
Возможно, вам следует предварительно обработать файлы, поэтому каждая запись находится в отдельном файле (или, по крайней мере, каждый файл имеет размер mmap).
Также не могли бы вы сделать все шаги обработки для каждой записи, прежде чем перейти к следующей? Может быть, это позволит избежать некоторых накладных расходов ввода-вывода?
источник
Я согласен , что mmap'd файл I / O будет быстрее, но в то время как ваш бенчмаркинг код, должен не контрпример быть несколько оптимизированы?
Бен Коллинз написал:
Я бы предложил также попробовать:
Кроме того, вы можете также попытаться сделать размер буфера таким же, как размер одной страницы виртуальной памяти, в случае, если 0x1000 не является размером одной страницы виртуальной памяти на вашей машине ... IMHO mmap'd файл ввода-вывода до сих пор побеждает, но это должно сближать.
источник
На мой взгляд, использование mmap () «просто» освобождает разработчика от необходимости писать собственный кеширующий код. В простом случае «прочитать файл точно один раз» это не будет трудным (хотя, как указывает mlbrock, вы все равно сохраняете копию памяти в пространстве процесса), но если вы идете туда-сюда в файле или пропуская биты и так далее, я считаю, что разработчики ядра, вероятно, проделали лучшую работу по внедрению кэширования, чем я могу ...
источник
mmap
кеширования состоит в том, что вы просто повторно используете существующий кеш страниц, который уже будет там, так что вы получаете эту память бесплатно, и она может быть разделена между процессами.Я помню отображение большого файла, содержащего древовидную структуру, в память много лет назад. Я был поражен скоростью по сравнению с обычной десериализацией, которая включает в себя большую работу в памяти, такую как выделение узлов дерева и установка указателей. Так что на самом деле я сравнивал один вызов mmap (или его аналога в Windows) со многими (MANY) вызовами оператора new и вызовов конструктора. Для такого рода задач mmap непобедим по сравнению с десериализацией. Конечно, для этого нужно изучить повышающий перемещаемый указатель.
источник
Это звучит как хороший пример использования многопоточности ... Я думаю, вы могли бы довольно легко настроить один поток для чтения данных, в то время как другой обрабатывает их. Это может быть способом значительно увеличить воспринимаемую производительность. Просто мысль.
источник
Я думаю, что величайшая вещь в mmap - это возможность асинхронного чтения с:
Проблема в том, что я не могу найти правильный MAP_FLAGS, чтобы дать подсказку, что эта память должна быть синхронизирована из файла как можно скорее. Я надеюсь, что MAP_POPULATE дает правильную подсказку для mmap (то есть он не будет пытаться загрузить все содержимое до возврата из вызова, но сделает это в асинхронном режиме с feed_data). По крайней мере, это дает лучшие результаты с этим флагом, даже если руководство заявляет, что ничего не делает без MAP_PRIVATE с 2.6.23.
источник
posix_madvise
сWILLNEED
флагом для ленивых подсказок предварительно заполнить.posix_madvise
это асинхронный вызов. Также было бы неплохо обратитьсяmlock
к тем, кто хочет подождать, пока вся область памяти не станет доступной без ошибок страницы.