Рис. 6.45. Быстрый путь от отправителя до получателя показан жирной линией. Шаги обработки вдоль этого пути показаны затененными прямоугольниками
Чтобы увидеть, как работает этот принцип на практике, рассмотрим случай TCP/ IP. На рис. 6.46, а изображен TCP-заголовок. Поля, одинаковые для заголовков последующих сегментов в однонаправленном потоке, затенены. Все, что нужно сделать передающей транспортной подсистеме, это скопировать пять слов заголовка-прототипа в выходной буфер, заполнить поле порядкового номера (скопировав одно слово), сосчитать контрольную сумму и увеличить на единицу значение переменной, хранящей текущий порядковый номер. Затем она может передать заголовок и данные специальной IP-процедуре, предназначенной для отправки стандартного, максимального сегмента. Затем IP-процедура копирует свой заголовок-прототип из пяти слов (рис. 6.46, б) в буфер, заполняет поле Идентификатор и вычисляет контрольную сумму заголовка. Теперь пакет готов к передаче.
Рассмотрим теперь быстрый путь обработки пакета получающей стороной на рис. 6.45. Первый шаг состоит в нахождении для пришедшего сегмента записи соединения. В протоколе TCP запись соединения может храниться в хэш-таблице, ключом к которой является какая-нибудь простая функция двух IP-адресов и двух портов. После обнаружения записи соединения следует проверить адреса и номера портов, чтобы убедиться, что найдена верная запись.
Процесс поиска нужной записи можно дополнительно ускорить, если установить указатель на последнюю использовавшуюся запись и сначала проверить ее. Кларк с соавторами книги, вышедшей в 1989 году, исследовал этот вопрос и пришел к выводу, что в этом случае доля успешных обращений превысит 90 %.
Рис. 6.46. TCP-заголовок (а); IP-заголовок (б). В обоих случаях затененные поля взяты
у прототипа без изменений
Затем сегмент проверяется на адекватность: соединение в состоянии ESTABLISHED, ни одна сторона не пытается его разорвать, сегмент является полным, специальные флаги не установлены, и порядковый номер соответствует ожидающемуся. Программа, выполняющая всю эту проверку, состоит из довольно значительного количества инструкций. Если все эти условия удовлетворяются, вызывается специальная процедура быстрого пути TCP-уровня.
Процедура быстрого пути обновляет запись соединения и копирует данные пользователю. Во время копирования она одновременно подсчитывает контрольную сумму, что уменьшает количество циклов обработки данных. Если контрольная сумма верна, запись соединения обновляется и отправляется подтверждение. Метод, реализованный в виде отдельной процедуры, сначала производящей быструю проверку заголовка, чтобы убедиться, что заголовок именно такой, какой ожидается, называется предсказанием заголовков (header prediction). Он применяется в большинстве реализаций протокола TCP. Использование этого метода оптимизации вместе с остальными, описанными в данном разделе, позволяет протоколу TCP достичь 90 % от скорости локального копирования из памяти в память, при условии, что сама сеть достаточно быстрая.
Две другие области, в которых возможны основные улучшения производительности, — это управление буферами и управление таймерами. Как уже было сказано, при управлении буферами следует пытаться избегать излишнего копирования. При управлении таймерами следует учитывать, что они почти никогда не срабатывают. Они предназначены для обработки нестандартного случая потерь сегментов, но большинство сегментов и их подтверждений прибывают успешно, и поэтому истечение периода ожидания является редким событием.
В программе таймеры обычно реализуются в виде связанного списка таймеров, отсортированного по времени срабатывания. Начальный элемент списка содержит счетчик, хранящий число минимальных интервалов времени (тиков — ticks), оставшихся до истечения периода ожидания. В каждом последующем элементе списка содержится счетчик, указывающий, через сколько тиков относительно предыдущего элемента списка истечет время ожидания данного таймера. Например, если таймеры сработают через 3, 10 и 12 тиков, счетчики списка будут содержать значения 3, 7 и 2 соответственно.
При каждом тике часов счетчик начального элемента списка уменьшается на единицу. Когда значение счетчика достигает нуля, обрабатывается связанное с этим таймером событие, а начальным элементом списка становится следующий элемент. При такой схеме добавление и удаление таймера является операцией, требующей затрат ресурсов, при этом время выполнения операции пропорционально длине списка.
Если максимальное значение таймера ограничено и известно заранее, то может быть применен более эффективный метод. Здесь можно использовать массив, называющийся циклическим расписанием (рис. 6.47) (timing wheel). Каждое гнездо соответствует одному тику. Текущее время, показанное на рисунке, — T = 4. Таймеры должны сработать через 3, 10 и 12 тиков. Если новый таймер должен сработать через 7 тиков, то все, что нужно сделать, — задать значение указателя в гнезде 11 на новый список таймеров. Аналогично, если таймер, установленный на время T + 10, должен быть отключен, нужно всего лишь обнулить запись в гнезде 14. Обратите внимание на то, что массив на рис. 6.47 не может поддерживать таймеры за пределами T + 15.
Рис. 6.47. Циклическое расписание
При тике часов указатель текущего времени перемещается по циклическому расписанию вперед на одно гнездо. Если гнездо, на которое он указывает, не нулевое, обрабатываются все таймеры списка, на который указывает гнездо. Многочисленные варианты основной идеи обсуждаются в (Varghese и Lauck, 1989).
6.6.5. Сжатие заголовков
Мы уже слишком долго говорим о быстрых сетях. Есть и другие темы. Давайте перейдем к вопросу о производительности в беспроводных сетях и сетях других типов, где пропускная способность ограничена. Если уменьшить издержки, возникающие за счет программного обеспечения, мобильные компьютеры будут работать эффективнее, но это не поможет улучшить производительность в тех случаях, когда узким местом являются сетевые каналы.
Чтобы эффективно использовать пропускную способность, заголовок протокола и полезная нагрузка должны занимать как можно меньше бит. Для полезной нагрузки этого можно достичь с помощью компактного кодирования информации — например, изображения лучше передавать в формате JPEG, а не в растровых форматах, или же в форматах документов со сжатием, таких как PDF. Также для этого необходимы механизмы кэширования прикладного уровня (такие как веб-кэш), уменьшающие число передач.
А что известно про заголовки протоколов? В беспроводных сетях на канальном уровне заголовки обычно занимают мало места, так как они изначально рассчитаны на низкую пропускную способность. Так, в 802.16 вместо длинных адресов используются короткие идентификаторы соединения. Однако протоколы более высоких уровней (такие как IP, TCP и UDP) универсальны для всех типов сетей, поэтому в них не используются компактные заголовки. На самом деле потоковая обработка, уменьшающая накладные расходы на программное обеспечение, часто приводит к еще большему увеличению длины заголовка (например, в IPv6 используются более разреженные заголовки, чем в IPv4).
Заголовки более высоких уровней могут сильно влиять на производительность. Рассмотрим, к примеру, процесс передачи VoIP-данных через IP, UDP и RTP. Этим протоколам требуется заголовок длиной 40 байт (20 байт для IPv4, 8 — для UDP, 12 — для RTP). В случае IPv6 ситуация еще хуже: 60 байт, включая 40-байтный заголовок IPv6. Эти заголовки могут становиться еще длиннее, потребляя более половины пропускной способности.
Чтобы уменьшить пропускную способность, потребляемую заголовками высоких уровней, используется сжатие заголовков (header compression). При этом используются не универсальные методы, а специально разработанные схемы. Дело в том, что по отдельности заголовки сжимаются плохо (поскольку они и так короткие), а для декомпрессии требуется, чтобы начальные данные пришли на место назначения. Последнее может не выполняться из-за потери пакетов.