Как стать автором
Обновить

Комментарии 95

а какая тут может быть критика, немного по другому интерпретированные слова с книги).
P.s
А вообще я очень рад этому блогу.
Что, простите? Какой книги?
НЛО прилетело и опубликовало эту надпись здесь
Да любой. Во всех примерно это и написано.
Пятница, вечер… плохо выражаю мысли.
Хотел сказать, что здесь критиковать то? Просто описание работы функции mmap() и пример её использования.
Всё вообщем то хорошо.
Естественно я не претендую на авторство функции mmap(). Всё уже давно придумано до нас. Всё, что нам остаётся — это систематизировать эти знания и доводить их до остальных. Что я и попытался сделать.
На мой взгляд интересно было бы почитать про создание проекта.
Допустим примитивный web сервер. Или счётчик трафика. А описание системных вызовов есть в книгах.
Только по месту работы =)
тфу ты, промазал
вы кстати в каком нибудь проекте состоите?
С кодом немного накосячили:
1) во многих местах точки вместо подчёркиваний
2) необъяснённый #include "apue.h"
3) mmap@ вместо mmap(0
Спасибо, поправил. Это у меня так из djvu файла скопировалось.
Ещё не всё :)
statbuf.st.sizestatbuf.st_size (в трёх местах)
SEEK.SETSEEK_SET
Да что ж такое-то. Щас исправлю и пойду уже спать. Спасибо за внимательность.
Все подобные ошибки легко выявляются компилятором ;)
Не было у меня на момент написания топика возможности откомпилировать.
Я попытался скомпилировать пример кода приведенного в статье.
Для этого пришлось добавить несколько констант и объявлений функций. Вот что из этого получилось. link to gist
НЛО прилетело и опубликовало эту надпись здесь
Там же и так ясно написано с какой книги взят пример кода.
Спасибо, Капитан Очевидность.
> мало кто использует их в своих программах. И очень зря.
Насчёт — «зря» — очень спорно
Навскидку:
1) Преимущество перед fseek по скорости работы никакого.
2) Заведомо не кроссплатформенно
3) Очень мало форматов ориентированы на удобное использование с mmap. Как правило все форматы — будь то картинки, звук, текст — упакованы и пережаты так, что кроме как последовательным чтением их не возьмёшь.
НЛО прилетело и опубликовало эту надпись здесь
> Блочное чтение быстрее последовательного.
А ктож спорит-то, только причём тут mmap? mmap не читает весь файл блоком, а как пойдёт чтение в программе — всё зависит от программиста.

> 1. Докажите
Сейчас не успеваю доказать, вкратце — mmap это по-сути завуалированный read/write, причём не самым лучшим способом порой.

> 2. используйте gcc
О да :) И компилятор мне добавит поддержку системного вызова? Вы действительно думали, что mmap — это функция glibc?

