Перейти к содержимому
ГоМастер

Поиск по урокам

Найдите урок, курс, термин или раздел

Урок 24 из 25 ~20 мин +50 XP

Производительность Fasthttp

Прошлый урок закрылся обещанием: сегодня мы поставили приложение на боевой сервер, завтра заглянем под капот движка, который этот сервер кормит. Исполняем. Тема «fasthttp производительность» — это разбор сделки, которую движок заключил за вас: zero memory allocations на горячем пути в обмен на совместимость с net/http. Мы разберём, где живут fiber аллокации и почему методы контекста отдают значения с коротким сроком годности, посчитаем всё руками: соберём fiber benchmark командой go test -bench с b.ReportAllocs, вскроем профиль pprof через отдельный net/http-порт, покрутим GOGC и GOMEMLIMIT — и честно проговорим границы применимости, на которых быстрые пути fasthttp перестают помогать. Сравнение fiber vs gin получит числа и критерии вместо лозунгов: фундамент определяет совместимость, а профиль нагрузки — выбор. Опоры уже стоят: [конфигурационный урок двадцатый](/courses/fiber/lessons/20-konfiguraciya/) назвал поля Concurrency и ReadBufferSize, [деплой двадцать третий](/courses/fiber/lessons/23-deploy/) дал боевой контекст — контейнер с лимитами CPU, nginx впереди. Все факты сверены с исходниками fiber v3.5.0 и его движком fasthttp v1.73.0 из go.mod.

Содержание урока · 14 разделов

Мост из прошлого урока: под капот движка, который кормит сервер#

Начнём почти дословно с того, чем закончили: «двадцать четвёртый урок — производительность Fasthttp: почему Fiber быстр, zero allocation и границы применимости. Логика прямая: сегодня мы поставили приложение на боевой сервер, завтра заглянем под капот движка, который этот сервер кормит — и поймём, за счёт чего fiber и выигрывает те самые бенчмарки, и на каких нагрузках быстрые пути fasthttp перестают помогать». Обещание исполняется в четыре захода. Первый — механика: что именно fasthttp переиспользует и почему на горячем пути не возникает аллокаций, которых не было бы у net/http. Второй — цена: значения, которые вы вытаскиваете из fiber.Ctx, живут ровно до возврата хендлера, и это следствие той же механики, а не каприз документации. Третий — измерение: fiber benchmark, который вы соберёте сами, и pprof, который покажет, куда уходит время и память вашего кода, а не движка. Четвёртый — границы: CRUD с походом в базу, стриминг больших тел и HTTP/2 — случаи, где смена движка не сдвинет ни миллисекунду. Канон курса остаётся при нас: инстанс на :8080, маршрут /api/tasks, конверт items, хендлер с c.JSON — теперь мы измерим и его тоже.

fasthttp производительность: сделка с переиспользованием#

Прочитаем шапку fasthttp так, как её написал автор, дословно: «Fast HTTP package for Go. Tuned for high performance. Zero memory allocations in hot paths. Up to 10x faster than net/http». Три обещания, и первое — technical, а не рекламное: zero memory allocations in hot paths. Горячий путь — это код, который выполняется на каждом запросе: распарсить заголовки, найти маршрут, подготовить контекст, собрать ответ. У стандартного net/http каждый такой запрос рождает новые объекты: http.Request, http.ResponseWriter, буферы чтения — сборщик мусора честно прибирает всё это, но тратит на прибирание процессорное время. Fasthttp пошёл другим путём: объекты контекста, буферы заголовков и тел не создаются на запрос, а берутся из пула и после ответа моются и возвращаются туда же. Десяток раз быстрее — это на синтетике с мелкими ответами и keep-alive-соединениями, и к честности цифр мы вернёмся в блоке про fiber benchmark; важно другое: выигрыш рождается не из магии, а из отказа создавать то, что можно переиспользовать. У всякой сделки есть цена, и здесь она видна в названии пакета: fasthttp — это не net/http. Типы несовместимы, готовые http.Handler-middleware не подключаются напрямую, а HTTP/2 движок не поддерживает вовсе. За скорость платят экосистемой, и умный выбор требует понимать обе стороны весов — этому посвящён блок fiber vs gin.

Канон: хендлер под микроскопом аллокаций

GO
app.Get("/api/tasks", func(c fiber.Ctx) error {
    // быстрый путь: движок не аллоцировал ни байта до этой строки
    // svc — сервис из композиционного корня двадцать второго урока
    items, err := svc.List(c.Context())
    if err != nil {
        return fiber.NewError(fiber.StatusInternalServerError, "сервис недоступен")
    }
    // а вот здесь аллокации есть: encoding/json строит байты ответа
    return c.JSON(fiber.Map{"items": items})
})

Разложим канон курса по строке аллокаций, потому что здесь спрятана самая частая ошибка чтения лозунгов. Всё, что сделал движок до вашей лямбды, — принял соединение, разобрал заголовки, нашёл маршрут, выдал контекст из пула — действительно прошло без аллокаций: это и есть обещание zero allocation. Но внутри хендлера мир меняется: svc.List отдаёт слайс структур — аллокация, вызванная вашим кодом; c.JSON вызывает кодировщик, а дефолтный JSONEncoder в fiber.Config — это json.Marshal, и маршалинг заведомо строит новые байты, плюс сама карта fiber.Map — аллокация. Выражение «движок без аллокаций» описывает накладные расходы фреймворка, а не устраняет ваши. Практический вывод двоякий. Первый: профиль вашего приложения почти всегда будет во главе списка не fiber, а ваш json.Marshal и ваши походы в хранилище — это нормально и именно это покажет pprof ниже. Второй: апгрейд фреймворка не отменяет дорогой хендлер — оптимизировать надо то, что профиль назвал главным, а не то, что маркетинг назвал быстрым. Запомните этот хендлер: он станет героем benchmark-блока.

fiber аллокации: UnsafeString и правило одной жизни значения#

Теперь цена zero allocation, и она записана в исходниках fiber v3.5.0 прямо. При старте приложения поле toString получает значение utils.UnsafeString — это конверсия байтов в строку без копии, через unsafe: строка, которую вам вернул метод контекста, не владеет своей памятью, она указывает в буфер соединения, который движок потом использует заново. Док-комментарий метода Params в req.go формулирует контракт дословно: «Returned value is only valid within the handler. Do not store any references. Make copies or use the Immutable setting to use the value outside the Handler» — значение живёт только внутри хендлера, ссылок не хранить, нужен хранить — копируйте или включайте Immutable. Комментарий к методу, возвращающему метод запроса, объясняет, что бывает с нарушителями: «an unsafe alias into the request buffer would be silently corrupted on connection reuse» — алиас в буфер соединения тихо портится при переиспользовании соединения. Тихо — ключевое слово: не паника, не гонка с детектором, а мусорные байты в чужом ответе, который вы будете ловить по дням. Отсюда и выход из сделки — поле fiber.Config.Immutable: его док-комментарий признаётся, что при true фреймворк «relinquishes the 0-allocation promise» — отказывается от обещания нуля аллокаций в отдельных случаях, — зато все значения контекста становятся честными копиями и переживают возврат из хендлера. fiber аллокации — это не враг, а параметр сделки: по умолчанию вы получаете скорость и отвечаете за дисциплину; Immutable — скорость чуть ниже, спокойный сон выше.

Проверьте себя: значение, пережившее хендлер

Хендлер передаёт заголовок запроса в фоновую горутину. Что произойдёт при наплыве клиентов?

fiber vs gin: фундамент решает#

Первый урок курса положил сравнение на стол — теперь, под занавес, у нас достаточно словаря, чтобы сравнить по-взрослому, потому что выбор между движками — это fiber производительность против совместимости, и никак иначе. Gin стоит на net/http, а значит бесплатно получает всё, что стандартная библиотека накопила за пятнадцать лет: HTTP/2 и HTTPS из коробки, огромный парк готовых middleware в стиле http.Handler, httptest и httputil, профилировщик net/http/pprof одной строкой импорта, совместимость с прокси и service mesh, которые говорят по-настоящему современному HTTP. Fiber стоит на fasthttp: HTTP/2 движок не поддерживает, http.Handler-middleware не подключается напрямую — только через adaptor, и каждый мост между мирами стоит аллокаций, ровно тех, от которых мы уходили. В прикладном CRUD-сервисе разницы в скорости вы не увидите: узкое место — база и сериализация, о чём первый урок уже честно предупреждал. Правило выбора из практики: legacy-проект с http.Handler-наследством, потребность в gRPC-gateway, HTTP/2 или командной привычке к net/http — gin, и это не поражение fiber, а трезвый расчёт; новый JSON-API с высокой частотой мелких запросов, где профиль показал, что накладные расходы фреймворка реальны, — fiber оправдан. Синтетические бенчмарки, в которых fasthttp уходит вперёд в разы, описывают плечо «plaintext, крошечный ответ, keep-alive» — убедитесь, что ваше плечо именно такое, прежде чем делать вывод.

