Шагните в науку и раскройте загадочную «нулевую копию»

Java задняя часть открытый источник Netty

предисловие

Три слова «нулевая копия» наверняка слышали многие из вас.Эта технология используется в различных компонентах с открытым исходным кодом, таких как kafka, RocketMQ, Netty, nginx и других фреймворках с открытым исходным кодом. Итак, сегодня я хочу поделиться с вами некоторыми знаниями о нулевом копировании.

передача данных в компьютер

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

Ранняя стадия:

Разбросанные соединения, серийная работа, программные запросы. На этом этапе ЦП выступает в роли няни и должен считывать данные с интерфейса ввода-вывода, а затем отправлять их в основную память.

Конкретный процесс этого этапа:

  1. CPU активно запускает устройство ввода/вывода
  2. Затем ЦП продолжает спрашивать старое железо устройства ввода-вывода, готовы ли вы.Обратите внимание, что это всегда спрашивает.
  3. Если устройство ввода-вывода сообщает ЦП: я готов. CPU считывает данные с интерфейса ввода/вывода.
  4. Затем ЦП продолжает передавать эти данные в основную память, как курьер.

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

Интерфейсный модуль и каскад прямого доступа к памяти

Эту часть мы также обсудим позже.

интерфейсный модуль

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

Это также структура обмена данными наших домашних компьютеров или некоторых небольших калькуляторов.

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

  1. CPU активно запускает устройство ввода/вывода.
  2. После запуска ЦП нет необходимости снова запрашивать ввод-вывод, и он начинает делать другие вещи, похожие на асинхронность.
  3. После того, как ввод-вывод готов, сообщите ЦП, что я готов, через прерывание шины.
  4. ЦП считывает данные и передает их в оперативную память.

DMA

Хотя вышеописанный метод улучшает коэффициент использования ЦП, ЦП все еще занят во время прерывания.Для дальнейшего решения проблемы занятости ЦП вводится метод DMA.В методе DMA основная память и устройство ввода/вывода Существует канал передачи данных, поэтому при обмене данными между оперативной памятью и устройствами ввода-вывода нет необходимости снова прерывать ЦП.

Вообще говоря, нам нужно обратить внимание только на DMA и прерывания.Ниже приведены некоторые из них, подходящие для больших компьютеров.Вот краткое введение:

сцена с канальной структурой

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

  • Каждое устройство ввода-вывода конфигурируется с выделенным интерфейсом DMA, что не только увеличивает стоимость оборудования, но и решает проблему конфликтов доступа DMA и ЦП, что очень усложняет управление.
  • Процессору необходимо управлять многочисленными DMA-интерфейсами, что также влияет на эффективность работы.

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

Этапы с обработчиками ввода/вывода

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

резюме

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

копия данных

Давайте сначала представим наши сегодняшние потребности.На диске есть файл, и теперь его нужно передать по сети. Если да, то что делать? Я считаю, что благодаря некоторым из приведенных выше вступлений у вас должны появиться некоторые идеи.

традиционная копия

Если мы реализуем его в коде Java, у нас будет следующая реализация: Ссылка на псевдокод выглядит следующим образом:

public static void main(String[] args) {
        Socket  socket = null;
        File file = new File("test.file");
        byte[] b = new byte[(int) file.length()];
       
        try {
            InputStream in = new FileInputStream(file);
            readFully(in, b);
            socket.getOutputStream().write(b);
        } catch (Exception e) {
            
        }
    }

    private static boolean readFully(InputStream in, byte[] b) {
        int size = b.length;
        int offset = 0;
        int len;
        for (; size > 0;) {
            try {
                len = in.read(b, offset, size);
                if (len == -1) {
                    return false;
                }
                offset += len;
                size -= len;
            } catch (Exception ex) {
                return false;
            }
        }
        return true;
    }

Это наш традиционный метод копирования.Конкретная схема потока данных выглядит следующим образом, PS: Здесь не рассматривается, что данные в куче нужно копировать в прямую память при передаче данных в Java.

