Эффективный перенос данных с помощью zero copy

Zero copy, zero overhead

В статье рассказывается о том, как с помощью технологии zero copy можно улучшить производительность Java™-приложений с интенсивным вводом-выводом, работающих на платформах Linux® и UNIX®. Zero copy позволяет избежать лишнего копирования данных между промежуточными буферами и уменьшить число контекстных переключений между пространством пользователя и пространством ядра.

Сатхискумар Паланьяппан, инженер по системному программному обеспечению, IBM

Сатхискумар Паланьяппан (Sathiskumar Palaniappan) — системный программист в Центре Java-технологий IBM India Labs.



Прамод Нагараджа, младший инженер по системному программному обеспечению, IBM

Прамод Нагараджа (Pramod B. Nagaraja) — инженер-программист в Центре Java-технологий IBM India Labs.



17.02.2009

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

Каждый раз, когда данные пересекают границу пользователь-ядро, их необходимо копировать; при этом затрачиваются ресурсы процессора и памяти. К счастью, этого копирования можно избежать с помощью технологии с соответствующим названием— zero copy. Приложения, использующие zero copy, запрашивают ядро о копировании данных прямо с диска в сокет, не затрагивая приложения. Zero copy значительно улучшает производительность приложений и уменьшает число контекстных переключений между режимами пользователя и ядра.

Библиотеки классов Java поддерживают zero copy в системах Linux и UNIX с помощью метода transferTo() из java.nio.channels.FileChannel. Метод transferTo() позволяет передать информацию прямо из канала, в котором они вызваны, в другой канал с поддержкой записи без необходимости пропускать данные через приложение. В данной статье сначала наглядно описываются системные издержки, возникающие при простой передаче файлов с помощью традиционной семантики копирования, а затем показывается, как с помощью технологии zero-copy, использующей transferTo() повышается производительность.

Передача данных: традиционный метод

Рассмотрим сценарий чтения из файла и передачу данных в другую программу по сети. (Этот сценарий описывает поведение многих серверных приложений, включая Web-приложения, выдающие статическое содержимое, FTP серверов, почтовые серверы и т.д.) Суть операции заключается в двух вызовах, показанных в Листинге 1 (ссылка на полный пример кода приведена в разделе Загрузка):

Листинг 1. Копирование байтов из файла в сокет
File.read(fileDesc, buf, len);
Socket.send(socket, buf, len);

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

Рисунок 1. Традиционный подход к копированию данных
Традиционный подход к копированию данных

На рисунке 2 показано переключение контекста:

Рисунок 2. Традиционное переключение контекста
Традиционное переключение контекста

Этапы операции:

  1. Вызов read() производит переключение контекста (см. рисунок 2) из режима пользователя в режим ядра. Внутри запускается sys_read() (или его эквивалент), чтобы прочитать данные из файла. Первое копирование (см. Figure 1) совершается с помощью механизма прямого доступа в память (DMA), который считывает содержимое файла с диска и сохраняет его в буфере пространства адресов ядра.
  2. Требуемое количество данных копируется из буфера чтения в пользовательский буфер, и возвращается вызов read() . Ответ из вызова производит еще одно переключение контекста из режима ядра обратно в пользовательский режим. Теперь данные находятся в буфере пользовательского пространства адресов.
  3. При вызове функции для работы с сокетом send() контекст переключается из режима пользователя в режим ядра. Производится третье копирование для повторного помещения данных в буфер пространства адресов ядра. На этот раз данные помещаются в другой буфер, связанный с сокетом назначения.
  4. Send() возвращает системный вызов, инициируя четвертое переключение контекста. Независимо и асинхронно происходит четвертое копирование, так как механизм DMA передает данные из буфера ядра в механизм протокола.

Использование промежуточного буфера ядра (вместо прямой передачи данных в пользовательский буфер) кажется неэффективным. Но промежуточные буфера ядра были введены в процессы, чтобы улучшить производительность. Применение промежуточного буфера на стороне чтения позволяет буферу ядра действовать как "кэш опережающего считывания", когда приложение не запрашивало столько данных, сколько содержится в буфере ядра. Это существенно улучшает производительность, когда количество требуемых данных меньше размера буфера ядра. Промежуточный буфер на стороне записи позволяет завершить запись асинхронно.

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

Метод Zero copy улучшает производительность, устраняя необходимость лишнего копирования данных.


Передача данных: метод zero-copy

Если снова просмотреть традиционный сценарий, можно заметить, что второе и третье копирование на самом деле не нужны. Приложение не делает ничего, кроме кэширования данных и передачи их обратно в буфер. Вместо этого данные могут быть переданы прямо из буфера чтения в буфер сокета. Метод transferTo() позволяет сделать именно так. В листинге 2 показана сигнатура метода transferTo():

Листинг 2. Метод transferTo() method
public void transferTo(long position, long count, WritableByteChannel target);

Метод transferTo() передает данные из файлового канала в записываемый байтовый канал. Внутренне он опирается на поддержку zero copy на уровне операционной системы; в UNIX и разновидностях Linux этот вызов направляется к системному вызову sendfile() , показанному в листинге 3, который передает данные от одного файлового дескриптора другому:

Листинг 3. Системный вызов sendfile()
#include <sys/socket.h>
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);

Действие вызовов file.read() и socket.send() calls in Listing 1 в листинге 1 может быть заменено простым вызовом transferTo() , как показано в листинге 4:

Листинг 4. Применение transferTo() для копирования данных из файла на диске в сокет
transferTo(position, count, writableChannel);

На рисунке 3 показан путь данных при использовании метода transferTo() :

Рисунок 3. Копирование данных с помощью transferTo()
Копирование данных с помощью transferTo()

На рисунке 4 показаны переключения контекста при использовании метода transferTo():

Рисунок 4. Переключения контекста при использовании transferTo()
Переключения контекста при использовании transferTo()

Этапы работы transferTo() , показанные в листинге 4 :

  1. Метод transferTo() инициирует ставляет скопированиеться содержимогое файла в буфер чтения с помощью механизма DMA. Затем данные копируются ядром в буфер ядра, связанный с выходным сокетом.
  2. В ходе третьего копирования механизм DMA передает данные из буфера ядра в механизм протокола.

Здесь заметно улучшение: мы уменьшили число контекстных переключений с четырех до двух и уменьшили число копирований данных с четырех до трех (при этом только одно из них использует процессор). Но цель пока ещё не достигнута. Мы можем дальше уменьшить дублирование данных ядром, если используемая плата сетевого интерфейса поддерживает операции сбора. В ядрах Linux 2.4 и более новых дескриптор буфера сокета был модифицирован в соответствии с этим требованием. Этот метод не только уменьшает число множественных переключений контекста, но и избавляет от повторного копирования данных, использующего процессор. С пользовательской стороны все остается неизменным, но внутренняя механика меняется:

  1. При вызове метода transferTo() содержимое файла копируется в буфер ядра с помощью механизма DMA.
  2. Данные в буфер сокета не копируются. Вместо этого в буфер сокета отправляются только дескрипторы с информацией о местоположении и размере данных. Механизм DMA передает данные прямо из буфера ядра в механизм протокола, избавляя, таким образом, от последнего копирования с участием процессора.

На рисунке 5 показано копирование данных с помощью transferTo() с операцией сбора:

Рисунок 5. Копирование данных с применением transferTo() и операций сбора
Копирование данных с применением transferTo() и операций сбора

Создание файлового сервера

Теперь давайте применим zero copy на практике, используя вышеупомянутый пример передачи файла между клиентом и сервером (код примера см. в Загрузках ). TraditionalClient.java и TraditionalServer.java основаны на традиционной семантике копирования, использующей File.read() и Socket.send(). TraditionalServer.java— программа-сервер, которая слушает определенный порт в ожидании подсоединения клиента и затем считывает 4K байт данных за один раз из сокета. TraditionalClient.java подключается к серверу, читает (используя File.read()) ) 4K байт данных из файла и отправляет (используя socket.send()) содержимое на сервер через сокет.

Аналогично, TransferToServer.java и TransferToClient.java выполняют те же функции, только используют метод transferTo() (и в свою очередь системный вызов sendfile() ), чтобы передать файл от сервера клиенту.

Сравнение производительности

Мы запускали программу-пример в системе Linux с ядром 2.6 и измерили скорость выполнения в миллисекундах как для традиционного метода, так и для метода transferTo() для различных размеров файла. Результаты показаны в таблице 1:

Таблица 1. Сравнение производительности: традиционный метод против zero copy
Размер файлаОбычная передача файла (мс)transferTo (мс)
7МБ15645
21МБ337128
63МБ843387
98МБ1320617
200МБ21241150
350МБ36311762
700МБ134984422
1ГБ183998537

Как видим, API transferTo() уменьшает время приблизительно на 65% в сравнении с традиционным методом. Это потенциал для значительного увеличения производительности приложений, которые совершают большую работу с копированием данных из одного канала ввода-вывода в другой, в частности, Web-серверов.


Заключение

Мы продемонстрировали преимущества использования transferTo() по сравнению с чтением из одного канала и записью в другой. Промежуточное буферное копирование — даже скрытое в ядре — может быть заметно ресурсоёмким. Технология zero-copy обеспечивает существенное улучшение производительности в приложениях, связанных с интенсивным копированием данных между каналами.


Загрузка

ОписаниеИмяРазмер
Sample programs for this articlej-zerocopy.zip3KB

Ресурсы

Научиться

Обсудить

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Технология Java, Linux
ArticleID=369971
ArticleTitle=Эффективный перенос данных с помощью zero copy
publish-date=02172009