fiber vs gin: сравнение по пунктам#

КритерийFiber (fasthttp)Gin (net/http)
Движокfasthttp v1.73.0, собственный стек соединенийnet/http — стандартная библиотека
Совместимость net/httpнет: типы несовместимы, мост — adaptorполная: http.Handler, httptest, pprof
HTTP/2не поддерживается движкоместь из коробки, включая h2 через TLS
Аллокации горячего путипулы объектов, zero allocation по умолчаниюаллокации на каждый запрос, GC их собирает
Готовые middlewareсвой комплект ядра и contribогромная экосистема http.Handler
Типичный выигрышмелкие ответы, keep-alive, высокие RPSне в скорости, а в совместимости и HTTP/2

Проверьте себя: выбор под нагрузкой

Проект: существующий сервис на net/http, нужен HTTP/2 и переиспользование пары десятков готовых http.Handler-middleware, а нагрузка — пара тысяч запросов в секунду CRUD-профиля. Какой фреймворк выбрать и почему?

fiber benchmark: что и как измерять#

Синтетику вроде TechEmpower, где fasthttp-стеки штурмуют верх plaintext-таблицы, принимайте как описание верхнего потолка, а не вашего сервиса: там нет базы, нет бизнес-логики, а тело ответа — несколько байт. Ваш fiber benchmark должен мерить ваш хендлер на вашей машине, и у Go для этого есть штатный инструмент — тесты с b.N: тестовый урок двадцать первый подарил нам app.Test, который гоняет запрос через полный стек без сети, — идеально для замера, потому что убирает TCP-шум и оставляет ровно то, что вы хотите сравнить. Команда запуска — go test -bench=. -benchmem -benchtime=2s ./internal/handlers/: флаг -benchmem добавит колонки памяти, -benchtime удлинит прогон, чтобы цифры устоялись. Сравнение версий делает benchstat: он берёт два файла вывода, считает разброс и честно говорит, значима ли разница, — без него «стало быстрее на пять процентов» почти всегда шум. И последнее правило гигиены замера: гоняйте бенчмарки без соседей по CPU — контейнер с лимитами из деплой-урока двадцать третьего на перегруженном хосте даст разброс, при котором вы оптимизируете погоду.

Канон: benchmark хендлера через app.Test

GO
// handlers_bench_test.go — замер канонного хендлера списка
package handlers

import (
        "net/http/httptest"
        "testing"

        "github.com/gofiber/fiber/v3"
        "myapi/internal/config"
        "myapi/internal/handlers"
        "myapi/internal/repository"
        "myapi/internal/service"
)

func BenchmarkListTasks(b *testing.B) {
        // сборка канона двадцать второго: память → сервис → хендлер → приложение
        repo := repository.NewMemoryRepo()
        h := handlers.NewTaskHandler(service.NewTasks(repo))
        app := buildApp(h, config.FromEnv()) // вне цикла b.N: сборка — не предмет замера

        b.ReportAllocs() // колонка allocs/op — главный улика
        b.ResetTimer()

        for i := 0; i < b.N; i++ {
                req := httptest.NewRequest("GET", "/api/tasks", nil)
                resp, err := app.Test(req)
                if err != nil {
                        b.Fatalf("app.Test: %v", err)
                }
                if resp.StatusCode != fiber.StatusOK {
                        b.Fatalf("статус: %d", resp.StatusCode)
                }
        }
}

