новости
статьи
.программирование

четыре способа увеличить скорость выполнения ваших сетевых приложений под Linux

Sockets API позволяет создавать клиентские и серверные приложения, которые могут взаимодействовать по локальной сети или по всему миру через Интернет. То, каким образом вы используете Sockets API (как и любой другой), может либо способствовать высокому быстродействию, либо сдерживать его. В этой статье предложено четыре способа использования Sockets API, которые позволят выжать из приложения максимальное быстродействие и наилучшим образом настроить среду GNU/Linux.

При разработке приложения, работающего с сокетами, первая задача - это обеспечение надежности и удовлетворение необходимым требованиям. Используя четыре совета из этой статьи, вы можете продумать и разработать свое приложение, работающее с сокетами, с наилучшей
производительностью с самого начала. В статье охвачена тема использования Sockets API, приведена пара опций сокета, увеличивающих
быстродействие, и описана настройка GNU/Linux.

совет 1 - минимизируйте время ожидания передачи пакета

Когда вы используете взаимодействие через сокет TCP, данные разбиваются на блоки так, чтобы соответствовать полезной нагрузке TCP для данного соединения. Размер полезной нагрузки определяется несколькими факторами (такими, как максимальный размер пакета на протяжении пути), но они становятся известны во время установления соединения. Смысл достижения лучшего быстродействия заключается в том, чтобы заполнить каждый пакет насколько возможно доступными данными. Если данных для заполнения полезной нагрузки (также известной как maximum segment size (максимальный размер сегмента) или MSS) недостаточно, TCP применяет алгоритм Нагля, чтобы автоматически соединять маленькие буферы в один сегмент. Это позволяет увеличить производительность приложения и уменьшить общую нагрузку сети путем минимизации количества отправляемых маленьких пакетов. Алгоритм Джона Нагля хорошо минимизирует количество маленьких пакетов, соединяя их в пакеты большего размера, но иногда хочется иметь возможность отправлять маленькие пакеты. Простым примером может служить приложение telnet, которое позволяет пользователю взаимодействовать с удаленной системой, обычно через shell. Было бы очень нежелательно, прежде чем отправить пакет, заполнять сегмент напечатанными символами. Другой пример - это протокол HTTP. Клиентский браузер делает маленький запрос (HTTP-сообщение с запросом), приводящий к гораздо большему ответу со стороны веб-сервера (веб-страница).

Решение. Первое, что нужно рассмотреть, удовлетворяет ли алгоритм Нагля потребностям. Из-за того, что он объединяет данные, стараясь создать целый сегмент TCP, создается некоторое время ожидания. Но при этом минимизируется количество пакетов, отправляемых по сети, и таким образом минимизируется ее перегрузка.

Но в случаях, когда вам необходимо минимизировать это время ожидания передачи, решение предлагает Sockets API. Для отключения алгоритма Нагля вы можете установить опцию сокета TCP_NODELAY:

int sock, flag, ret;

/* Создание нового потокового сокета */
sock = socket( AF_INET, SOCK_STREAM, 0 );

/* Отключение алгоритма Нагля (TCP No Delay) */
flag = 1;

ret = setsockopt( sock, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(flag) );

if (ret == -1) {
printf("Couldn't setsockopt(TCP_NODELAY)\n");
exit( EXIT_FAILURE );
}


совет 2 - минимизируйте косвенные затраты системного вызова

Каждый раз при чтении или записи данных в сокет вы используете системный вызов. Этот вызов (например, read или write) пересекает границу между приложением в пользовательском пространстве и ядром. К тому же, прежде чем попасть в ядро, ваш вызов проходит через библиотеку C к стандартной функции в ядре (system_call()). От system_call() он переходит на уровень файловой системы, где ядро определяет, с каким типом устройств вы имеете дело. В итоге вызов попадает на уровень сокетов, где данные считываются или становятся в очередь для передачи в сокет (включая копию данных).

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

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

Решение. Сразу пишите в сокет все доступные данные, вместо того чтобы производить несколько записей. Для чтения передавайте самый большой буфер, который вы можете поддержать, так как ядро попытается заполнить целый буфер, если данных достаточно. Таким способом вы можете минимизировать число вызовов, которые вы делаете, и достичь лучшей общей производительности. При передаче большого количества данных также бывает полезен системный вызов sendfile, но в этом случае должна быть установлена опция сокета TCP_CORK. Для групповой пересылки может использоваться системный вызов writev, а также асинхронный IO API (aio_read, aio_write и т.д.).

совет 3 - настройте окна TCP под произведение полосы пропускания канала на задержку

Производительность TCP зависит от нескольких факторов. Два наиболее значимых -- link bandwidth (пропускная способность соединения) - скорость, с которой пакеты могут быть переданы по сети, и round-trip time (период кругового обращения) или RTT - задержка между отправлением сегмента и получением уведомления от узла. Эти два значения определяют то, что называется Bandwidth Delay Product (BDP).

Зная пропускную способность и RTT, можно вычислить BDP, но что это вам даст? Оказывается, что BDP дает простой способ сосчитать теоретическое оптимальное значение размера буфера сокета TCP (в котором содержится и очередь данных, ожидающих передачи, и очередь данных, ожидающих подтверждения от приложения). Если буфер слишком мал, окно TCP не может полностью открыться, и это ограничивает производительность. Если оно слишком большое, могут пропасть ценные источники памяти. Если вы точно установите буфер, вы можете полностью использовать доступную полосу пропускания. Давайте посмотрим на пример:

BDP = link_bandwidth * RTT

Если ваше приложение взаимодействует через 100-мегабитный канал с RTT 50ms, BDP будет:

100MBps * 0.050 sec / 8 = 0.625MB = 625KB

Обратите внимание: я делю на 8, чтобы перевести переданные биты в байты.

Так что установите свое окно TCP равным BDP или 625 Кб. Но по умолчанию окно для TCP в Linux 2.6 – 110 Кб, что ограничивает пропускную способность соединения до 2.2 Мбит/с, как я тут подсчитал:

пропускная способность = размер окна / RTT
110KB / 0.050 = 2.2MBps

Если вместо этого вы будете использовать окно с посчитанным выше размером, вы получите 12.5 Мбит/с, как показано ниже:

625KB / 0.050 = 12.5MBps

Это совершенно другое дело, и тогда для сокета получается большая пропускная способность. Итак, теперь вы знаете, как сосчитать оптимальный размер буфера сокета. Но как произвести это изменение?

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

Обратите внимание: Хотя размер буфера сокета определяет размер запрошенного окна TCP, TCP также поддерживает окно для защиты от перегрузки внутри этого запрошенного окна. Поэтому из-за перегрузки данный сокет возможно никогда не воспользуется максимально ожидаемым окном.

int ret, sock, sock_buf_size;
sock = socket( AF_INET, SOCK_STREAM, 0 );
sock_buf_size = BDP;
ret = setsockopt( sock, SOL_SOCKET, SO_SNDBUF,
(char *)&sock_buf_size, sizeof(sock_buf_size) );
ret = setsockopt( sock, SOL_SOCKET, SO_RCVBUF,
(char *)&sock_buf_size, sizeof(sock_buf_size) );


Внутри ядра Linux 2.6 размер окна для пересылочного буфера берется в равным тому, что определено пользователем в вызове, но приемный буфер автоматически удваивается. Вы можете подтвердить размер каждого буфера с помощью вызова getsockopt.

Что касается масштабирования окна, TCP изначально поддерживал окно в 64 Кб (16 бит использовались для определения размера окна). С введением масштабируемости окна (в RFC 1323) вы можете использовать 32-битное значение для указания размера окна. Стек TCP/IP в GNU/Linux поддерживает эту опцию (и многие другие).

Дополнительный совет. Ядро Linux также дает возможность автоматически настраивать эти буферы сокета (смотрите tcp_rmem и tcp_wmem ниже, в таблице 1), но эти опции влияют на стек целиком. Если вам нужно настроить окно только для одного соединения или типа соединений, этот механизм не подойдет.

Jumbo frames. Подумайте также об увеличении размера пакета с 1500 до 9000 байт (известном как jumbo frame). В условиях локальной сети это можно сделать, установив максимальный размер передаваемого блока данных (MTU, Maximum Transmit Unit), в результате действительно может увеличиться производительность. Такое решение хорошо для LAN, но может оказаться проблематичным в WAN, потому что промежуточное оборудование, например коммутаторы, может не поддерживать его. MTU можно изменять с помощью утилиты ifconfig.

совет 4 - динамически настраивайте TCP/IP-стек GNU/Linux

Стандартный дистрибутив GNU/Linux пытается провести оптимизацию для большого спектра применений. Это означает, что стандартный дистрибутив может не быть оптимальным для среды, которую вы используете.

Решение. GNU/Linux предоставляет широкий спектр настраиваемых параметров ядра, которые вы можете использовать при динамической настройке операционной системы для своих конкретных целей. Давайте рассмотрим некоторые из наиболее важных опций, которые влияют на быстродействие сокетов.

Настраиваемые параметры ядра существуют внутри виртуальной файловой системы /proc. Каждый файл в ней представляет собой один или более параметров, которые можно прочитать утилитой cat или изменить командой echo. Ниже показано, как запросить и сделать активным настраиваемый параметр (в данном случае активируется перенаправление IP внутри стека TCP/IP).

[root@camus]# cat /proc/sys/net/ipv4/ip_forward

0

[root@camus]# echo "1" > /proc/sys/net/ipv4/ip_forward

[root@camus]# cat /proc/sys/net/ipv4/ip_forward

1

[root@camus]#


Таблица 1. Список из нескольких настраиваемых параметров, которые помогут вам увеличить быстродействие стека TCP/IP в Linux.


Настраиваемый параметрЗначение по умолчаниюОписание параметра
/proc/sys/net/core/rmem_default "110592"Определяет размер окна получения по умолчанию; для больших значений BDP размер должен быть больше.
/proc/sys/net/core/rmem_max "110592"Определяет максимальный размер окна получения; для больших значений BDP размер должен быть больше.
/proc/sys/net/core/wmem_default "110592"Определяет размер окна отправления по умолчанию; для больших значений BDP размер должен быть больше.
/proc/sys/net/core/wmem_max "110592"Определяет максимальный размер окна отправления; для больших значений BDP размер должен быть больше.
/proc/sys/net/ipv4/tcp_window_scaling "1"Активирует масштабирование окна, как определено в RFC 1323; должен быть включен для поддержки окон размером больше, чем 64 Кб.
/proc/sys/net/ipv4/tcp_sack "1"Активирует выборочное подтверждение (selective acknowledgment), которое улучшает производительность, выборочно подтверждая пакеты, полученные вне очереди (в результате отправитель повторно передает только пропущенные сегменты); должен быть включен (для большой области сетевых коммуникаций), но может усилить использование CPU.
/proc/sys/net/ipv4/tcp_fack "1"Включает Forward Acknowledgment (прогнозное подтверждение), которое оперирует с выборочным подтверждением (SACK, Selective Acknowledgment) для уменьшения перегрузки; должен быть включен.
/proc/sys/net/ipv4/tcp_timestamps "1"Активирует вычисление RTT более достоверным способом (смотрите RFC 1323), чем интервал для повторной передачи; должен быть включен для быстродействия.
/proc/sys/net/ipv4/tcp_mem "24576 32768 49152"Определяет, как стек TCP должен использовать память; считается в страницах памяти (как правило 4 Кб). Первое значение - нижняя граница для использования памяти. Второе значение - порог для режима уплотнения памяти, чтобы начать использовать уплотнение для использования буфера. Третье значение - максимальная граница. На этом уровне пакеты могут быть пропущены для уменьшения использования памяти. Увеличьте счет для большого BDP (но помните, он указывается в страницах памяти, не в байтах).
/proc/sys/net/ipv4/tcp_wmem "4096 16384 131072"Определяет использование памяти на каждый сокет для автоматической настройки. Первое значение - минимальное число байт, выделенных для буфера отправления сокета. Второе значение - значение по умолчанию (переопределенное wmem_default), до которого размер буфера может расти при несильной загрузке системы. Третье значение - максимальное пространство буфера отправления (переопределенное wmem_max).
/proc/sys/net/ipv4/tcp_rmem "4096 87380 174760"То же, что и tcp_wmem, за исключением того, что ссылается на буферы получения для автоматической настройки.
/proc/sys/net/ipv4/tcp_low_latency "0"Разрешает стеку TCP/IP отдавать предпочтение низкому времени ожидания перед более высокой пропускной способностью; должен быть отключен.
/proc/sys/net/ipv4/tcp_westwood "0"Активирует алгоритм контроля перегрузки со стороны отправителя, который поддерживает оценочные значения пропускной способности и старается оптимизировать полное использование полосы пропускания; должен быть включен для соединений WAN. Этот параметр также полезен для беспроводных интерфейсов, так как потери пакета могут быть вызваны не перегрузкой.
/proc/sys/net/ipv4/tcp_bic "1"Активирует Binary Increase Congestion (контроль насыщения с бинарным увеличением) для быстрых протяженных сетей; позволяет лучше использовать ссылки, действующие на гигабитных скоростях; должен быть включен для соединений WAN.


инструментарий GNU/Linux

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

Программы GNU/Linux, которые обычно находятся в дистрибутиве (должны быть вам хорошо знакомы):

- ping - чаще всего используется для проверки доступности хоста, но также может применяться для идентификации RTT для вычисления Bandwidth Delay Product;

- traceroute - показывает путь прохождения пакетов между хостами через несколько маршрутизаторов и шлюзов, определяя время ожидания при каждой пересылке;

- netstat - определяет различные статистические данные о сетевых подсистемах, протоколах и соединениях;

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

Полезные программы анализа производительности, обычно не входящие в дистрибутив GNU/Linux:

- netlog - предоставляет приложению инструментарий для измерения производительности сети;

- nettimer - генерирует метрику для критического значения пропускной способности канала; может использоваться для автоматической настройки протокола;

- Ethereal – функционал примерно тот же, что и у tcpump, но с графическим интерфейсом;

- iperf - измеряет производительность сети для TCP и UDP; измеряет максимальную полосу пропускания и сообщает флуктуации времени задержки и потери пакетов;

- trafshow - предоставляет полноэкранную визуализацию сетевого трафика.

заключение

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

При настройке всегда обращайте внимание на природу вашего приложения. Например, создается приложение для LAN или оно будет взаимодействовать с Интернетом? Если оно будет работать только с LAN, увеличение размеров буфера может не дать желаемого результата, а вот включение jumbo frames будет очень даже полезным!

И наконец, всегда проверяйте результаты своей настройки программами типа tcpdump или Ethereal. Изменения, которые вы увидите на уровне пакета, помогут определить, помогла ли настройка, произведенная этими методами.



М. Тим Джонс, инженер-консультант, Emulex
обсудить статью
© сетевые решения
.
.