> Блочное чтение…
Блочное чтение — это read. mmap — это «непоймикакое» чтение.
В read можно отдать параметр — длину блока, например 128Кбайт — это будет оптимально.
Средствами mmap вы всегда считываете 1 байт (а как вы хотели, из «памяти» всё по 1 байту считывается!). Другое дело, что mmap считает всё равно блок, заведомо неизвестно какой, но явно:
1) Меньшего размера
2) Неоптимальным способом
mmap читает файл блоками не меньше чем по одной странице, не?
P.S. в виндах mmap тоже есть, если лень писать враперы самому можно использовать apr (apache portable runtime)
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
Думаю это не совсем то, SSoft хотел сказать, что в случае read дублируется память в ядре и в userspace-приложении.
> Взгляните хотя бы на исходники 7-zip и посмотрите, каким образом он производит начальную и конечную обработку архива.
Посмотрел, не вникал в подробности, но mmap я там не обнаружил.
(http://www.7-zip.org/). Зато без труда нашёл fread, fseek и т.д.

> У нас есть большой файл. К примеру мы хотим его отсортировать
Ну да, это самая типичная задача, которую выполняют над файлами!

> Если читать файл по одному байту
Только больной будет читать их по-одному байту, не что не мешает читать их блоком, причём БОЛЬШИМ, чем блок чтения mmap.

> после каждой отработки функции read ОС имеет полное право передать управление какому либо другому процессору
Это не довод, ОС может прервать выполнение процесса когда ей заблагорассудится и без системного вызова.

> лишние объемы памяти за счет копий данных в кеше
Да возможно, для каких-то задач mmap может быть удобен, но не более того.
И круг таких задач крайне узок.

Большинство форматов файлов упакованы ПОТОКОВО, т.е. предполагают чтение фиксированным, достаточно большим чунком:

char chunk[1024 * 64];
while (! конец) {
read (fd, chunk, ...)
// Обработка chunk
}

Примерно так устроены архивы, файлы изображений, видео, звука и т.д.

Если же их читать mmap-ом, то получаем именно побайтовое чтение с диска, которое, конечно, будет переведено в чтение блоками, но небольшими, и будет не так эффективно.

И по памяти в этом случае эффективность read будет выше, поскольку на чтение одного, пусть даже большого файла, задействован один относительно небольшой буфер.
>Если же их читать mmap-ом, то получаем именно побайтовое чтение с диска, которое,
>конечно, будет переведено в чтение блоками, но небольшими, и будет не так эффективно.

Вообще-то если вы не знаете или не понимаете сути работы mmap, то не нужно делать голословных утверждений.
mmap работает примерно так же разделы подкачки (или файлы для win), при помощи механизма страничной адресации (это которая paging). Страницы (на аппаратном уровне для x86 обычно 4кб или 4Мб) загружаются и выгружаются из оперативной памяти по мере необходимости сразу целиком, а никак не по 1 байту. Это можно рассматривать так, как-будто ОС добавляет себе еще один swap. И соответственно работает он по тем же правилам, что и основной swap раздел.
Поэтому здесь нет ни накладных расходов, ни потери скорости при копировании память-память, только работа диск-память.
Единственным недостатком может стать случай с большим количеством page faults (забыл как это по-русски), и то это происходит тогда, когда требуемые страницы постоянно сбрасываются и обратно загружаются в оперативную память.
> Вообще-то если вы не знаете или не понимаете сути работы mmap, то не нужно делать голословных утверждений. mmap работает примерно так же разделы подкачки…

Отбейте у себя привычку читать между строк:

> Если же их читать mmap-ом, то получаем именно побайтовое чтение с диска, которое, конечно, будет переведено в чтение блоками, но небольшими, и будет не так эффективно.
НЛО прилетело и опубликовало эту надпись здесь
«Отображаемые» с одной «м».
Сам долбился с отображаемыми файлами, когда курсовой писал. В юниксе все одной функцией делается, но с кучей параметров(что и была приведена) а в вин побольше надо писать. Примеров больше надо реально работающих, те что я видел половина постоянно обращалась к невыделенной памяти, происходили ошибки. Начал смотреть как сделано отображение в grep и ktorrent, но половину не понял, да и не люблю магию с адресами (. Пишите примеры короче
Насчет «зря» это вы погорячились. На системах бсд активное использование mmap создает довольно заметные проблемы с файловым кэшем.
НЛО прилетело и опубликовало эту надпись здесь
man write
man mmap
и посмотрите аргументы функций
НЛО прилетело и опубликовало эту надпись здесь
Извиняюсь, показалось write(b, a)
Но тогда следует задать такой вопрос — почему не в цикле
read (A, c)
write (B, c)
и что быстрее
НЛО прилетело и опубликовало эту надпись здесь
Вы оба правы ;-)
Еще хотел добавить, что начиная с kernel 2.4 используется mmap2.
mmap тоже вызывает mmap2, но в mmap2 используется офсет из блоков по 4096 байт, что позволяет отображать в память файлы размера до 2^44 байтов=16 384 ТБайт.
что-то пример какой-то подозрительный. если файл, который нужно скопировать, весит 4 гига, а памяти на машине — 2 гига, то копировать начинаем через своп или как?
Изучайне мат часть. В частности что такое виртуальная память и что такое виртуальные адреса (это не только своп!).
это такое стиль ответов без ответа? я задал конкретный вопрос с целью получить конкретный ответ.
Ваш «ответ» вообще ни о чём: ни ссылок, ни объяснений. Если есть желание показать себя крутым, но сказать при этом ничего конкретного не можете, может стоит промолчать?
О господи. Да кому нужна эта крутость? Времени не было просто ответить развернуто.

По сабжу: отображение файла в память не занимает физических страниц памяти вообще. При попытке процесса приложения обратиться к странице виртуальной памяти, отображенной в файл, происходит аппаратное исключение «ошибка обращения к странице». Ядро это исключение ловит и проверяет состояние. Убедившись, что это не ошибка приложения и что данная страница действительно отображается в файл, ядро производит временное выделение физического кадра (4К), который заполняет данными файла по соответствующему смещению. Далее управление передается приложению.

Поэтому, с точки зрения приложения, такое отображение файла в память ничем не отличается от просто аллоченной области памяти.

Такой подход наиболее эффективен в случаях, когда приложение часто елозит по файлу. При этом, код приложения получается намного проще, нежели работа с файлами через методы fXXX.
НЛО прилетело и опубликовало эту надпись здесь
Ну блин вы должны понимать что чудес не бывает. Я имел в виду что страницы не аллокируются перманентно для хранения содержимого файла, а используются только по факту I/O. Капитан Очевидность нервно курит :)
НЛО прилетело и опубликовало эту надпись здесь
Во-первых, readahead.
Во-вторых, винда, например, читает блоками по 256кб, насколько я помню, хоть страница 4к.
НЛО прилетело и опубликовало эту надпись здесь
А вы уверены, что чтение с жесткого диска 4к и 256к занимает разное время (по сравнению со временем seek-а)? :-)
НЛО прилетело и опубликовало эту надпись здесь
Думаю, на readahead-нные страницы из кэша вылетят при первой же возможности, т.к. их никто не читал. Так что 64 предлагаю уменьшить до 42 или даже 4.2 :-)
НЛО прилетело и опубликовало эту надпись здесь
Да нет же, они будут специальным образом тормозить и, таким образом, снижать нагрузку :-D
Вы не правы. По трем моментам.