Разбор по строкам, потому что в бенчмарке каждая — деньги. Сборка канона двадцать второго урока — память → сервис → хендлер → приложение — выполняется один раз до цикла: buildApp из двадцать первого урока возвращает готовый инстанс без прослушивания порта, конфиг — FromEnv, как в main. b.ReportAllocs() добавляет в вывод колонку allocs/op даже без флага -benchmem — приучитесь класть её в код, а не в память командной строки. b.ResetTimer() стоит после подготовки, чтобы разводка приложения не попала в замер; b.N Go подберёт сам, раскатывая цикл до устойчивых цифр. Внутри — тот же app.Test, что и в юнит-тестах: запрос строится httptest.NewRequest и проходит полный стек, поэтому замер честно включает middleware и сериализацию. Читаем вывод так: строка BenchmarkListTasks-8 — восьмиядерная машина; ns/op — время на одну итерацию; B/op — байты, выделенные на итерацию; allocs/op — число аллокаций. Наш канонный хендлер отдаст единицы аллокаций: карта конверта, маршалинг, слайс задач — всё своё; движок в этой колонке почти не участвует. Сравнивать версии стоит benchstat-ом: benchstat old.txt new.txt — и только значимая разница считается разницей.

Прочитайте строку бенчмарка

Прочитайте код и выберите, что он выведет:

$ go test -bench=. -benchmem ./internal/handlers/
goos: linux
goarch: amd64
BenchmarkListTasks-8    1000000    1035 ns/op    512 B/op    8 allocs/op

pprof: куда утекают миллисекунды и байты#

Бенчмарк отвечает «сколько», pprof — «куда». Каноническая пара — CPU-профиль и профиль памяти, и в Go оба собирает пакет runtime/pprof, а готовые эндпоинты для них регистрирует net/http/pprof на DefaultServeMux. Здесь таится первая ловушка fiber-мира: ваш процесс — не net/http-сервер, и /debug/pprof сам по себе на :8080 не появится. Решение каноническое и признанное: поднять рядом крошечный net/http-сервер на отдельном локальном порту, где DefaultServeMux отдаст профили. Это не костыль, а гигиена: профилировщик не должен светиться наружу и грузить боевой стек — localhost:6060 слушает только свои, за nginx из деплой-урока его не выносят. Дальше — два рабочих приёма. CPU-профиль под реальной нагрузкой: go tool pprof собирает тридцать секунд выборки и показывает top, где flat — время собственной функции, cum — её же плюс вызванные. Профиль аллокаций: -sample_index=alloc_objects считает количество объектов, alloc_space — байты; первый находит «много мелких», второй — «мало жирных». flame graph (команда web или web в интерактивном pprof) покажет ширину стеков наглядно. И важная дисциплина: профилируйте под нагрузкой, похожей на боевую — профиль на пустом сервере честно покажет, что простаивание бесплатно.

Канон: pprof-сервер рядом с fiber

GO
// cmd/api/main.go — профилировщик на соседнем порту, только localhost
import (
        "net/http"
        _ "net/http/pprof" // side-effect: регистрирует /debug/pprof на DefaultServeMux
)

func main() {
        // fiber на :8080 — боевой стек; pprof на :6060 — свой канал
        go func() {
                log.Println(http.ListenAndServe("localhost:6060", nil))
        }()

        app := buildApp(config.FromEnv())
        log.Fatal(app.Listen(":8080"))
}

Три строки, и каждая важна. Импорт с подчёркиванием — пакеты с side-effect-инициализацией в Go подключают именно так: net/http/pprof в init() вешает на DefaultServeMux хендлеры /debug/pprof/profile, /debug/pprof/heap, /debug/pprof/allocs и десяток соседей; сам пакет в коде не упоминается. http.ListenAndServe("localhost:6060", nil) — nil означает DefaultServeMux, тот самый, куда записался pprof; префикс localhost делает порт недоступным извне — профилировщик на боевом IP это дыра в периметре. Горутина с go — потому что main занят fiber- Listen: два сервера в одном процессе, у каждого свой порт и свой mux. Дальше команды: go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30 — тридцать секунд CPU-выборки под нагрузкой; go tool pprof -sample_index=alloc_objects http://localhost:6060/debug/pprof/heap — счётчик объектов; внутри — top, list по интересной функции (строчки с ценой прямо в исходнике), web для flame graph. Сняли профиль — сервер профилировщика можно и погасить: постоянный pprof в проде не нужен.

Соберите pprof-обвязку и команду замера

Вставьте пропуски так, чтобы профилировщик поднялся на соседнем порту, а бенчмарк считал память и аллокации.