Видно, что нашему менеджеру нужно пройти четыре этапа, 2 DMA, 2 прерывания ЦП, всего четыре копии, четыре переключения контекста, и он будет занимать два ЦП.

  1. ЦП отправляет инструкции DMA устройства ввода-вывода, а DMA передает данные с нашего диска в буфер ядра в пространстве ядра.
  2. Второй этап запускает прерывание ЦП, и ЦП начинает копировать данные из буфера ядра в кеш нашего приложения.
  3. ЦП копирует данные из кеша приложения в буфер сокета в ядре.
  4. DMA копирует данные из буфера сокета в буфер сетевой карты.

Достоинства: низкая стоимость разработки, подходит для некоторых невысоких требований к производительности, например некоторых систем управления, думаю должно хватить

Недостатки: несколько переключений контекста, использование нескольких процессоров и низкая производительность.

sendFile реализует нулевое копирование

Как насчет нулевой копии выше? Позиционирование в вики: обычно относится к тому, что когда компьютер отправляет файл по сети, ему не нужно копировать содержимое файла в пространство пользователя (User Space) и напрямую передает его в сеть в пространстве ядра ( пространство ядра).

В java NIO FileChannal.transferTo() реализует sendFile операционной системы, мы можем выполнить вышеуказанные требования с помощью следующего псевдокода:

public static void main(String[] args) {
        SocketChannel socketChannel = SocketChannel.open();
        FileChannel fileChannel = new FileInputStream("test").getChannel();
        fileChannel.transferTo(0,fileChannel.size(),socketChannel);

    }

Мы заменили наш сокет и fileInputStream выше каналом в java.nio, тем самым завершив нашу нулевую копию.

Вышеупомянутый конкретный процесс выглядит следующим образом:

  1. Когда вызывается sendfie(), ЦП отправляет инструкцию, называемую DMA, для копирования данных с диска в буфер ядра.
  2. После завершения копирования DMA выдается запрос на прерывание, а ЦП копируется и копируется в буфер сокета. Вызов sendFile завершается и возвращается. 3. DMA копирует буфер сокета в буфер сетевой карты.

Вы можете видеть, что мы вообще не копировали данные в кеш нашего приложения, поэтому этот подход является нулевым копированием. Тем не менее, этот метод по-прежнему очень болезненный, хотя он сводится всего к трем копиям данных, он по-прежнему требует от ЦП прерывания копирования данных. Зачем? Потому что DMA должен знать адрес памяти, прежде чем я смогу отправить данные. Поэтому в ядро ​​Linux 2.4 были внесены улучшения, и соответствующая информация описания данных (адрес памяти, смещение) в буфере ядра записывается в соответствующий буфер сокета. В итоге формируется следующий процесс:

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

Подобные коды есть в Netty, RocketMQ и kafka в сторонних фреймворках с открытым исходным кодом, если интересно, можете поискать самостоятельно.

сопоставление mmap

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

Полагаясь на MappedByteBuffer для сопоставления mmap в Java, конкретный MappedByteBuffer может обратиться к этой статье за ​​подробностями:Woohoo.Краткое описание.com/afraid/send 90866 low cost….

Наконец

С тех пор тайна нулевого копирования также была раскрыта.Нулевое копирование предназначено только для уменьшения использования ЦП и позволяет ЦП выполнять более реальные бизнес-задачи. Благодаря этой статье вы можете увидеть, как Нетти делает нулевое копирование, я думаю, это произведет более глубокое впечатление.

Наконец, эта статья была включена в JGrowing, всеобъемлющий и отличный маршрут изучения Java, совместно созданный сообществом.Если вы хотите участвовать в обслуживании проектов с открытым исходным кодом, вы можете создать его вместе.Адрес github:GitHub.com/Java растет…Пожалуйста, дайте мне маленькую звезду.

Если вы считаете, что эта статья полезна для вас, или если у вас есть какие-либо вопросы и вы хотите предоставить бесплатный VIP-сервис 1 на 1, вы можете подписаться на мой официальный аккаунт Ваше внимание и пересылка - самая большая поддержка для меня, O(∩_∩)O :