Zero copy что это
Zero-copy: ликбез
Примечание автора: Данная статья ставит целью ознакомить читателя с современными технологиями ускорения передачи данных внутри приложений и ОС. В статье намеренно опускаются многие технические подробности реализации рассматриваемых процессов. Автор рекомендует читателю ознакомиться со стеком графической подсистемы Linux, а также технологией общей памяти и DRMA в первичной документации и в исходных кодах.
С ростом объёма обрабатываемых данных растут и требования к серверному оборудованию. Современные системы обработки Big Data, аналитические DB и кластерные приложения способны обрабатывать десятки гигабайт данных в секунду, иметь десятки или даже сотни терабайт памяти и оперировать десятками петабайт данных. Очевидно, что оптимизации работы подобных систем могут сэкономить десятки, а то и сотни миллионов долларов их владельцам.
Сегодня мы поговорим о технологиях Zero-copy и RDMA, которые могут существенно ускорить работу почти любого высоконагруженного кластерного приложения или БД, и, в той или мере, используются почти всеми современными системами обработки больших объёмов данных.
Суть технологии Zero-copy заключается в минимизации операций копирования буферов путём использования общей памяти буферов между приложениями, или между приложением и оборудованием, или между ядром и оборудованием. Также вводятся оптимизации и внутри приложения, минимизируя бессмысленные операции копирования буферов одних структур в другие (уменьшается количество буферов, буферы могут переиспользоваться разными структурами).
Классическим примером работы технологии Zero-copy можно назвать передачу данных между приложениями в пределах одного физического компьютера. Чтобы понять разницу между передачей данных с использованием Zero-copy и без.
Допустим, приложению необходимо передать в другое приложение какие-то данные. В классическом случае два приложения должны открыть между собой соединение, инициализировав его через API, и в конце получить два файловых дескриптора: по одному на приложение (не суть важно, какого типа: будь то локальное сетевое соединение, pipe или UNIX-сокет), после чего начинается основная передача данных. В классическом случае протекает она весьма медленно и состоит из следующих этапов:
В итоге для передачи 10Мб данных через буфер 512b может потребоваться более 8000 (!) переключений контекста процессора, не говоря уже о возможной дополнительной обработки данных на уровне ядра, которое тоже требует процессорного времени. Ситуация осложняется тем, что при этих переключениях сбрасывается кэш L1 и управляющие регистры процессора, что ещё больше снижает производительность.
Размер буфера и использование разделяемой памяти (shared memory) при передаче данных
Использование буфера очень большого размера может частично решить ситуацию, однако из-за выделения буфера минимум в трёх местах (ядро, первое и второе приложение) подобная практика может привести к значительному росту потребления оперативной памяти (особенно, если в реальности передавать данные необходимо не только между двумя приложениями). К тому же операции копирования памяти требуют процессорного времени, времени ожидания оперативной памяти (поскольку в L2/L3 кэш такие буферы уже не будут помещаться), что не позволит добиться максимальной оптимизации.
Выходом из этой ситуации является использование разделяемой памяти (shared memory) для буфера передачи данных между приложениями.
В таком случае передача данных между приложениями выглядит следующим образом:
В такой ситуации для передачи 10Мб данных достаточно лишь три раза переключить контекст ядра при использовании общего буфера в 10Мб. При этом (в случае, если кэш процессора достаточно большой) приложения смогут передать данные без ожидания оперативной памяти, т.к. данные осядут в кэше процессора.
Данный пример может быть недостаточно показательным, поэтому давайте возьмём практическую задачу.
Во многих UNIX-like операционных системах для работы с графикой используется протокол X11. В текущим варианте он имеет множество улучшений, связанных именно с Zero-copy.
Итак, возьмём следующий пример (в упрощенном варианте): необходимо вывести в фреймбуфер видеокарты кадр 4K (3840x2160x24bit) видео из youtube в окне.
Данная операция происходит следующим образом (включая вопросы настройки ядра и соединений):
1. Браузер передаёт серверу Xorg данные буфера изображения (через UNIX-сокет, размера меньше, чем передаваемый буфер).
2. Сервер Xorg передаёт данные композитному менеджеру (через UNIX-сокет, размера меньше, чем передаваемый буфер)
3. Композитный менеджер обрабатывает изображение, а также может производить наложение рамок окон, прозрачность, наложение других окон. После чего сформированный буфер передаётся серверу Xorg (через UNIX-сокет, размера меньше, чем передаваемый буфер)
4. Сервер Xorg передаёт кадр изображения в буфер ядра, где формируется изображение для передачи в фреймбуфер карты (через UNIX-сокет, размера меньше, чем передаваемый буфер)
5. Ядро передаёт в фреймбуфер видеокарты данные буфера из оперативной памяти (через вызов функции видеокарты копирования данных из оперативной памяти в теневой буфер видеокарты)
6. Ядро асинхронно ожидает завершения копирования данных в теневой буфер видеокарты (через получение прерывания от видеокарты).
7. Ядро отсылает видеокарте команду смены основного и теневого буферов.
8. Видеокарта обрисовывает кадр на экран.
Как видно из схемы, данные пересылаются множество раз через UNIX сокеты между несколькими приложениями, при этом постоянно переключая контекст ядра. Учитывая, что UNIX-сокеты обычно оперируют максимальным значением 64Kb на буфер, для передачи одного кадра видео 4К (около 23.7Мб) необходимо совершить минимум 1140 переключений контекста процессора. Если произвести расчёт количества переключений с пункта 1 по 4, то их будет как минимум 4560. Также композитный менеджер тратит много тактов процессора на выполнение графических операций с окнами (наложение, обрисовка рамок и др. функционал)
Оптимизация в современном графическом стеке Linux систем
Для уменьшения нагрузки в современных системах был проделан ряд оптимизаций:
Таким образом получается сократить количество переключений контекста с более чем тысячи до десятков, а также уменьшить время обработки изображения с использованием аппаратного ускорения GPU. Также уменьшается нагрузка на процессор и оперативную память.
Подобным образом (общая память между приложениями, между приложениями и устройствами, а также между приложениями на разных хостах) ускоряются многие операции. В следующей статье мы разберем технологию RDMA, а также посмотрим, как задержки сети, переключения контекста процессора, а также сам сетевой стек могут уменьшать производительность в сотни раз.
В компании Anna Systems мы пытаемся максимально использовать современные подходы к вычислениям, поэтому в системах симуляции гидродинамики, электромагнитных полей, нейронных сетей и другого высокопроизводительного и требовательного ПО активно применяются различные технологии снижения задержек: от грамотной оркестрации (предотвращающей вымывания кэшей) и zero-copy до MPI-over-RDMA. Применение данного стека технологий позволяет более эффективно использовать вычислительные машины, и, как следствие, предоставлять нашим клиентам результаты расчётов быстрее и за меньшие деньги. Мы стремимся к максимальной интеграции бизнес-требований с конечным IT-решением.
Русские Блоги
Нулевая копия (Zero-copy) и ее применение в деталях
Предисловие
Вы можете обратиться к английской вики или связанным с ней книгам по теории операционных систем, включенным в статью, таким как «Концепции операционной системы» Абрахама Зильбершаца и «Современные операционные системы» Эндрю С. Таненбаума.
Традиционный метод передачи данных
В эпоху Интернета распространена передача данных (например, файлов) с одного компьютера на другой по сети. Если вы следуете общей идее, используйте язык Java для описания логики отправителя примерно следующим образом.
Конечно, это кажется очень простым. Но если мы перейдем на уровень операционной системы, мы обнаружим, что реальные микрооперации более сложны, в частности, есть следующие шаги:
JVM отправляет системный вызов read () для запуска переключения контекста и переключения из режима пользователя в режим ядра.
Считайте содержимое файла из внешнего хранилища (например, жесткого диска) и сохраните его в буфере адресного пространства ядра через прямой доступ к памяти (DMA).
Данные копируются из буфера ядра в буфер пространства пользователя, возвращается системный вызов read (), и режим ядра переключается обратно в режим пользователя.
JVM отправляет системный вызов write () для запуска переключения контекста и переключения из режима пользователя в режим ядра.
Скопируйте данные из пользовательского буфера в буфер, связанный с целевым сокетом в ядре.
Наконец данные передаются в аппаратный (например, сетевой) буфер через Socket через DMA, системный вызов write () возвращается и переключается из режима ядра обратно в режим пользователя.
Если описание языка кажется немного запутанным, оно будет более понятным из описания временной диаграммы.
Диаграмма последовательности традиционного метода
На данный момент, как вы думаете, есть ли громоздкие вещи, скрытые под простой логикой кода? Это также верно: всего 4 переключения контекста (строго говоря, переключения режимов) произошли в этом процессе, и данные были скопированы туда и обратно 4 раза. Если вы игнорируете детали системного вызова, весь процесс может быть представлен следующими двумя диаграммами.
Блок-схема традиционного метода
Процесс переключения контекста традиционного метода
Метод передачи данных с нулевой копией
«Базовый» механизм нулевого копирования
Из приведенного выше анализа видно, что вторая и третья копии (то есть обратная и обратная копии из пространства ядра в пространство пользователя) не имеют смысла, и данные должны быть напрямую отправлены из буфера ядра в буфер Socket. Механизм нулевого копирования достигает этого. Однако нулевая копия должна напрямую поддерживаться операционной системой, и разные ОС имеют разные способы реализации. Большинство Unix-подобных систем предоставляют системный вызов sendfile (), который описан на его странице руководства:
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.
Ниже приведена схема последовательности передачи данных по механизму нулевого копирования.
Временная диаграмма метода нулевого копирования
Можно видеть, что копирование из пространства ядра в пространство пользователя действительно исключено, поэтому термин «нулевое копирование» фактически говорит с точки зрения ядра, и это не означает, что никакого копирования не произойдет вообще.
Перепишите традиционную логику отправителя примерно следующим образом.
С помощью метода TransferTo () весь процесс может быть представлен следующей диаграммой.
Блок-схема метода нулевой копии
Процесс переключения контекста методом нулевого копирования
Можно видеть, что не только количество копий увеличилось в 3 раза, но и количество переключений контекста также было уменьшено до 2, что намного более эффективно, чем традиционные методы. Но он не в идеальном состоянии, давайте посмотрим, как его оптимизировать.
Поддержка Scatter / Gather
На временной диаграмме «базового» метода нулевого копирования есть цикл «запись данных в целевой буфер сокетов», а на блок-схеме также есть большая стрелка от «Чтение буфера» до «Сокет буфера». Это связано с тем, что в обычном блочном режиме DMA физический адрес источника и целевой физический адрес должны быть непрерывными, поэтому за один раз может быть передан только физически непрерывный блок данных, и прерывание инициируется для каждого переданного блока до завершения передачи, поэтому он должен Копировать данные между двумя буферами.
Метод Scatter / Gather DMA отличается. Связанный список физически прерывистых блочных дескрипторов будет поддерживаться заранее. Дескрипторы содержат начальный адрес и длину данных. При передаче требуется только пройти по связанному списку, передать данные по порядку и инициировать прерывание после того, как все завершено. Эффективность выше, чем у блочного DMA. Другими словами, аппаратное обеспечение может напрямую получать все данные из буфера ядра через Scatter / Gather DMA, не копируя данные из буфера ядра в буфер Socket. Следовательно, приведенная выше временная диаграмма может быть дополнительно упрощена.
Поддержка Scatter / Gather нулевой копии временной диаграммы
Это полный механизм нулевого копирования, это много освежает? Напротив, его блок-схема выглядит следующим образом.
Блок-схема процесса нулевого копирования с поддержкой Scatter / Gather
Поддержка отображения памяти (mmap)
Поддержка mmap нулевой копии временной диаграммы
Конечно, в мире не существует бесплатного ланча, и в вышеуказанном процессе все равно будет 4 переключения контекста. Кроме того, необходимо всегда поддерживать адресное пространство, соответствующее всем данным в быстрой таблице (TLB), до тех пор, пока не завершится мигание, поэтому накладные расходы на устранение ошибок страницы будут больше. При использовании этого механизма необходимо взвешивать эффективность.
MappedByteBuffer предоставляется в инфраструктуре NIO для поддержки mmap. Как и обычно используемый DirectByteBuffer, он выделяет пространство вне кучи. Напротив, HeapByteBuffer выделяет пространство в памяти кучи.
Применение механизма нулевого копирования
Нулевое копирование широко используется во многих средах, а Netty обычно используется в качестве примера для анализа. Но как большой инженер данных, позвольте мне взять в качестве примера Кафку и Спарка, чтобы сказать несколько слов.
Применение в Кафке
При использовании Kafka мы часто задаемся вопросом, почему Kafka может достичь такой огромной пропускной способности? Это неотделимо от многих философий дизайна Кафки, таких как параллелизм разделов, механизм ISR, последовательное написание, кэширование страниц, эффективная сериализация и т. Д., Конечно, нулевая копия является одной из них. Поскольку хранилище сообщений Kafka включает в себя массовое чтение и запись данных, использование нулевого копирования может значительно сократить время ожидания и повысить эффективность.
В Kafka основное транспортное действие определяется интерфейсом TransportLayer. Он просто инкапсулирует SocketChannel, где метод TransferFrom () определяется следующим образом. (Кафка версия 0.10.2.2)
Вызов этого метода находится в методе FileRecords.writeTo (), который используется для записи кэшированных данных, полученных Kafka, в целевой канал с нулевой копией.
Применение в Spark
Хотя Spark является эффективной и активной вычислительной средой, использующей память, она будет переполнена соответствующим образом, когда ей потребуется использовать диск. Механизм нулевого копирования в основном используется в Spark Core для оптимизации логики перезаписи в процессе перемешивания. Поскольку процесс Shuffle предполагает большой объем обмена данными, чем выше эффективность, тем лучше.
В Sort Shuffle с механизмом обхода и на этапе записи в случайном порядке Tungsten Sort Shuffle нулевое копирование используется для быстрого объединения фрагментов перезаписанных файлов, а также имеется специальный элемент конфигурации. spark.file.transferTo Чтобы контролировать, следует ли включить нулевое копирование (по умолчанию, конечно, верно). Возьмем в качестве примера BypassMergeSortShuffleWriter, в конечном итоге он вызывает метод copyFileStreamNIO () в универсальном служебном классе Utils.
Видно, что этот метод используется для обнуления данных из одного FileChannel в другой FileChannel. Управляя параметрами начальной позиции и длины, все перезаписанные файлы могут быть точно сшиты вместе.
Русские Блоги
Анализ технологии Zero Copy в Linux
В этой статье рассматривается LinuxОсновные технологии нулевого копированияИ технология нулевого копированияПрименимые сценарии. Чтобы быстро понять концепцию нулевого копирования, мы представим общий сценарий:
Цитирование
При написании серверной программы (веб-сервер или файловый сервер) загрузка файла является основной функцией. Задача сервера на данный момент:Отправлять файлы на диске хоста сервера без изменений из подключенного сокета, Обычно мы используем следующий код для завершения:
Когда прикладная программа обращается к фрагменту данных, операционная система сначала проверяет, осуществлялся ли доступ к файлу в последнее время и кэшируется ли содержимое файла в буфере ядра. Если это так, операционная система напрямую read Системный вызов предоставлен buf Адрес, скопируйте содержимое буфера ядра в buf Перейти в указанный буфер пользовательского пространства. В противном случае операционная система сначала копирует данные с диска в буфер ядра. В настоящее время этот шаг в основном зависит от DMA Для передачи и последующего копирования содержимого буфера ядра в пользовательский буфер. Далее, write Затем системный вызов копирует содержимое пользовательского буфера в буфер ядра, связанный с сетевым стеком, и, наконец, socket Затем отправьте содержимое буфера ядра на сетевую карту. Сказав так много, лучше четко смотреть на картинку:
Как видно из рисунка выше, всего было создано четыре копии данных, хотя DMA Чтобы обеспечить связь с оборудованием, ЦП по-прежнему необходимо обрабатывать две копии данных.Одновременно произошло несколько переключений контекста в пользовательском режиме и режиме ядра, что, несомненно, увеличило нагрузку на ЦП.
В этом процессе мы не вносили никаких изменений в содержимое файла, поэтому копирование данных туда и обратно между пространством ядра и пространством пользователя, несомненно, является пустой тратой, а нулевое копирование в основном предназначено для решения этой проблемы.
Что такое технология нулевого копирования (zero-copy)? ##
Разрешить передачу данных без прохождения через пользовательское пространство
Используйте mmap
Обычно мы используем следующие решения, чтобы избежать этой проблемы:
Мы должны быть mmap Заблокируйте файл до и разблокируйте после операции:
Использовать sendfile
Начиная с ядра 2.1, Linux представила sendfile Чтобы упростить операцию:
Системный вызов sendfile() Дескриптор, представляющий входной файл in_fd И дескриптор, представляющий выходной файл out_fd Передача содержимого файла (байтов) между ними. Дескриптор out_fd Должен указывать на сокет и in_fd Указанный файл должен быть в порядке mmap оф. Эти ограничения ограничивают sendfile Используйте, чтобы сделать sendfile Вы можете передавать данные только из файла в сокет, но не наоборот. использовать sendfile Это не только уменьшает количество копий данных, но и уменьшает переключение контекста. Передача данных всегда происходит только в kernel space 。
Прежде чем мы позвоним sendfile Когда, что произойдет, если другой процесс усекает файл? Предполагая, что мы не настроили никаких обработчиков сигналов, sendfile Вызов просто возвращает количество байтов, которое он передал до того, как был прерван, errno Будет настроен на успех. Если мы заблокируем файл перед вызовом sendfile, sendfile Поведение остается таким же, как и раньше, и мы также получим сигнал RT_SIGNAL_LEASE.
Пока что мы уменьшили количество копий данных, но есть еще одна копия, которая является копией кеша страниц в кеш сокета. Так можно опустить эту копию?
С помощью оборудования мы можем это сделать. Фактически, прежде чем мы скопировали данные кэша страницы в кеш сокета, нам нужно только передать дескриптор буфера в socket Buffer, а затем передать длину данных, поэтому DMA Контроллер напрямую упаковывает данные в кеш страниц и отправляет их в сеть.
в заключении, sendfile Использование системных вызовов DMA Механизм копирует содержимое файла в буфер ядра, а затем добавляет дескриптор буфера с информацией о местоположении и длине файла в буфер сокета.Этот шаг не копирует данные из ядра в буфер сокета. DMA Механизм скопирует данные из буфера ядра в механизм протокола, избегая последней копии.
Однако для этой функции копирования коллекции требуется поддержка оборудования и драйверов.
Используйте сращивание
В вызове splice используется механизм буфера канала, предложенный Linux, поэтому по крайней мере один дескриптор должен быть конвейером.
Вышеупомянутые технологии нулевого копирования реализуются за счет уменьшения количества копий данных в пространстве пользователя и ядра, но иногда данные должны копироваться между пространством пользователя и пространством ядра. В настоящее время мы можем сосредоточиться только на времени копирования данных в пространстве пользователя и ядра. Linux обычно использует копирование при записи, чтобы уменьшить системные издержки. Этот метод часто называют COW 。
Русские Блоги
Введение в принцип Zero-Copy
Это может быть немного запутанным, чтобы понять теоретически. Я объясню это с практической точки зрения здесь, и развить принцип Zero-Copy шаг за шагом.
очертание
Наша система, будь то система электронной коммерции или официальный сайт, представляет собой интерактивный режим запроса-ответа, но сегодня мы не говорим о режиме http-запроса / ответа. Откройте страницу. Для системы необходимо отобразить статический контент (аналогично изображениям и файлам), сгенерированный системой, пользователю. В этом случае системе необходимо скопировать статический контент с диска в буфер памяти, а затем Этот буфер передается пользователю через сокет, а затем отображается пользователь или статический контент. Это кажется нормальным, но на самом деле это очень неэффективный процесс. Мы абстрагируем эту ситуацию в следующий процесс:
Сначала вызовите read для чтения статического содержимого, здесь предполагается, что это File, read tmp_buf, а затем вызовите write для записи tmp_buf в сокет, как показано на рисунке:
улучшения javaNIO
Технология Zero-Copy устраняет этапы копирования буфера чтения операционной системы в буфер программы и из буфера программы в буфер сокетов и напрямую копирует буфер чтения в буфер сокетов. Метод FileChannal.transferTo () в Java NIO: Такая реализация, эта реализация зависит от базовой реализации sendFile () операционной системы.
Основной вызов метода sendfile
На следующем рисунке показан поток данных после TransferTo ():
На следующем рисунке показано переключение контекста после использования TransferTo ():
Расширенный Zero-Copy
В ядре Linux 2.1 появилась функция sendfile (упомянутая в предыдущем разделе), которая используется для передачи файлов через сокеты.
Эта функция завершает передачу файла через один системный вызов, уменьшая переключение режимов исходного режима чтения / записи. Кроме того, копирование данных сокращается, а подробный процесс отправки файла показан на рисунке:
Для отправки файла через sendfile требуется только один системный вызов. При вызове sendfile:
Первое (через DMA) чтение данных с диска в буфер ядра;
Затем скопируйте буфер ядра в буфер сокетов;
Наконец, скопируйте данные из буфера сокетов в сетевой механизм (механизм протокола) и отправьте его;
Этот процесс является этапом в разделе 2 (подробно).
По сравнению с режимом чтения / записи, sendfile имеет на одну копию меньше. Однако из вышеприведенного процесса можно обнаружить, что нет необходимости копировать данные из буфера ядра в буфер сокетов.
Ядро Linux2.4 улучшило sendfile, как показано на рисунке:
Добавить текущую позицию буфера и смещение в буфере сокета к буферу сокета;
Скопируйте данные буфера ядра непосредственно в механизм протокола в соответствии с положением и смещением в буфере сокета;
После вышеописанного процесса данные переносятся с диска только после 2 копий. Это настоящая нулевая копия (здесь нулевая копия для ядра, а данные для нулевой копии в режиме ядра).
Улучшено ядро Linux2.4. TransferTo () в Java реализует Zero-Copy, как показано ниже:
Существует множество сценариев использования технологии Zero-Copy, таких как Kafka, Netty и т. Д., Которые могут значительно повысить производительность программы.