MAATRIX / Блог / Что сетевая карта считает за процессор и что происходит, когда она это перестаёт делать

Что сетевая карта считает за процессор и что происходит, когда она это перестаёт делать

MAATRIX

Сервер вдруг начинает есть заметно больше CPU, чем обычно, и единственное, что изменилось — выросла сетевая нагрузка. Ни новых процессов, ни утечек памяти, ни изменений в коде — просто трафика стало больше, и вместе с ним поднялся %sys в top. Первая мысль обычно про приложение, но часто дело в куда более скучной вещи: сетевая карта перестала делать часть своей работы сама, и эта работа тихо переехала на процессор. Разберёмся, что именно карта умеет считать вместо CPU, как это называется и почему иногда перестаёт работать.

Что сетевая карта делает, кроме "передать пакет"

Наивная модель сетевой карты — это провод с разъёмом: пакет пришёл с шины PCIe, карта его отправила в эфир или в кабель, и всё. На деле современный сетевой контроллер — это отдельный маленький процессор со своей логикой, буферами и очередями, и часть работы по подготовке пакетов он берёт на себя, а не просто передаёт их дальше. Это называется offloading — перенос вычислений с CPU на специализированное железо, которое умеет делать конкретную операцию быстрее и без участия основного процессора.

Идея не нова и не уникальна для сети: то же самое происходит с AES-NI для шифрования (см. материал про то, почему шифрование перестало нагружать CPU) или с аппаратным RAID-контроллером, который считает чётность сам. Сетевая карта берёт на себя рутинные, предсказуемые операции над пакетами, оставляя CPU то, что действительно требует его внимания — логику приложения.

Список того, что умеет offloading у современных карт, довольно большой: контрольные суммы (checksum offload), сегментация крупных пакетов (TSO/GSO), сборка входящих сегментов (GRO/LRO), распределение потоков по очередям и ядрам (RSS), а на специализированном железе — ещё и терминация TLS, обработка VXLAN-инкапсуляции, фильтрация по правилам. Остановимся на двух самых массовых и чаще всего "теряемых" механизмах: контрольных суммах и сегментации/сборке пакетов — именно их отключение даёт наиболее непропорциональный рост нагрузки на CPU.

Контрольные суммы: чек, который раньше считал процессор

У каждого пакета TCP/IP есть контрольная сумма — число, которое позволяет получателю понять, не побился ли пакет в пути. Считается она по достаточно простому, но объёмному алгоритму: нужно пройтись по всем байтам заголовка и (в случае TCP/UDP) по всем байтам данных, суммируя их по определённому правилу. Для одного маленького пакета это копейки процессорного времени. Для сервера, который прогоняет через себя гигабиты трафика в секунду — это миллионы таких операций, и если считать их на CPU программно, набегает ощутимая доля процессорного времени, просто на арифметику, которая по сути не требует "интеллекта" — только скорости.

Checksum offload решает это просто: сетевая карта умеет считать эту сумму сама, на своей логике, прямо во время передачи данных по шине, без отдельного прохода по памяти со стороны CPU. Для исходящих пакетов (tx-checksumming) ядро формирует пакет с заглушкой вместо суммы, помечает его флагом "посчитай сам" — и карта досчитывает сумму на лету при отправке. Для входящих (rx-checksumming) карта проверяет сумму пакета ещё до того, как он попадёт в память хоста, и если сумма не сходится — может отбросить пакет сразу, не тратя на него цикл ядра вообще.

Проверить состояние этой опции можно командой ethtool:

ethtool -k eth0 | grep checksum

Типичный вывод на нормально работающей карте:

rx-checksumming: on
tx-checksumming: on
tx-checksum-ip-generic: on

Если вместо on вы видите off, а рядом в скобках стоит [fixed] — это значит, что опцию нельзя включить программно: либо драйвер её не поддерживает, либо она аппаратно отсутствует на этой карте или в этой конфигурации виртуализации. Если скобок нет — опцию можно попробовать включить вручную командой ethtool -K eth0 rx-checksumming on.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

TSO и GRO: укрупнение и разбор сегментов на лету

Второй большой источник экономии CPU — это работа с размером сегментов. TCP-стек в ядре умеет готовить для отправки данные крупными кусками — условно говоря, десятками килобайт за раз, а не пакет за пакетом по 1500 байт (стандартный MTU для Ethernet). Разбивать этот крупный кусок на отдельные пакеты правильного размера, с правильными заголовками и последовательными номерами — работа, которую можно сделать программно в ядре, либо переложить на карту.

Это и есть TSO (TCP Segmentation Offload) — ядро отдаёт карте один большой сегмент данных с одним заголовком-шаблоном, а карта уже сама нарезает его на пакеты нужного размера, дублируя и корректируя заголовки для каждого. Программная версия того же механизма, которая работает даже без аппаратной поддержки, называется GSO (Generic Segmentation Offload) — она откладывает нарезку максимально близко к моменту реальной отправки, но всё равно делает это на CPU. Разница между TSO и GSO принципиальна: первое — это работа карты, второе — это работа ядра, просто отложенная во времени для лучшей эффективности кэша.

