Что такое маппинг файлов
File mapping is the association of a file's contents with a portion of the virtual address space of a process. The system creates a file mapping object (also known as a section object) to maintain this association. A file view is the portion of virtual address space that a process uses to access the file's contents. File mapping allows the process to use both random input and output (I/O) and sequential I/O. It also allows the process to work efficiently with a large data file, such as a database, without having to map the whole file into memory. Multiple processes can also use memory-mapped files to share data.
Processes read from and write to the file view using pointers, just as they would with dynamically allocated memory. The use of file mapping improves efficiency because the file resides on disk, but the file view resides in memory. Processes can also manipulate the file view with the VirtualProtect function.
The following illustration shows the relationship between the file on disk, a file mapping object, and a file view.
The file on disk can be any file that you want to map into memory, or it can be the system page file. The file mapping object can consist of all or only part of the file. It is backed by the file on disk. This means that when the system swaps out pages of the file mapping object, any changes made to the file mapping object are written to the file. When the pages of the file mapping object are swapped back in, they are restored from the file.
A file view can consist of all or only part of the file mapping object. A process manipulates the file through the file views. A process can create multiple views for a file mapping object. The file views created by each process reside in the virtual address space of that process. When the process needs data from a portion of the file other than what is in the current file view, it can unmap the current file view, then create a new file view.
When multiple processes use the same file mapping object to create views for a local file, the data is coherent. That is, the views contain identical copies of the file on disk. The file cannot reside on a remote computer if you want to share memory between multiple processes.
Вы когда-нибудь думали, как было бы здорово, если бы слитый в один файл и минифицированный яваскрипт код в production-окружении можено было удобно читать и даже отлаживать без ущерба производительности? Теперь это возможно, если использовать штуку под названием source maps.
Если коротко, то это способ связать минифицированный/объединённый файл с файлами, из которых он получился. Во время сборки для боевого окружения помимо минификации и объединения файлов также генерируется файл-маппер, который содержит информацию об исходных файлах. Когда производится обращение к конкретному месту в минифицированном файле, то производится поиск в маппере, по которому вычисляется строка и символ в исходном файле. Developer Tools (WebKit nightly builds или Google Chrome Canary) умеет парсить этот файл автоматически и прозрачно подменять файлы, как будто ведётся работа с исходными файлами. На момент написания (оригинальной статьи — прим. перев.) Firefox заблокировал развитие поддержки Source Map. Подробнее — на MozillaWiki Source Map.
Пример — правильное определение места в исходном коде
В этом примере можно ткнуть в любом месте textarea правой кнопкой и выбрать пункт «Get original location». При этом будет произведено обращение к файлу-мапперу с передачей строки и номера символа в минифицированном коде, и будет показан соответствующий кусок кода из исходного файла. В консоль будут выведены номер строки и номер символа в исходном файле и другая интересная информация.
Реальное использование
Прежде чем смотреть следующий пример, нужно активировать просмотр source maps в Chrome Canary или WebKit nightly, для этого в свойствах активировать пункт «Enable source maps» (см. скриншот)
Зачем вообще нужны Source Maps?
- CoffeeScript
- ECMAScript 6 и выше
- SASS/LESS и т.п.
- Практически любой язык, который компилируется в JavaScript
Google Web Toolkit (GWT) недавно добавил поддержку Source Maps и Ray Cromwell из GWT сделал отличный скринкаст, показывающий работу Source Map в действии.
Другой пример использует библиотеку Google Traceur, которая позволяет писать на ES6 (ECMAScript 6) и компилировать в ES3-совместимый код. Компилятор Traceur также генерирует source map. Посмотрите на пример использования особенностей ES6 (классов и traits), как если бы они поддерживались браузером нативно. Textarea в примере также позволяет писать ES6-код, который будет компилироваться на лету в ES3 и также будет создаваться файл-маппер.
Пример — можно написать код на ES6 и сразу посмотреть в отладчике
Как это работает?
Единственный пока компилятор/минификатор с поддержкой Source Map — Closure compiler (как при компиляции сгенерировать маппер — написано ниже). При минификации JavaScript будет создан и файл-маппер. Пока Closure compiler не добавляет в конец файла специальный комментарий для Google Chrome Canary dev tools о том, что доступен файл-маппер:
Такой комментарий позволяет браузеру искать нужное место в исходном файле, используя файл-маппер. Если идея использовать странные комментарии вам не нравится, то можно добавить к скомпилированному файлу специальный заголовок:
Как и комментарий, это скажет клиенту, где искать маппер для этого файла. Использование заголовка также позволяет работать с языками, которые не поддерживают однострочные комментарии.
Файл-маппер будет скачан только если включено свойство и открыта консоль. Ну и конечно нужно будет залить исходные файлы, чтобы они были доступны по указанным в маппере путям.
Как сгенерировать файл-маппер?
Как уже говорилось выше, нужен будет Closure compiler для минификаци, склейки и генерации файла-маппера для нужных JavaScript-файлов. Для этого нужно выполнить команду:
Нужные флаги — это --create_source_map и --source_map_format . Последний нужен, т.к. по умолчанию маппер создаётся в формате V2, а нам нужен V3.
Внутреннее устройство Source Map
Чтобы лучше понять Source Map, возьмём для примера небольшой файл-маппер и подробно разберём, как устроена «адресация». Ниже приведён немного модифицированный пример из V3 spec:
- Версию маппера
- Название минифицированного/объединённого файла для production
- sourceRoot позволяет дописывать префикс в путь к исходным файлам
- sources содержит названия исходных файлов
- names содержит все настоящие названия переменных/функций из полученного файла
- а mappings — это соответствующие минифицированные названия
BASE64 VLQ или как сделать Source Map маленьким
- Номер символа в сгенерированном файле
- Исходный файл
- Номер строки в исходном файле
- Номер символа в исходном файле
- Исходное название (если есть)
Потенциальные проблемы с XSSI
В спецификации говорится о возможных проблемах с внедрением XSS при использовании Source Map. Избавиться от неё можно, написав в начале своего map-файла " )]> ", чтобы сделать это js-файл невалидным и вызвать ошибку. WebKit dev tools уже умеет её забарывать:
Как видно, первые три символа обрезаются и производится проверка их на соответствие указанному в спецификации невалидному коду и в этом случае вырезается всё до следующего символа перевода строки.
@sourceURL и displayName в действии: eval и анонимные функции
Эти два соглашения хотя пока и не входят в спецификацию Source Map, но позволяют серьёзно упростить работу с eval и анонимными функциями.
Первый хелпер очень похож на свойство //@ sourceMappingURL и вообще-то в спецификации (V3) упоминается. Включив этот специальный комментарий в код, который потом будет выполнен через eval , можно назвать eval -ы, что даст им более логичные имена при работе в консоли. Ниже приведён простой пример с использованием компилятора CoffeeScript:
Пример — пропущенный через eval код со сгенерированным именем
Другой хелпер позволяет давать имена анонимным функциям при помощи свойства displayName , указанного в контексте этой функции. Попрофилируйте этот пример, чтобы увидеть displayName в действии.
Пример — названия для анонимных функций через displayName (только WebKit NIghtly)
При профилировании будут показываться красивые названия вместо (anonymous function) . Но скорее всего displayName не будет включён в финальную сборку Google Chrome. Хотя надежды ещё остаются, предлагают также переименовать свойство в debugName.
К моменту написания статьи присваивание названий коду, выполненному через eval , поддерживают только Firefox и Google Chrome. Свойство displayName доступно только в ночных сборках Google Chrome.
Вливайтесь
Есть очень длинное обсуждение по поддержке Source Map в CoffeeScript.
У UglifyJS также есть тикет про поддержку Source Map.
Вы можете помочь, если примете участие в обсуждении и выскажете мнение по поводу нужности поддержки Source Map. Чем больше будет инструментов, поддерживающих эту технологию, тем будет проще работать, так что требуйте её поддержки в вашем любимом OpenSource-проекте.
Source Map не идеален
Есть одна неприятность с использованием Source Map для нормальной отладки. Проблема заключается в том, что при попытке проверить значение аргумента или переменной, определённой в контексте исходного файла, контекст ничего не вернёт, т.к. он на самом деле не существует. Нужен какой-то обратный маппинг, чтобы проверить значение соответствующей переменной/аргумента в минифицированном коде и сопоставить его исходному коду.
Проблема решаемая, а при должном внимании к Source Map могут появиться ещё более интересные его применения.
Инструменты и ресурсы
- Nick Fitzgerald сделал форк UglifyJS с поддержкой Source Map
- Paul Irish сделал простое демо Source Map
- Conrad Irwin написал удобный Source Map gem для Ruby-разработчиков
- Что ещё почитать про именование eval и свойство displayName
- Можно посмотреть исходный код Closure Compiler создания Source Map
- Несколько скриншотов и разговор о поддержке GWT source maps
Source Map — мощный инструмент для разработчика. Он позволяет держать production-код максимально сжатым, но при этом позволяет его отлаживать. Так же полезен для начинающих разработчиков, чтобы посмотреть код, написанный опытными разработчиками, чтобы поучиться правильному структурированию и написанию своего кода без необходимости продираться сквозь минифицированный код. Так чего же вы ждёте? Сгенерируйте Source Map для своего проекта!
В этой статье я хотел бы рассказать о такой замечательной штуке, как файлы, отображаемые в память(memory-mapped files, далее — MMF).
Иногда их использование может дать довольно таки существенный прирост производительности по сравнению с обычной буферизированной работой с файлами.
Это механизм, который позволяет отображать файлы на участок памяти. Таким образом, при чтении данных из неё, производится считывание соответствующих байт из файла. С записью аналогично.
«Клёво, конечно, но что это даёт?» — спросите вы. Поясню на примере.
Допустим, перед нами стоит задача обработки большого файла(несколько десятков или даже сотен мегабайт). Казалось бы, задача тривиальна — открываем файл, поблочно копируем из него в память, обрабатываем. Что при этом происходит. Каждый блок копируется во временный кэш, затем из него в нашу память. И так с каждым блоком. Налицо неоптимальное расходование памяти под кэш + куча операций копирования. Что же делать?
Тут-то нам на помощь и приходит механизм MMF. Когда мы обращаемся к памяти, в которую отображен файл, данные загружаются с диска в кэш(если их там ещё нет), затем делается отображение кэша в адресное пространство нашей программы. Если эти данные удаляются — отображение отменяется. Таким образом, мы избавляемся от операции копирования из кэша в буфер. Кроме того, нам не нужно париться по поводу оптимизации работы с диском — всю грязную работу берёт на себя ядро ОС.
В своё время я проводил эксперимент. Замерял с помощью quantify скорость работы программы, которая буферизировано копирует большой файл размером 500 мб в другой файл. И скорость работы программы, которая делает то же, но с помощью MMF. Так вот вторая работает быстрее почти на 30% (в Solaris, в других ОС результат может отличаться). Согласитесь, неплохо.
Чтобы воспользоваться этой возможностью, мы должны сообщить ядру о нашем желании отобразить файл в память. Делается это с помощью функции mmap().
Она возвращает адрес начала участка отображаемой памяти или MAP_FAILED в случае неудачи.
Первый аргумент — желаемый адрес начала участка отбраженной памяти. Не знаю, когда это может пригодится. Передаём 0 — тогда ядро само выберет этот адрес.
len — количество байт, которое нужно отобразить в память.
prot — число, определяющее степень защищённости отображенного участка памяти(только чтение, только запись, исполнение, область недоступна). Обычные значения — PROT_READ, PROT_WRITE (можно кобминировать через ИЛИ). Не буду на этом останавливаться — подробнее читайте в манах. Отмечу лишь, что защищённость памяти не установится ниже, чем права, с которыми открыт файл.
flag — описывает атрибуты области. Обычное значение — MAP_SHARED. По поводу остальных — курите маны. Но замечу, что использование MAP_FIXED понижает переносимость приложения, т.к. его подержка является необязательной в POSIX-системах.
filedes — как вы уже догались — дескриптор файла, который нужно отобразить.
off — смещение отображенного участка от начала файла.
Важное замечание. Если вы планируете использовать MMF для записи в файл, перед маппингом необходимо установить конечный размер файла не меньше, чем размер отображенной памяти! Иначе нарвётесь на SIGBUS.
Ниже приведён пример(честно стырен из замечательной книжки «Unix. Профессиональное программирование») программы, которая копирует файл с использованием MMF.
Вот вобщем-то и всё. Надеюсь, эта статья была полезной. С удовольствием приму конструктивную критику.
Сопоставление файлов — это связь содержимого файла с частью виртуального адресного пространства процесса. Система создает объект сопоставления файлов (также называемый объектом раздела) для поддержания этой связи. Представление файлов — это часть виртуального адресного пространства, которая используется процессом для доступа к содержимому файла. Сопоставление файлов позволяет процессу использовать как случайные входные, так и выходные данные (ввода-вывода) и последовательные операции ввода-вывода. Он также позволяет эффективно работать с большим файлом данных, например базой данных, без необходимости сопоставлять весь файл с памятью. Несколько процессов также могут использовать сопоставленные с памятью файлы для совместного использования данных.
Обрабатывает чтение и запись в представление файла с помощью указателей так же, как и с динамически выделенной памятью. Использование сопоставления файлов повышает эффективность, так как файл находится на диске, но представление файла находится в памяти. Процессы также могут управлять представлением файлов с помощью функции VirtualProtect .
На следующем рисунке показана связь между файлом на диске, объектом сопоставления файлов и представлением файлов.
Файл на диске может быть любым файлом, который требуется сопоставить с памятью, или системным файлом подкачки. Объект сопоставления файлов может состоять из всего или только части файла. Он поддерживается файлом на диске. Это означает, что когда система переключает страницы объекта сопоставления файлов, все изменения, внесенные в объект сопоставления файлов, записываются в файл. При переключении страниц объекта сопоставления файлов они восстанавливаются из файла.
Представление файла может состоять из всего или только части объекта сопоставления файлов. Процесс управляет файлом с помощью представлений файлов. Процесс может создать несколько представлений для объекта сопоставления файлов. Представления файлов, созданные каждым процессом, находятся в виртуальном адресном пространстве этого процесса. Если процессу требуются данные из части файла, отличной от текущего, он может отобразить текущее представление файла, а затем создать новое представление.
Если несколько процессов используют один и тот же объект сопоставления файлов для создания представлений для локального файла, данные согласованы. То есть представления содержат идентичные копии файла на диске. Файл не может находиться на удаленном компьютере, если требуется совместно использовать память между несколькими процессами.
Отображаемый в память файл содержит содержимое файла в виртуальной памяти. Отображение файла в области памяти позволяет приложению, содержащему несколько процессов, взаимодействовать с файлом путем чтения этой памяти и записи в нее. Вы можете использовать управляемый код для доступа к сопоставленным в памяти файлам тем же способом, что и собственные функции Windows. Описание этого механизма можно найти на странице Управление сопоставленными в памяти файлами.
Есть два типа отображенных в память файлов:
Постоянные отображенные в память файлы.
В контексте отображения в память постоянными называются файлы, сопоставленные с исходным файлом на диске. Когда последний процесс завершит работу с таким файлом, все данные сохраняются в исходный файл на диске. Такие отображенные в память файлы удобны для работы с очень большими исходными файлами.
Непостоянные отображенные в память файлы.
Непостоянными называются отображенные в память файлы, которые не сопоставлены с файлом на диске. Когда последний процесс закончит работу с таким файлом, данные не сохраняются, а файл удаляется при сборке мусора. Такие файлы позволяют создать общую область памяти для межпроцессного взаимодействия (IPC).
Программирование с использованием отображенных в память файлов
В следующей таблице содержатся инструкции по использованию объектов и элементов для отображенных в память файлов.
Процессы, представления и управление памятью
Отображенные в память файлы можно сделать общими для нескольких процессов. Процессы могут обращаться к одному отображенному в память файлу по единому имени, которое назначается процессом, создающим этот файл.
Для работы с отображенным в память файлом следует создать представление всего файла или его части. Также вы можете создать несколько представлений для одной части отображенного в память файла, фактически применяя одновременно используемую память. Чтобы два представления оставались согласованными, их нужно создавать из одного отображенного в память файла.
Несколько представлений потребуются еще и в том случае, если размер файла превышает размер пространства логической памяти, доступной приложению для сопоставления с памятью (например, 2 ГБ на 32-разрядном компьютере).
Есть два типа представлений: представление потокового доступа и представление произвольного доступа. Представления потокового доступа удобны для последовательного доступа к файлу (чаще всего это непостоянные файлы и IPC). Представления произвольного доступа предпочтительны для работы с постоянными файлами.
Доступ к сопоставленным с памятью файлам осуществляется через диспетчер памяти операционной системы, который автоматически разделяет файл на несколько страниц и предоставляет их по мере необходимости. Вам не придется самостоятельно осуществлять управление памятью.
На следующем изображении показано, как несколько процессов могут одновременно использовать несколько перекрывающихся представлений для одного отображенного в память файла:
На следующем рисунке показано несколько перекрывающихся представлений для отображенного в память файла:
Читайте также: