История одной оптимизации

Писал я тут свой первый драйвер для сетевой карты. Взял, как полагается, за основу готовый образец драйвера. Выкинул всю аппаратно-зависимую часть. Добавил свою. По ходу дела выяснил, что tear-down код в примере отсутствует напрочь, чтение стандартной конфигурации не сделано, кругом hardcoded константы. В общем, обычная история, что вы хотите от образца?

Дописал до работающего состояния, протестировал скорость TCP/IP соединения. Получается примерно 3 MByte/sec на одно соединение, около 12 MByte/sec – пиковая пропускная способность нескольких параллельных соединений. Маловато для 1 Gbit/sec соединения.

Померил загрузку CPU. 100%, из которых больше 90% - DPC. Т.е. драйвер из прерываний не вылезает. Ну, хорошо, процессор у нас конечно слабенький, но все равно очень подозрительно. Добавил поддержку Interrupt Moderation, т.е. выставил принудительную задержку между прерываниями так, чтобы их происходило не более 1000 в секунду. Никакого эффекта. Сделал приемные буфера кэшируемыми. Для этого пришлось заметно переделать схему размещения этих буферов; позаботится о выравнивании и т.п. Померял – скорость едва заметно упала. Что за черт? Стал корректно отрабатывать параметры Interrupt Throttling. Уровень DPCs упал до 60% без заметного эффекта на скорость соединения. И то – хлеб.

По всей видимости, без выгрузки вычислений контрольных сумм пакетов на сетевую карту ничего не выйдет. Добавил соответствующую поддержку в драйвер. Большая часть добавленного кода при этом, почему-то ушла на то, чтобы договориться с NDIS, а не с адаптером. По ходу дела нашелся баг в логике проверки контрольных сумм адаптером. Баг приводил к тому, что при определённых условиях, как правило – под высокой нагрузкой, TCP/IP соединение «заклинивало» (за неимением лучшего слова). Стоило только начать перехватывать все пакеты с помощью Network Monitor, баг не повторялся. В конце концов, баг (довольно тривиальный по своей сути) был пойман и нейтрализован. Производителю был послан баг репорт с подтекстом «как это вы тестировали свою железку, что такое пропустили?». Пропускная способность выросла до 17 MByte/sec при полной загрузке процессора.

Возникла мысль, что дело видимо в настройке TCP/IP. Было замечено, что для каждого соединения число неподтвержденных пакетов было слишком низким. Долго ли коротко ли, но проведя сеанс одновременной удаленной медитации с коллегой, мы выяснили (в смысле - коллега ткнул меня носом, сам того не понимая), что проблема была до смешного простая. Приходящие пакеты отдавались NDIS-у не в том порядке, в каком они были приняты. С точки зрения TCP/IP это выглядело как потери пакетов. Драйвер протокола постоянно запрашивал повторную передачу «потерянных» пакетов, генерируя больший, чем нужно, траффик и сужая ширину TCP окна до минимума. Стоило поменять порядок пакетов в списке (три строчки в коде), скорость одного соединения выросла свыше 30 MByte/sec.

Зато у нас теперь работает checksum offloading и куча другой полезной фигни, до которой обычно руки не доходят. :-)

comments powered by Disqus