Во-первых, есть такое понятие как working set. Ни одному приложению не нужны ВСЕ данные СРАЗУ. Такого никогда не бывает. Даже в высоконагруженных БД.

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

В-третьих, не надо путать само I/O и логику работы. Дисковая подсистема имеет собственные кэши и будет работать в любом случае. Так же как из памяти в кэш, из диска никто не читает данные по одному байту. Читается весь сектор сразу.
НЛО прилетело и опубликовало эту надпись здесь
Поясните, пожалуйста, что имеется ввиду.
Если я вызываю read(), происходит вызов ядра. И если я делаю x = *p, где p — указатель на непрогруженную страницу, происходит исключение и вызов ядра.


Да, здесь я пожалуй затупил :) Сказать определенно сходу не могу, однако точно помню что разница там есть. В общем надо залезть в литературу и перечитать.

BTW, если интересует внутрення кухня ядра, могу посоветовать книгу Д. Бовет и М. Чезати «Ядро Linux», издательство O'Reily. Вполне доступно все расписано. Ну или в сами исходники %)
Единственный на данный момент пришедший в голову аргумент в пользу mmap на нагруженных системах такой:

Если файл отображается в режиме read only, скажем html страница или иной ресурс на веб сервере, то ядро будет отображать один и тот же физический кадр памяти для всех конкурирующих процессов, обращающихся к данному месту файла.

То есть: 100 процессов открыли файл Х и читают его начало — физически в памяти файл будет представлен только единственным кадром, отображенным на все процессы, в то время как при чтении через read() каждый процесс будет вынужден выделить свой отдельный буфер на куче.
НЛО прилетело и опубликовало эту надпись здесь
Нет, вы не правы. Ядро внимательно следит за буферами и выкидывает неиспользуемые страницы, либо заменяет их другими.