import ( "net/http" _ "net/http/" // регистрирует хендлеры /debug/pprof ) func main() { go func() { log.Println(http.("localhost:6060", nil)) }() // ... } // замер хендлеров с колонками памяти и аллокаций $ go test -=. -mem -benchtime=2s ./internal/handlers/

GC под прицелом: GOGC, GOMEMLIMIT и ReduceMemoryUsage#

Аллокации не бесплатны даже при нуле кадров: чем чаще выделение, тем чаще сборщик мусора выходит на работу, а его смены видны как хвосты задержек. Диагностика начинается с честного счётчика: runtime.ReadMemStats отдаёт NumGC, PauseTotalNs и HeapAlloc — нарисуйте их на графике рядом с RPS, и картина сборки станет видимой; GODEBUG=gctrace=1 печатает в stderr каждую сборку с её цифрами. Регулировок две, и они не равнозначны. GOGC задаёт, во сколько раз куча должна вырасти от последней сборки до следующей: дефолт 100 — сборка при удвоении; поднять — реже собирать и больше есть памяти, опустить — чаще и прожорливее по CPU. GOMEMLIMIT из Go 1.19 — мягкий потолок памяти процесса: GC старается не выходить за лимит, и это спасает контейнер с жёстким лимитом из деплой-урока от смерти по OOM; GOMEMLIMIT=GOGC, GOMEMLIMIT не заменяет, а ограничивает. У fiber есть и собственная ручка — поле ReduceMemoryUsage в конфиге, и его док-комментарий честен: «Aggressively reduces memory usage at the cost of higher CPU usage» — снижает память агрессивно, платя процессором, — и рекомендует включать, только когда сервер ест память на массе простаивающих keep-alive-соединений. Общее правило всех этих ручек одно: сначала уберите аллокации, которые профилирование назвало лишними, — крутить GC вместо исправления кода это поливать огонь бензином с соседнего ведра.

Читаем pprof top

Прочитайте код и выберите, что он выведет:

$ go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30
(pprof) top5
Showing nodes accounting for 2.55s, 85.00% of 3s total
      flat  flat%   sum%        cum   cum%
     2.05s 68.33% 68.33%  2.15s 71.67%  encoding/json.Marshal
     0.20s  6.67% 75.00%  0.25s  8.33%  reflect.Value.Set
     0.15s  5.00% 80.00%  0.30s 10.00%  fasthttp.(*Server).serveConn
     0.10s  3.33% 83.33%  0.12s  4.00%  fiber.(*App).next
     0.05s  1.67% 85.00%  0.06s  2.00%  regexp.Match

Границы применимости: где быстрые пути перестают помогать#

Честный финал разбора — карта мест, где fasthttp не даст обещанного. Первая граница — профиль нагрузки: если время запроса — это база и внешний API, накладные расходы фреймворка — проценты от общего, и никакой движок их не спасёт; fiber производительность выигрывает там, где плечо — мелкие ответы и много keep-alive-соединений: health-чеки, plaintext, метрики, лёгкий JSON-конверт. Вторая граница — большие тела: по умолчанию движок буферизует весь запрос в памяти, BodyLimit по умолчанию 4 MiB, а при больших загрузках это и память, и задержка; поле StreamRequestBody в fiber.Config включает стриминг — док-комментарий говорит, что хендлер вызывается раньше, когда тело больше текущего лимита, — но стриминг вынуждает вас самим следить за тем, что вы читаете. Соседняя ручка DisablePreParseMultipartForm: движок по умолчанию сам парсит multipart-форму — для огромных файлов это лишняя работа, и её выключают, чтобы разбирать форму вручную и лениво, как делал одиннадцатый урок с загрузками. Третья граница — протоколы: HTTP/2 не поддерживается, а значит webpush-приложения за современным CDN и gRPC-овер-HTTP/2 мимо; WebSocket живёт через hijack, как в шестнадцатом уроке, — это решено, но это исключение, а не правило. Четвёртая — экосистема: каждый http.Handler-мост через adaptor съедает часть выигрыша. Ни одна из границ не приговор — это параметры сделки; плохо только не знать о них до выбора.

Проверьте себя: ускорит ли переезд

Профиль fiber-сервиса: 68% — encoding/json.Marshal, 10% — движок, остальное — база. Команда предлагает переехать на gin «потому что fiber тормозит». Что скажет расчёт?

Чек-лист производительности fiber#

  • Baseline до всяких правок: go test -bench=. -benchmem -benchtime=2s и сохранённый вывод для benchstat.
  • b.ReportAllocs() в каждом benchmark-е — колонка allocs/op дороже флага в командной строке.
  • pprof на отдельном localhost-порту рядом с fiber; CPU-профиль 30 секунд под боеподобной нагрузкой.
  • Читаем flat раньше cum: оптимизировать то, что во главе списка, а не то, что раздражает.
  • Ни одной ссылки на значения fiber.Ctx за пределами хендлера: копия string(...) или Immutable: true.
  • Большие тела: StreamRequestBody и DisablePreParseMultipartForm вместо слепого увеличения BodyLimit.
  • Память контейнера: GOMEMLIMIT под лимит из деплой-урока, gctrace=1 при подозрениях, ReduceMemoryUsage — только при толпах idle keep-alive.
  • fiber benchmark против gin — только на вашем плече нагрузки; синтетика TechEmpower — не ваш прод.

Соберите порядок производственной диагностики

Расставьте шаги так, как их делает инженер, который не хочет тратить недели на оптимизацию случайного кода.

Строки

Ваша программа

Нажимайте на строки слева, чтобы собрать программу

Итоги: скорость как дисциплина, а не как маркетинг#

Соберём урок в одну формулу. fasthttp производительность — это переиспользование: пулы контекстов и буферов, UnsafeString-конверсии, ноль аллокаций на горячем пути — и в обмен на это совместимость с net/http, HTTP/2 и ваша дисциплина не хранить ссылки на значения контекста. fiber vs gin — это не таблица бенчмарков, а выбор фундамента: экосистема и протоколы против скорости на узком плече. fiber benchmark — это ваш хендлер, app.Test, -benchmem и benchstat; pprof — это отдельный localhost-порт, тридцатисекундная CPU-выборка и колонка flat во главе решений; GC-ручки — GOGC, GOMEMLIMIT, ReduceMemoryUsage — последнее средство после чистки кода. Границы применимости переписали маркетинг: база и сериализация съедают выигрыш, большие тела требуют стриминга, HTTP/2 мимо. Честное слово про песочницу: ни fasthttp, ни fiber в браузерном интерпретаторе сайта не живут — runner-ов в уроках Fiber нет с первого урока, а интерактивы здесь quiz, predict, fill, order и флешкарты; бенчмарки и профили требуют локального тулчейна и нагрузки, похожей на боевую. Полный маршрут — на странице курса Fiber.

Следующий, двадцать пятый урок — финальный: итоговый проект «REST API заметок на Fiber»: CRUD, валидация, JWT, лимиты — готовое мини-приложение. Логика прямая: сегодня мы научились измерять и понимать движок, завтра соберём из двадцати четырёх уроков один проект, где слои, тесты, аутентификация и деплой-артефакты встретятся в одном main.go — и прогоним его от go mod init до curl через nginx.

Карточки: fasthttp производительность

Термин

Zero memory allocations in hot paths

нажмите, чтобы перевернуть
Определение

Обещание fasthttp из README: пулы контекстов и буферов переиспользуются между запросами. Относится к накладным расходам движка, а не к вашему json.Marshal.

1 / 6

Песочница урока

Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.

Вывод

Запустите код, чтобы увидеть вывод.

Коротко: что запомнить

  • fasthttp производительность — это переиспользование: пулы контекстов и буферов, zero allocation на горячем пути движка; ваши аллокации (json.Marshal, слайсы, карты) остаются вашими и их покажет pprof.
  • fiber аллокации под сделкой: toString по умолчанию — utils.UnsafeString, значения контекста живут до возврата хендлера («Do not store any references»); копии даёт Immutable: true ценой отказа от 0-allocation promise.
  • fiber vs gin — выбор фундамента: net/http даёт HTTP/2 и экосистему http.Handler, fasthttp — скорость на мелких ответах и keep-alive; CRUD-профиль на паре тысяч RPS выбирает по совместимости, а не по синтетике.
  • fiber benchmark — go test -bench=. -benchmem с b.ReportAllocs поверх app.Test; сравнение версий — benchstat; pprof — отдельный localhost-порт с net/http/pprof, читаем flat раньше cum.
  • Границы применимости: база и сериализация нивелируют выигрыш, большие тела требуют StreamRequestBody и DisablePreParseMultipartForm, HTTP/2 движок не поддерживает; GC-ручки GOGC/GOMEMLIMIT/ReduceMemoryUsage — после чистки кода.

Домашнее задание

Измерьте канон курса своими руками. Шаг первый: напишите BenchmarkListTasks по образцу урока — app.Test, b.ReportAllocs, проверка статуса — и сохраните вывод go test -bench=. -benchmem -benchtime=2s в old.txt. Шаг второй: поднимите pprof-сервер на localhost:6060 и снимите CPU-профиль на 30 секунд, одновременно нагружая /api/tasks циклом curl или ab. Шаг третий: в pprof top найдите главную функцию по flat и запишите, сколько процентов забирает сериализация против слоёв fiber и fasthttp. Шаг четвёртый: замените fiber.Map в ответе на структуру с json-тегами и omitempty, перегоните бенчмарк в new.txt и сравните benchstat old.txt new.txt — была ли разница значимой. Шаг пятый: попробуйте сохранить значение c.Params("id") в горутине и прогоните тест с -race — покажет ли детектор гонку или промолчит, объясните в комментарии, почему порча байтов реальна в обоих случаях; затем исправьте копией или Immutable. Бонус: включите GODEBUG=gctrace=1 и посчитайте число сборок до и после оптимизации.

Личные заметкиПусто
Хранятся только в вашем браузере (Fiber · Производительность Fasthttp)

Вопросы и ответы по уроку

Насколько Fiber реально быстрее Gin на продакшн-нагрузке?

На CRUD-сервисе с базой и тяжёлым JSON — обычно в пределах погрешности: время уходят хранилище и сериализация, а они одинаковы для обоих фреймворков. Выигрыш fasthttp раскрывается на плече «мелкие ответы, keep-alive, высокие RPS» — health-чеки, метрики, plaintext. Меряйте свой хендлер бенчмарком из урока и решайте по профилю, а не по таблицам TechEmpower.

Почему нельзя просто сохранить c.Params("id") в глобальную переменную или горутину?

Потому что дефолтная конверсия — UnsafeString: строка не владеет байтами, а указывает в буфер соединения, который движок переиспользует после ответа. Комментарий исходника называет исход «silently corrupted on connection reuse» — тихо испорченные байты без паники и детектора гонок. Копия string(...) или Immutable: true делают значение честным.

Почему /debug/pprof не работает прямо на fiber-порте?

Пакет net/http/pprof регистрирует хендлеры на DefaultServeMux стандартного net/http, а fiber на fasthttp — это другой движок с несовместимыми типами. Каноническое решение — крошечный http.ListenAndServe("localhost:6060", nil) в горутине рядом с боевым приложением: профилировщик слушает только локальный интерфейс и не попадает наружу.

Когда включать GOMEMLIMIT и ReduceMemoryUsage?

GOMEMLIMIT — когда контейнер имеет жёсткий лимит памяти: мягкий потолок заставит GC работать до того, как OOM-killer убьёт процесс; ставьте ниже лимита контейнера. ReduceMemoryUsage — только когда профилирование показало, что память едят массы простаивающих keep-alive-соединений: поле честно предупреждает «at the cost of higher CPU usage». Обе ручки — после того, как чистка кода убрала лишние аллокации.

Термины урока

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

Сравните с альтернативами

Практический вопрос после первых уроков: а почему именно эта библиотека? Мы разобрали конкурентов по одним критериям, с кодом одной задачи рядом и персональным подбором под ваши приоритеты.

Следующая ступень учебного пути

Gin25 уроков~13 чпрактика25 уроков уже доступны

Gin: самый популярный веб-фреймворк Go

Курс Gin для Go: маршрутизация, binding, валидация, middleware, JWT, WebSocket, тесты и деплой. 25 уроков с кодом и итоговым REST API.

  • Уверенно владеть gin.Engine: маршруты, группы, версии
  • Привязывать запросы к структурам (ShouldBindJSON и др.)
  • Писать middleware и использовать встроенные
  • Строить аутентификацию на JWT