-
Notifications
You must be signed in to change notification settings - Fork 337
Оптимизация потребления памяти на соединение (~40%) #412
Description
Привет!
Решил проверить, могу ли я сделать более экономичной юнит-экономику по соединениям — оперативка нынче дорогая :) и по сути главный лимитирующий фактор для количества одновременных соединений.
Что нашёл:
Замерил расход памяти на одно соединение. Основные потребители — горутинные стеки, которые раздуваются из-за stack-allocated массивов по 16 КБ и больше не сжимаются:
| Компонент | Расход |
|---|---|
pump() × 2 — var buf [MaxRecordPayloadSize]byte на стеке |
~64 КБ (стеки растут до 32 КБ каждый) |
doppel.start() — buf := [MaxRecordSize]byte{} на стеке |
~32 КБ |
Clock.Start() — отдельная горутина + канал на соединение |
~2-4 КБ |
ctx.Done() watcher горутины в relay и proxy |
~4 КБ |
| Итого стеки | ~100 КБ |
Плюс TLS/doppel heap-буферы (~13 КБ) и прочее. В сумме ~120 КБ на соединение.
Что сделал:
-
sync.Pool для relay буферов + уменьшение до 4 КБ. 16 КБ буфер не нужен, потому что TLS-слой сам собирает TLS records во внутреннем
readBuf— relay просто читает из него порциями. 4 КБ не увеличивает число syscalls, только больше итерацийio.CopyBuffer(overhead — наносекунды на interface dispatch). -
sync.Pool для doppel буфера. Перенос 16 КБ массива из стека в пул.
-
Inline Clock в
start(). Вместо отдельной горутины с каналом —time.Timerпрямо в циклеstart(). Семантика та же: таймер стреляет → обработка → reset. Backpressure сохраняется, т.к. Reset только после завершения итерации. -
context.AfterFuncвместо горутин в relay и proxy. Вместо горутин, висящих на<-ctx.Done()всё время жизни соединения —context.AfterFunc, который создаёт горутину только в момент отмены.
Результат в проде:
| Метрика | До | После |
|---|---|---|
| Горутин на соединение | 4-5 | 2-3 |
| Стеки горутин (суммарно) | ~100 КБ | ~12 КБ |
| RSS на соединение (замер) | ~160 КБ | ~93 КБ |
| Экономия | ~42% |
Замерял так: RSS процесса минус baseline (RSS при 0 соединениях), делим на число соединений. Тестировал при ~50-60 активных соединениях.
Все тесты проходят, включая -race. В проде крутится несколько часов без проблем.
У себя в форке реализовал: dolonet#1
Если идея ок, могу подготовить чистый PR в mtg.