← На главную

Оптимизировал пинги на 8 КБ — вышло весело, но бесполезно

08.06.2026 10:01 · hackernews

Коллега рассказывал автору про систему мониторинга соединений. Она шлёт ICMP Echo Requests на несколько серверов и замеряет задержки и потери за минуту, 5 и 15 минут. Данные хранятся в кольцевом буфере на 512 записей. Первая версия структуры ping_timestamp содержала sent_ns, received_ns (оба uint64_t), source_addr (in_addr_t), seq_no (uint16_t) и received (bool). Массив из 512 таких структур занимал 12 288 байт (12 КБ).

Автор решил сэкономить. Вместо двух таймштампов он сделал tagged union: пока пакет не получен — хранится sent_ts, после получения — elapsed_ts (разность). Единицы измерения — 100 микросекунд (0.1 мс), хватит на 20 лет. Размер уменьшился до 8192 байт.

Затем попробовал битовые поля: sent_or_elapsed_ts (43 бита), received (1 бит), seq_no (16 бит) и оставил source_addr. Но из-за выравнивания размер не изменился — 36 бит уходило в padding.

Тогда автор пересмотрел необходимость source_addr. Он нужен, потому что адрес меняется (мобильная сеть), и при смене сбрасывается seq_no. Но можно обойтись 4-битным счётчиком, который увеличивается при смене адреса. Счётчик позволяет отличать пакеты от разных адресов, даже если seq_no совпадает. Этот счётчик вставляется вместо source_addr. Финальная структура: elapsed_or_sent_ts (43 бита), received (1 бит), counter (4 бита), seq_no (16 бит). Поля переставлены так, чтобы seq_no лежал на 16-битной границе — загрузка одной инструкцией ldrh. Размер массива — 4096 байт (одна страница памяти). Экономия 8 КБ.

В дополнении автор переставил received и counter местами: теперь читать received можно простым сдвигом без маски. А чтобы counter тоже не требовал маски, он переименовал бит в not_received и читает счётчик только когда not_received == 0 — компилятор убирает маску.

В конце автор признаётся: всё это было бессмысленно — приложение не ограничено по памяти. Но было весело.

Читать оригинал →