Еще раз: отображение файла в память НЕ ЗНАЧИТ что выделяется память и туда копируется весь файл. Можно запамить хоть 10 гиговый файл (при наличии соответствующего объема виртуальных адресов), и до тех пор пока приложение реально не начнет с ним работать, в физической памяти он не будет занимать ни байта
* замапить, пардон :)
НЛО прилетело и опубликовало эту надпись здесь
Я уверен, своп использоваться не будет, по крайней мере если не мапить файл с каким-нибудь copy-on-write. Не верите? Поставьте эксперимент.
С какой стати? Откуда они должны взяться? Если скажем программа мапит файл и начинает линейно его вычитать то происходит следующее:

1) маппинг файла. физ память не юзается.
2) программа пытается прочитать байт по 0 смещению:
— ядро читает первые 4К файла в буфер и маппит этот буфер в адресное пространство процесса.
3) дальнейшие 4095 прочтений происходят одинаково, просто из страницы буфера
4) читается 4097й байт — аллочится еще одна страница буфера, да в этот момент занято как бы 8К под буферы.
5) и т. д.
6) наступает некий момент (специфика ядра и настройки), когда ядро соображает, что процесс чего то зажрался и захавал уже прилично памяти под буферы — тогда очередная страница не аллочится а берется из ранее мапленных. Деталей не помню, но кажется банально по LRU (less recently used, то есть наименее используемую; в нашем случае — самую первую)
7) с этого момента суммарный объем используемой физ памяти увеличиваться не будет, не важно какого размера файл.
По поводу пункта 6 мне было довольно интересно, как же ядро определяет, что кто-то определенный участок памяти читал или нет в последний квант времени.
Увы, уже всё забыл :-)
У ядра все в таблицах есть. Тут нет никаких проблем. Если вдруг окажется что освобожденная страница вновь востребована приложением — ну какие проблемы, опять будет загружена.
Я просто не помню, как ядру эту информацию процессор сливает.

Википедия пишет: At a certain fixed time interval, the clock interrupt triggers and clears the referenced bit of all the pages, so only pages referenced within the current clock interval are marked with a referenced bit.
Какую информацию? То что страница не была востребована? Или что она вообще есть?

Про востребованность вы правильно написали, а присутствие определяется специальным битом присутствия в записи таблицы дескрипторов страниц. Если этот бит снят, то при попытке обращения к такой странице будет сгенерировано аппаратное исключение, про которое я уже писал выше.
Вы ответили за меня на все вопросы =) Спасибо вам огромное.
Всегда пожалуйста :-)
Да нет, просто кэши потеряются. Да и то не факт.
А никто случаем не знает утилиты чтобы померить пик потребления памяти своего приложения? Мне советовали посмотреть valgrind, но он мерит только неосвобожденную память на выходе, а как померить память во время исполнения?
getrusage(2)?
Ага, тоже думал об этом, только я так и не понял где там в структуре rusage потребление памяти.
А вообще чтобы ее заюзать, получается нужна прога, которая форкнется, потом запустит замеряемую прогу, а в это время основной процесс в цикле вызывает getrusage?
long ru_maxrss; /* maximum resident set size */
не оно разве?
Ничего не понимаю с этим getrusage… Все поля в структурк возвращаются нулевые. Что я делаю не так?
Вот тут что наклепал pastie.org/429856
Мануал кто читать будет?
RUSAGE_CHILDREN
Return resource usage statistics for all children of the calling process that have terminated and been waited for. These statistics will include the resources used by grandchildren, and further removed descendants, if all of the intervening descendants waited on their terminated children.


Не надо оставлять мертвых детей валяться вокруг.

А потом надо дочитать мануал до конца:
The structure definition shown at the start of this page was taken from 4.3BSD Reno. Not all fields are meaningful under Linux. In Linux 2.4 only the fields ru_utime, ru_stime, ru_minflt, and ru_majflt are maintained. Since Linux 2.6, ru_nvcsw and ru_nivcsw are also maintained.


и сказать, что я не прав :-)
Хм, может все-таки и правы, в моем случае:) Я под маком эксперементирую, а он же на бсд основан.
По этой же причине я /proc заюзать не могу…
У меня в мане написано
Getrusage() returns information describing the resources utilized by the current process, or all its terminated child processes.

Т.e., чтобы получить данные о ресурсах мне придеться сначала SIGTERM детям послать?:)
Вобщем похоже придется другой способ искать. Очень странно что стандартного решения нету…
Пока идея — скрипт который в цикле парсит вывод ps aux :)
О, прошу прощения, я по использованию valgrind сделал вывод про linux. Не знал, что его под что-либо кроме Linux/x86 портанули уже.

Как вариант — натравить strace (или что там на маке?) на этот самый ps и посмотреть, какие сисколы он использует для сбора этой статистики.
В самой первой статье этого блога в комментах узнал про порт valgrind. Гугленье показало что он уже даже в бранче оф. репозитария:)
strace нету на маке. Есть гуишные альтернативы, попробую покопаться, спасибо за идею.
Тут кстати и для замера памяти гуишные утилиты есть, но, как-то неудобно ими пользоваться, хочу консольный вариант:)
Решил погуглить еще про strace для мака, и нашел чту тут есть солярисовская DTrace. И похоже она умеет мерять память, то что нужно! Будем разбираться:)
Мне, кажется, программирование в *nix — это не столько C, сколько Shell…
Редко приходится писать что-либо так низко, что этого нельзя сделать шелл-скриптом, используя уже кем-то написанные программы, перенапрявляя и анализируя их ввод и вывод…
НЛО прилетело и опубликовало эту надпись здесь
Подумайте над тем что я имел в виду, вместо мгновенного отрицания. Кажется куча шеллов как раз имеется и ее роль в логике работы системы сложно переоценить.

Но то говорю не о системном программировании и о важности Си для системы, а просто о «программировании в среде *nix» в том числе прикладном — и тут мне настойчиво кажется, что на каждого кто сидит сейчас за написанием программы на Си, найдется несколько десятков тех, кто пишет скрипт. И это занятие ничем, кроме как программированием не назовешь. Разве не так? В конце концов не шелл так python или perl…

Ну в случае задач общего плана — безусловно найдется способ не изобретать велосипед, но все таки в мире еще прилично нерешенных проблем, так что так безапелляционно заявлять я думаю не стоит %)
Тут приводят пример — копирование файла лучше (вроде бы) сделать через sendfile(), хоть ее используют обычно в серверах, она делает копирование без переноса данных в userspace, и нет никакого смысла в цикле вызывать memcpy (и вообще, эту функцию лучше никогда не использоватаь, канал к памяти не резиновый чтобы гонять данные туда-сюда, еще и разрушая процессорный кеш на больших блоках).

Жаль что многоие программисты не умеют выбирать ниболее оптимальный инструмент для совей работы.

p.s Цитата из мана, запоминайте! sendfile() copies data between one file descriptor and another. Because this copying is done within the kernel, sendfile() is more efficient than the combination of read(2) and write(2), which would require transferring data to and from user space.

p.p.s А вот клевая функция sendvfile() позволяет объединить данные из нескольких файлов и перемешать их с заранее подготовленными данными в памяти (например заголовки, если речь о веб-сервере) и отправить все это в файл или сокет.
Извиняюсь за некропостинг.

Первый аргумент — желаемый адрес начала участка отбраженной памяти. Не знаю, когда это может пригодится.


Это очень полезно если, например, два процесса используют один и тот же файл для обмена данными, где в виде данных выступают С-структуры с сырыми указателями внутри.

Для решения этой проблемы есть два решения:
  1. offset-pointers более гибкие, однако требующие больше внимания и не совместимые с существующими классами в большинстве случаев)
  2. fixed mapping address — как раз этот случай, сырой указатель будет валидным в обоих процессах


Замерял с помощью quantify скорость работы программы, которая буферизировано копирует большой файл размером 500 мб в другой файл.

Что есть quantify?

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории