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

Файлы, отображаемые в память

Программирование *
В этой статье я хотел бы рассказать о такой замечательной штуке, как файлы, отображаемые в память(memory-mapped files, далее — MMF).
Иногда их использование может дать довольно таки существенный прирост производительности по сравнению с обычной буферизированной работой с файлами.

Это механизм, который позволяет отображать файлы на участок памяти. Таким образом, при чтении данных из неё, производится считывание соответствующих байт из файла. С записью аналогично.
«Клёво, конечно, но что это даёт?» — спросите вы. Поясню на примере.
Допустим, перед нами стоит задача обработки большого файла(несколько десятков или даже сотен мегабайт). Казалось бы, задача тривиальна — открываем файл, поблочно копируем из него в память, обрабатываем. Что при этом происходит. Каждый блок копируется во временный кэш, затем из него в нашу память. И так с каждым блоком. Налицо неоптимальное расходование памяти под кэш + куча операций копирования. Что же делать?
Тут-то нам на помощь и приходит механизм MMF. Когда мы обращаемся к памяти, в которую отображен файл, данные загружаются с диска в кэш(если их там ещё нет), затем делается отображение кэша в адресное пространство нашей программы. Если эти данные удаляются — отображение отменяется. Таким образом, мы избавляемся от операции копирования из кэша в буфер. Кроме того, нам не нужно париться по поводу оптимизации работы с диском — всю грязную работу берёт на себя ядро ОС.
В своё время я проводил эксперимент. Замерял с помощью quantify скорость работы программы, которая буферизировано копирует большой файл размером 500 мб в другой файл. И скорость работы программы, которая делает то же, но с помощью MMF. Так вот вторая работает быстрее почти на 30% (в Solaris, в других ОС результат может отличаться). Согласитесь, неплохо.
Чтобы воспользоваться этой возможностью, мы должны сообщить ядру о нашем желании отобразить файл в память. Делается это с помощью функции mmap().
#include<sys/mman.h>
void *mmap(void *addr, size_t len, int prot, int flag, int filedes, off_t off);

Она возвращает адрес начала участка отображаемой памяти или MAP_FAILED в случае неудачи.
Первый аргумент — желаемый адрес начала участка отбраженной памяти. Не знаю, когда это может пригодится. Передаём 0 — тогда ядро само выберет этот адрес.
len — количество байт, которое нужно отобразить в память.
prot — число, определяющее степень защищённости отображенного участка памяти(только чтение, только запись, исполнение, область недоступна). Обычные значения — PROT_READ, PROT_WRITE (можно кобминировать через ИЛИ). Не буду на этом останавливаться — подробнее читайте в манах. Отмечу лишь, что защищённость памяти не установится ниже, чем права, с которыми открыт файл.
flag — описывает атрибуты области. Обычное значение — MAP_SHARED. По поводу остальных — курите маны. Но замечу, что использование MAP_FIXED понижает переносимость приложения, т.к. его подержка является необязательной в POSIX-системах.
filedes — как вы уже догались — дескриптор файла, который нужно отобразить.
off — смещение отображенного участка от начала файла.

Важное замечание. Если вы планируете использовать MMF для записи в файл, перед маппингом необходимо установить конечный размер файла не меньше, чем размер отображенной памяти! Иначе нарвётесь на SIGBUS.

Ниже приведён пример(честно стырен из замечательной книжки «Unix. Профессиональное программирование») программы, которая копирует файл с использованием MMF.
#include <fcntl.h>
#include <sys/mman.h>
int main(int argc, char *argv[])
{
  int fdin, fdout;
  void *src, *dst;
  struct stat statbuf;
  if (argc != 3)
  err_quit("Использование: %s <fromfile> <tofile>", argv[0]);
  if ( (fdin = open(argv[1], O_RDONLY)) < 0 )
    err_sys("невозможно открыть %s для чтения", argv[1]);
  if ( (fdout = open(argv[2], O_RDWR | O_CREAT | O_TRUNC, FILE_MODE)) < 0 )
    err_sys("невозможно создать %s для записи", argv[2]);
  if ( fstat(fdin, &statbuf) < 0 ) /* определить размер входного файла */
    err_sys("fstat error");
  /* установить размер выходного файла */
  if ( lseek(fdout, statbuf.st_size - 1, SEEK_SET) == -1 )
  err_sys("ошибка вызова функции lseek");
  if ( write(fdout, "", 1) != 1 )
    err_sys("ошибка вызова функции write");
  if ( (src = mmap(0, statbuf.st_size, PROT_READ, MAP_SHARED, fdin, 0)) == MAP_FAILED )
    err_sys("ошибка вызова функции mmap для входного файла");
  if ( (dst = mmap(0, statbuf.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fdout, 0)) == MAP_FAILED )
    err_sys("ошибка вызова функции mmap для выходного файла");
  memcpy(dst, src, statbuf.st_size); /* сделать копию файла */
  exit(0);
}

* This source code was highlighted with Source Code Highlighter.

Вот вобщем-то и всё. Надеюсь, эта статья была полезной. С удовольствием приму конструктивную критику.
Теги:
Хабы:
Всего голосов 58: ↑51 и ↓7 +44
Просмотры 68K
Комментарии Комментарии 94