Твердотельный накопитель с паспортной скоростью 7 ГБ/с в реальной системе отдает вдвое меньше, а дорогая видеокарта трудится на четырех линиях вместо шестнадцати. Такие сюрпризы почти никогда не связаны с самим устройством: шина PCIe договаривается о скорости и ширине при каждой загрузке, и любое слабое звено в цепочке снижает общий потолок. Ядро Linux дает все инструменты, чтобы увидеть фактическое состояние каждого звена и повлиять на него, от чтения двух файлов в sysfs до настройки размеров пакетов в регистрах конфигурационного пространства. Ниже разбирается, как считать теоретический предел, чем согласованная скорость отличается от заявленной, как настроить MaxPayloadSize, MaxReadRequestSize и ASPM и как измерить реальный трафик. Все примеры содержат команды, пути и числа, пригодные и для сервера, и для домашней рабочей станции.
Согласование скорости звена и числа линий при каждой загрузке
При включении питания физический уровень PCIe перебирает скорости и ширины от младших к старшим и останавливается на максимальных, которые одновременно поддерживают оба конца звена. Результат фиксируется в регистрах конфигурационного пространства: Link Capabilities описывает возможности порта, Link Status показывает фактическое состояние после тренировки. Утилита lspci с уровнем детализации vv печатает обе пары сразу:
lspci -vv -s 0000:01:00.0
Ключевые строки выглядят так:
LnkCap: Port #0, Speed 16GT/s, Width x16, ASPM L0s L1, Exit Latency L0s <1us, L1 <8us
LnkSta: Speed 8GT/s (downgraded), Width x4 (downgraded)
Слово downgraded в скобках прямо сообщает, что порт рассчитан на большее, а согласовал меньше. Причины бывают разные: слот физически соединен не всеми линиями, соседняя карта заняла линии процессора, BIOS ограничил скорость ради стабильности, аплинк чипсета делится между несколькими устройствами. Каждая причина лечится по-своему, поэтому разбор всегда начинается с этих двух строк. Отдельный регистр LnkCtl2 содержит поле Target Link Speed, где прошивка или драйвер выставляют потолок согласования; значение ниже паспортного сразу объясняет часть потерь. На длинных райзерах и переходниках сигнал на 16 GT/s затухает, и прошивка намеренно держит младшее поколение ради устойчивости. Заодно здесь видны задержки выхода из состояний L0s и L1, они пригодятся при разговоре про энергосбережение.
Чтение состояния звена из sysfs и обход всех устройств скриптом
Для быстрой проверки без многостраничного вывода lspci ядро публикует состояние звена в каталоге устройства. Файл /sys/bus/pci/devices/0000:01:00.0/current_link_speed содержит строку вида "16 GT/s", а файл current_link_width значение "x16". Рядом лежат max_link_speed и max_link_width с потолками от прошивки; запись в max_link_speed нового значения, например "8 GT/s", ограничивает согласование сверху, что помогает при поиске нестабильности на высоких поколениях. Обойти все устройства одним циклом проще, чем читать вывод lspci по каждому отдельно:
for d in /sys/bus/pci/devices/*; do
printf '%-12s %-8s %s\n' "${d##*/}" \
"$(cat "$d/current_link_speed")" \
"$(cat "$d/current_link_width")"
done
Для чтения этих файлов права суперпользователя не нужны, а адрес устройства берут из первой колонки вывода lspci. Укороченный пример для платы с видеокартой и диском выглядит так:
0000:00:01.0 16 GT/s x16
0000:01:00.0 8 GT/s x4
0000:06:00.0 5 GT/s x1
Один запуск рисует карту системы: порты процессора держат 16 GT/s и x16, устройства чипсета довольствуются меньшим, и кандидаты на разбор видны сразу. Адреса с расширением .0 относятся к функциям устройств, остальные строки к мостам и концентраторам, и их состояния тоже имеют смысл, ведь транзакции к устройству проходят через всю цепочку.
Арифметика теоретического потолка по поколениям и ширине звена
Каждая линия PCIe 3.0 передает 8 гигатрансферов в секунду с кодированием 128b/130b, на выходе 984,6 МБ/с на линию. Умножение на четыре линии дает 3938 МБ/с, то есть около 3,9 ГБ/с на звено. PCIe 4.0 удваивает частоту до 16 GT/s: линия несет 1969 МБ/с, а восемь линий 15752 МБ/с, примерно 15,7 ГБ/с. Для полноты картины PCIe 1.x отводит по 250 МБ/с на линию, PCIe 2.0 по 500, PCIe 5.0 по 3938. Формула одна и та же:
частота × 128/130 × число линий / 8 = байтов в секунду
Для звена PCIe 3.0 x4 вычисление выглядит так: 8 × 128 / 130 × 4 / 8 = 3,94 ГБ/с. Тот же расчет применим к любому звену. Видеокарта на PCIe 3.0 x16 располагает теми же 15,7 ГБ/с, что и накопитель на PCIe 4.0 x8. Аплинк чипсета типично строится на четырех линиях поколения 3.0 или 4.0, то есть 3,9 или 7,9 ГБ/с на все устройства чипсета вместе: два диска M.2, сетевая карта и контроллеры USB делят его между собой. Поэтому подключение накопителя к портам чипсета часто оказывается медленнее, чем к линиям процессора, даже при равных поколениях. Практическая отдача всегда ниже теоретической: каждый пакет несет служебный заголовок и контрольную сумму, поэтому на больших передачах эффективность держится в пределах 90-95 процентов, а на мелких блоках проседает сильнее. Накопитель PCIe 4.0 x4 с паспортными 7000 МБ/с упирается в теоретические 7877 МБ/с почти вплотную, значит любой откат на поколение 3.0 режет его потолок ровно вдвое, до 3938 МБ/с. Отсюда простое правило: если замер стабильно повторяет потолок старого поколения, искать нужно звено, а не накопитель.
Размеры пакетов MaxPayloadSize и MaxReadRequestSize и параметр pcie_bus_perf
Единица передачи PCIe, пакет TLP, обвешивается служебными байтами: заголовок на 12-20 байтов, номер последовательности на 2, контрольная сумма LCRC на 4 плюс служебный кадр. Итого порядка 20-24 байтов на каждый пакет. При размере полезной нагрузки MaxPayloadSize в 128 байтов шина переносит 152 байта на каждые 128 полезных, эффективность около 84 процентов. При 256 байтах она вырастает до 91 процента, при 512 до 95 процентов. Для операций чтения ту же задачу решает MaxReadRequestSize: запрос на 4096 байтов создает в 32 раза меньше служебных транзакций, чем запрос на 128.
Текущие значения видны в регистре Device Control:
setpci -s 0000:01:00.0 CAP_EXP+08.w
В слове Device Control поле MaxPayloadSize занимает биты 7:5, MaxReadRequestSize биты 14:12, кодируются они одинаково: 0 соответствует 128 байтам, 1 значит 256, 2 значит 512, 3 значит 1024, 4 значит 2048 и 5 значит 4096. После настройки lspci показывает итог в строке DevCtl:
DevCtl: CorrErr- NonFatal- Fatal- UnsupReq- RlxdOrd+ ExtTag+ PhantFunc- AuxPwr- NoSnoop+ MaxPayload 256 bytes, MaxReadReq 512 bytes
Поднимать значения вручную по одному устройству рискованно: размер пакета обязан совпадать у всех устройств ниже одного моста, иначе транзакции теряются. Самый безопасный путь, параметр ядра, который согласует максимум по всему дереву PCI:
pci=pcie_bus_perf
Его добавляют в строку GRUB_CMDLINE_LINUX_DEFAULT в файле /etc/default/grub и пересобирают конфигурацию загрузчика:
update-grub
На нагрузках с мелкими блоками, например на базах данных, выигрыш достигает 10-15 процентов полосы, на последовательных передачах эффект скромнее, зато снижается число служебных транзакций на мегабайт данных. Прирост считается прямо из эффективности: те же восемь линий поколения 4.0 при MPS 512 отдают около 15,0 ГБ/с практических, а при MPS 128 не выше 13,3, и разница целиком уходит в заголовки. Для особых конфигураций существуют еще pci=pcie_bus_safe и pci=pcie_bus_peer2peer, первый выбирает безопасный общий максимум, второй нужен узлам, обменивающимся памятью напрямую.
Энергосбережение звена ASPM и его цена в задержках
Механизм ASPM переводит линии в низкое энергопотребление во время простоя: состояние L0s на короткие паузы, L1 на глубокий сон. Выход из L1 занимает от единиц до десятков микросекунд, и для накопителя с собственной задержкой в 50-100 мкс каждая пауза заметна. Активный режим показывает файл политики:
cat /sys/module/pcie_aspm/parameters/policy
В выводе скобками отмечен текущий вариант, например [default]. Режим performance отключает перевод линий в сон:
echo performance > /sys/module/pcie_aspm/parameters/policy
Для всей системы раз и навсегда работает параметр ядра pcie_aspm=off в той же строке загрузчика. Подсостояния L1.1 и L1.2 уходят глубже, их выход длится десятки и даже сотни микросекунд, поэтому на накопителях и аудиоинтерфейсах их отключение дает самый заметный эффект. Значения Exit Latency из начала статьи как раз показывают, сколько микросекунд требует выход из каждого состояния на этом порту. Выбор зависит от задачи: серверу, где ценится предсказуемость задержек, ближе performance, а ноутбуку, живущему от батареи, честнее оставить default и платить микросекундами за ватты. На звеньях с высокой нагрузкой постоянные переходы L1 туда и обратно еще и съедают часть полосы на повторную тренировку.
Измерение фактической пропускной способности нагрузкой и счетчиками
Замер последовательного чтения на сыром устройстве через fio показывает, сколько реально проходит через звено:
fio --name=seqread --filename=/dev/nvme0n1 --direct=1 --rw=read \
--bs=1M --iodepth=32 --runtime=30 --time_based --group_reporting
Чтение сырого устройства безвредно, содержимое не меняется; на смонтированном разделе достаточно указать файл вместо блочного устройства. Итоговая строка отчета выглядит так:
READ: bw=3387MiB/s (3552MB/s), io=99.0GiB, runt=30001msec
Мегабайты из отчета сравнивают с потолком звена: 3552 МБ/с против 7877 теоретических означают откат на поколение 3.0 или урезание ширины. Если накопитель PCIe 4.0 x4 выдает 3,4 ГБ/с вместо 6,8, звено ушло на поколение вниз. Для видеокарты состояние шины смотрят утилитой nvidia-smi:
nvidia-smi --query-gpu=pcie.link.gen.current,pcie.link.width.current,pcie.link.gen.max --format=csv
Строка ответа содержит согласованные поколение и ширину и их максимум. На процессорах AMD Zen фактический трафик считает утилита pcie_bw по аппаратным счетчикам: она не создает нагрузку, а фиксирует байты, прошедшие через шину во время реальной работы приложения. Сетевые адаптеры проверяют iperf3 с несколькими потоками:
iperf3 -c 192.168.10.2 -P 4 -t 30
Крупный кадр снижает долю служебных транзакций на звене:
ip link set ens1f0 mtu 9000
Сравнение замеров с арифметикой из предыдущих разделов дает точный диагноз: сетевая карта на 10 Гбит/с, отдающая меньше гигабайта в секунду, почти наверняка сидит на PCIe 2.0 x1 с его 500 МБ/с, а не на обещанных четырех линиях поколения 3.0.
Разбор случая, когда накопитель PCIe 4.0 выдает скорость поколения 3.0
Типовая картина: накопитель с паспортом 7100 МБ/с на чтение стабильно показывает 3450 МБ/с. Число почти совпадает с практическим потолком PCIe 3.0 x4, значит звено работает на поколении 3.0. Цепочка проверок короткая:
- сверить строки LnkCap и LnkSta командой
lspci -vvи найти пометку downgraded; - прочитать current_link_speed и current_link_width для накопителя и для порта, к которому он подключен, и сравнить значения;
- выяснить по схеме платы, не делит ли разъем M.2 линии с соседним слотом и не укорочен ли он электрически до двух линий;
- проверить, не проходит ли путь через чипсет с аплинком на четыре линии, который занят еще и другими дисками;
- перезапустить тренировку звена командой
echo 1 > /sys/bus/pci/devices/0000:01:00.0/resetи снять показания заново; - если скорость держится только на 2.5 или 5 GT/s, осмотреть контакты разъема, поправить крепление радиатора и повторить замер fio.
Чаще всего виноваты деление линий и укороченный слот, реже прошивка материнской платы, где поддержка высоких поколений появляется после обновления BIOS. На серверных платах деление слота управляется в BIOS параметром bifurcation со значениями вроде x4x4x4x4, и неверный режим выглядит для системы так же, как неисправный разъем. Финальная проверка после каждого изменения одна и та же: короткий прогон fio и сравнение с потолком из арифметики. Системный подход к шине не требует пересборки железа: два файла в sysfs, точный расчет и один параметр ядра возвращают устройствам паспортные гигабайты, которые раньше терялись молча.