На приёме работает зеркальный механизм — GRO (Generic Receive Offload): вместо того чтобы отдавать сетевому стеку каждый маленький входящий пакет по отдельности, карта (или, в программном варианте, драйвер ещё до передачи в стек) склеивает несколько последовательных пакетов одного TCP-потока в один крупный сегмент и передаёт его наверх одним куском. Дальше по стеку идёт уже один крупный кусок данных вместо десятков мелких — меньше проходов через код обработки пакета, меньше работы с очередями, меньше нагрузки на обработку прерываний.

Экономия здесь получается не столько на самой нарезке или сборке (это относительно дешёвая операция), сколько на количестве проходов через весь сетевой стек ядра: каждый отдельный пакет — это отдельный вызов обработчика и отдельное программное прерывание (softirq). На высокой частоте пакетов в секунду эти накладные расходы обычно весомее, чем стоимость самой арифметики.

Проверяется это аналогично:

ethtool -k eth0 | grep -E 'tcp-segmentation|generic-segmentation|generic-receive'

Что происходит, когда offloading выключается

Когда любой из этих механизмов перестаёт работать — не важно, отключили его вручную, драйвер не смог его инициализировать или виртуализация его не пробрасывает — вся работа, которую раньше делала карта, не исчезает. Она просто возвращается туда, откуда изначально уехала: в ядро, на CPU. Контрольные суммы снова считаются программно проходом по каждому байту пакета. Крупные сегменты снова режутся на MTU-размер до передачи карте, если TSO выключен, а GSO по какой-то причине тоже не подхватывает эту работу. Входящие пакеты снова идут в стек по одному, без склейки, и каждый требует полного прохода через обработчики протоколов.

С точки зрения приложения ничего не поменялось — TCP-соединение как работало, так и работает, данные доходят. С точки зрения top картина другая: доля %si (softirq) и %sys начинает расти пропорционально сетевой нагрузке, без видимой связи с тем, что делает сама программа. Особенно заметно это на конфигурациях с высоким PPS — много мелких запросов, а не редкая передача больших файлов: там разница между "карта склеивает пакеты сама" и "ядро разбирает каждый пакет по отдельности" ощущается сильнее всего, потому что накладные расходы линейны по количеству пакетов, а не по объёму данных.

Рост нагрузки от отключённого offloading не будет катастрофическим скачком до 100% на пустом месте — это постепенное, пропорциональное трафику увеличение доли CPU, которая раньше практически не была видна в профиле нагрузки. Его легко спутать с "сервис стал тяжелее", хотя причина в том, что просто изменилась эффективность обработки того же объёма трафика.

Причин, по которым offloading перестаёт работать, на практике немного, но каждая встречается регулярно:

  • Драйвер не поддерживает функцию или поддерживает её криво. Особенно это касается драйверов не самых массовых карт, где часть offload-функций объявлена, но фактически работает с ошибками — тогда их отключают защитным образом.
  • Виртуализация. Виртуальная сетевая карта — это программная эмуляция, и то, что она "показывает" гостевой ОС через ethtool, не обязательно соответствует реальным возможностям физической карты хоста. Подробнее — в статье про virtio и эмуляцию сетевой карты.
  • Ручное отключение при отладке. Иногда offload-функции выключают намеренно — чтобы тестировать поведение стека без "магии" карты, или потому что offloading подозревают в баге — и забывают включить обратно.
  • Обновление ядра или драйвера. После апгрейда состав поддерживаемых по умолчанию offload-функций может измениться — драйвер новой версии осторожнее к каким-то флагам, или у карты появилась новая прошивка с другим набором возможностей.

Виртуализация — самый частый источник сюрпризов

Стоит отдельно остановиться на виртуализации, потому что именно там offloading чаще всего оказывается не тем, чем кажется. У виртуальной машины нет физической сетевой карты — есть либо полная эмуляция реального устройства (например, Intel e1000), либо паравиртуализированный драйвер вроде virtio-net, спроектированный для эффективной работы внутри гипервизора. Разница между ними принципиальна: эмулированная карта в лучшем случае делает вид, что поддерживает offload-функции, но реальная экономия CPU там сомнительна, потому что вся "аппаратная" логика на деле выполняется тем же гипервизором на том же физическом CPU хоста — просто в другом процессе.

С virtio-net ситуация лучше: он прозрачно прокидывает часть offload-флагов между гостем и хостом, а на хосте эту работу может взять на себя либо реальная карта (при SR-IOV или похожем механизме проброса), либо оптимизированный путь вроде vhost-net. Но нюанс остаётся: то, что ethtool внутри гостевой ОС показывает tx-checksumming: on, не гарантирует, что вычисление происходит на физическом чипе, а не программно на хосте — эта работа может просто переехать на процессор гипервизора, и в top внутри гостя её вообще не видно, зато она заметна на хосте.

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

Как проверить состояние offloading на своём сервере

Полная сводка по всем offload-флагам конкретного интерфейса:

ethtool -k eth0

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

rx-checksumming: on
tx-checksumming: on
tcp-segmentation-offload: on
generic-segmentation-offload: on
generic-receive-offload: on

Если что-то из ключевого набора стоит off без пометки [fixed] — это можно попытаться включить:

sudo ethtool -K eth0 tso on gso on gro on rx on tx on

Если после этой команды флаг всё равно возвращается в off — либо драйвер молча игнорирует запрос (стоит проверить dmesg на предмет ошибок), либо карта или виртуальное окружение действительно не поддерживают эту функцию, и разбираться нужно на уровне драйвера или гипервизора, а не флага.

Дальше стоит убедиться, что дело именно в offloading, а не в чём-то ещё. Полезная связка:

mpstat -P ALL 1

покажет распределение %soft (softirq) и %sys по ядрам во время сетевой нагрузки. Если один или несколько ядер стабильно упираются в softirq под сетевой нагрузкой, это симптом, знакомый по теме прерываний — подробный разбор того, как сетевая карта "ест" ядро через прерывания и при чём здесь процесс ksoftirqd, есть в статье про прерывания и почему сетевая карта нагружает конкретное ядро. Offloading и распределение прерываний — смежные, но разные механизмы: первый экономит объём работы, второй распределяет её по ядрам. Проблема с offloading часто усугубляет проблему с прерываниями, потому что каждый несклеенный пакет — это ещё и лишнее прерывание.

Если нужно посмотреть на трафик в динамике, полезен sar -n DEV 1, который покажет пакеты в секунду и объём трафика по интерфейсам — это помогает понять, действительно ли рост CPU пропорционален росту PPS, а не просто совпал по времени с чем-то другим.

Практические следствия: когда стоит проверять offloading в первую очередь

Гнаться за offloading в качестве первой гипотезы при любой нагрузке не стоит — в большинстве случаев высокая загрузка CPU объясняется работой самого приложения, и общий разбор причин лучше начинать с материала о том, как искать причину высокой нагрузки на процессор. Но есть признаки, при которых offloading стоит проверить одним из первых шагов:

  • Рост %sys или %si совпадает по времени именно с ростом сетевого трафика, а не с изменением бизнес-логики приложения.
  • Нагрузка выросла после миграции виртуалки на другой хост, обновления ядра или смены драйвера сетевой карты — то есть после события, которое могло тихо сбросить offload-флаги.
  • Проблема проявляется сильнее на потоках с большим количеством мелких пакетов, а не на редких передачах больших файлов — это характерный почерк отсутствующего TSO/GRO.
  • В ethtool -i eth0 драйвер помечен как generic или software-эмуляция, а не как драйвер конкретного производителя карты.

Если все признаки сходятся — стоит проверить ethtool -k, сравнить с тем, что было раньше, и только после этого списывать проблему на "сервису просто нужно больше CPU". Иногда решение — это одна команда ethtool -K, а не апгрейд тарифа.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Влияет ли offloading на задержку (latency), а не только на CPU?

Косвенно да: GRO задерживает передачу пакета наверх стека до момента склейки, добавляя доли миллисекунды. Для латенси-чувствительных сценариев (например, торговых систем) offload-функции иногда сознательно отключают в обмен на предсказуемость задержки.

Можно ли включить offloading, если карта его физически не поддерживает?

Нет — если в выводе ethtool -k рядом с флагом стоит [fixed], значит его значение задано железом или драйвером и программно не меняется. Остаётся либо смириться с программной обработкой, либо сменить карту или окружение.

Почему на VPS offloading иногда работает хуже, чем на выделенном сервере при том же трафике?

Потому что часть "аппаратной" работы на виртуалке в реальности выполняется программно на хосте, а не на чипе карты, и зависит от типа виртуального адаптера и настроек гипервизора — это не всегда видно изнутри гостевой ОС.

Стоит ли трогать offload-флаги на проде без крайней необходимости?

Не стоит — если сеть работает штатно и нагрузка на CPU в норме, менять флаги "на всякий случай" не нужно. Проверка и корректировка оправданы как диагностика конкретной проблемы, а не как профилактика.

GRO может навредить приложению, которое ожидает пакеты определённого размера?

Да, в редких случаях: код, работающий с сырыми сокетами и рассчитывающий на конкретный размер входящих сегментов, может вести себя неожиданно при включённой склейке. Для обычных TCP/UDP-приложений через стандартный сокет это не проблема.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →