Как я пишу HTTP сервисы на Go спустя 13 лет
How I write HTTP services in Go after 13 years
Мэт Райер (Grafana, Go Time podcast) делится опытом, как он пишет HTTP-сервисы после 13 лет работы с Go.
Ключевая мысль статьи: не нужен ни фреймворк, ни DI-магия, всё решается стандартной библиотекой и явными зависимостями.
TL;DR:
-
- Все роуты в одном месте (
-
- Тесты поднимают сервис целиком (с
- Хэндлеры собираются фабриками, middleware пишутся обычными функциями func(h http.Handler) http.Handler — всё просто и прозрачно.
————
🟢 Что я могу сказать.. Такое ощущение, будто статью писал я сам. Хочу, чтобы каждый мой коллега прочитал её дважды ! Я устал регулярно объяснять и защищать все те простые вещи, о которых пишет автор.
Мне даже 13 лет на это не понадобилось, просто учителя были хорошие👴
Единственное, что вызывает вопросы — это «nil-зависимости» в тестах (автор иногда передаёт nil, если зависимость не используется). Я бы всё же предпочёл простые no-op фейки, чтобы не ловить паники внезапно, пусть даже в тестах — поверьте, фиксить тесты без должной гигиены та ещё морока. Пусть сегодня в конкретном кейсе дело не доходит до какой-то зависимости, но завтра дойдёт.
В остальном подход отличный: код простой и читаемый, тесты пишутся легко, нет магии. Отличная статья, особенно для тех, кто только ещё не набил руку в архитектуре сервисов на Go❤️
Для новичков must read, опытным товарищам тоже лишним не будет ознакомиться.
#article #http #architecture #english
How I write HTTP services in Go after 13 years
Мэт Райер (Grafana, Go Time podcast) делится опытом, как он пишет HTTP-сервисы после 13 лет работы с Go.
Ключевая мысль статьи: не нужен ни фреймворк, ни DI-магия, всё решается стандартной библиотекой и явными зависимостями.
TL;DR:
-
NewServer(...) принимает все зависимости конструктором и возвращает http.Handler. Да, список аргументов может быть длинным, и это нормально — зато всё явно.- Все роуты в одном месте (
routes.go), никакой путаницы.-
main() тонкий, реальная логика в run(ctx, ...) — удобно и для тестов, и для graceful shutdown.- Тесты поднимают сервис целиком (с
/readyz и прочим), а окружение передаётся как параметры функции, без обращения к глобальному состоянию (os.Getenv, flag, os.Stdin/Stdout)- Хэндлеры собираются фабриками, middleware пишутся обычными функциями func(h http.Handler) http.Handler — всё просто и прозрачно.
————
Мне даже 13 лет на это не понадобилось, просто учителя были хорошие
Единственное, что вызывает вопросы — это «nil-зависимости» в тестах (автор иногда передаёт nil, если зависимость не используется). Я бы всё же предпочёл простые no-op фейки, чтобы не ловить паники внезапно, пусть даже в тестах — поверьте, фиксить тесты без должной гигиены та ещё морока. Пусть сегодня в конкретном кейсе дело не доходит до какой-то зависимости, но завтра дойдёт.
В остальном подход отличный: код простой и читаемый, тесты пишутся легко, нет магии. Отличная статья, особенно для тех, кто только ещё не набил руку в архитектуре сервисов на Go
Для новичков must read, опытным товарищам тоже лишним не будет ознакомиться.
#article #http #architecture #english
Please open Telegram to view this post
VIEW IN TELEGRAM
Grafana Labs
How I write HTTP services in Go after 13 years | Grafana Labs
Mat Ryer, principal engineer at Grafana Labs and host of the Go Time podcast, shares what he's learned from more than a dozen years of writing HTTP services in Go.
1🔥45❤11🤔7👍5
🔍 Утечка горутин в продакшене — разбор реального кейса
- Оригинальный пост
- Обсуждение на Hacker News
Автор рассказывает про классическую production-проблему: out of memory в k8s-подах посреди во время работы сервиса.
Спойлер: виноваты горутины, которые живут вечно.
Как искали проблему:
- Grafana показала растущее потребление памяти горутинами
- Подключили pprof и какое-то время мониторили результаты
- Нашли фабрику бесконечных горутин🙃
Виновник — функция конвертации канала:
Проблема:
Инструменты для поиска утечек:
- Grafana + Pyroscope — flame-графы памяти
- pprof с diff_base — сравнение снапшотов до/после
- goleak — для автоматического обнаружения в тестах
Главная мысль от автора: не конвертируйте каналы!
Используйте обычные паттерны работы с
————
Что ж, вот вам ещё одно напоминание о том, что Go хоть и простой язык, но конкурентность в нём кусается как везде. И go func() — это не fire-and-forget, как кажется новичкам, а обязательство следить за временем жизни горутины. Опытный разработчик 10 раз подумает перед тем как использовать эту конструкцию где бы то ни было.
#article #concurrency #english
- Оригинальный пост
- Обсуждение на Hacker News
Автор рассказывает про классическую production-проблему: out of memory в k8s-подах посреди во время работы сервиса.
Спойлер: виноваты горутины, которые живут вечно.
Как искали проблему:
- Grafana показала растущее потребление памяти горутинами
- Подключили pprof и какое-то время мониторили результаты
- Нашли фабрику бесконечных горутин
Виновник — функция конвертации канала:
func ToDoneInterface(done <-chan struct{}) <-chan interface{} {
interfaceStream := make(chan interface{})
go func() {
defer close(interfaceStream)
select {
case <-done: return
}
}()
return interfaceStream
}Проблема:
select{} ждёт сигнала из done, но если контекст никогда не отменяется — горутина блокируется навсегда. defer close() там есть, но он не поможет, если select никогда не вернётся.Инструменты для поиска утечек:
- Grafana + Pyroscope — flame-графы памяти
- pprof с diff_base — сравнение снапшотов до/после
- goleak — для автоматического обнаружения в тестах
Главная мысль от автора: не конвертируйте каналы!
Используйте обычные паттерны работы с
context.Context и не забывайте вызывать .Done() там, где нужно.————
Что ж, вот вам ещё одно напоминание о том, что Go хоть и простой язык, но конкурентность в нём кусается как везде. И go func() — это не fire-and-forget, как кажется новичкам, а обязательство следить за временем жизни горутины. Опытный разработчик 10 раз подумает перед тем как использовать эту конструкцию где бы то ни было.
#article #concurrency #english
Please open Telegram to view this post
VIEW IN TELEGRAM
👍26❤10🔥4
Насколько быстр Go? Симуляция миллионов частиц на смарт-ТВ
https://habr.com/ru/articles/953434/
Автор проверил производительность Go на практике — написал симуляцию миллионов частиц с мультиплеером, которая работает только на CPU, причём настолько легковесно, что запускается даже на смарт-телевизоре.
Задача: симулировать миллионы частиц, синхронизировать их между сотнями клиентов через WebSocket, но при этом не заставлять клиент ничего симулировать — только рисовать.
Подход как из GPU-программирования:
- Используется g-buffer из deferred shading: как там отвязывают количество полигонов от расчёта освещения, так и здесь количество частиц отвязывается от объёма передаваемых данных
- Сервер рендерит всё сам и отправляет клиентам готовые кадры-буферы
- Клиент просто рисует пиксели в canvas — работает везде, где есть браузер
- Стоимость передачи фиксирована разрешением экрана, а не числом частиц
Математика:
Для разрешения 1920×1080 при 1 бите на пиксель получается примерно 260 КБ на кадр. При 30 fps это около 7.8 МБ/с (≈ 62 Мбит/с) на одного клиента. На сервере с пропускной способностью 2.5 Гбит/с можно теоретически обслужить порядка 40 клиентов при Full HD, или 300+ клиентов при мобильных разрешениях (например, 640×360).
Оптимизации:
- Воркер-пулы вместо создания горутин в цикле — pprof показал, что создание горутин не бесплатное
- Lookup-таблицы для побитовых операций вместо прямой упаковки/распаковки — ускорило в разы
- Простой трюк с
- Двойная буферизация кадров, каналы для неблокирующей отправки, пул буферов
Результат: 2.5 миллиона частиц на 60 fps симуляции + 30 fps отправка клиентам. Работает на смарт-ТВ.
Попробовать можно тут: howfastisgo.dev
————
Статья очень крутая, рекомендую.
Автор честно признаётся в разочаровании: Go без SIMD в чистых вычислениях проигрывает даже JavaScript, не говоря уж про Rust (который на одном ядре обошёл Go на восьми).
Но Go и не позиционируется как язык для тяжёлых вычислений — он про сетевые сервисы и конкурентность.
Мне понравился подход с g-buffer'ом — изящное решение проблемы масштабирования. Дельта-кодирование с зигзаг-кодированием в итоге забросили из-за сложности кода, но это правильный выбор — иногда простота важнее оптимальности.
#article #performance #websocket
https://habr.com/ru/articles/953434/
Автор проверил производительность Go на практике — написал симуляцию миллионов частиц с мультиплеером, которая работает только на CPU, причём настолько легковесно, что запускается даже на смарт-телевизоре.
Задача: симулировать миллионы частиц, синхронизировать их между сотнями клиентов через WebSocket, но при этом не заставлять клиент ничего симулировать — только рисовать.
Подход как из GPU-программирования:
- Используется g-buffer из deferred shading: как там отвязывают количество полигонов от расчёта освещения, так и здесь количество частиц отвязывается от объёма передаваемых данных
- Сервер рендерит всё сам и отправляет клиентам готовые кадры-буферы
- Клиент просто рисует пиксели в canvas — работает везде, где есть браузер
- Стоимость передачи фиксирована разрешением экрана, а не числом частиц
Математика:
Для разрешения 1920×1080 при 1 бите на пиксель получается примерно 260 КБ на кадр. При 30 fps это около 7.8 МБ/с (≈ 62 Мбит/с) на одного клиента. На сервере с пропускной способностью 2.5 Гбит/с можно теоретически обслужить порядка 40 клиентов при Full HD, или 300+ клиентов при мобильных разрешениях (например, 640×360).
Оптимизации:
- Воркер-пулы вместо создания горутин в цикле — pprof показал, что создание горутин не бесплатное
- Lookup-таблицы для побитовых операций вместо прямой упаковки/распаковки — ускорило в разы
- Простой трюк с
p := &particles[i] вместо particles[i].x дал +30% за счёт меньшего числа bounds checks- Двойная буферизация кадров, каналы для неблокирующей отправки, пул буферов
Результат: 2.5 миллиона частиц на 60 fps симуляции + 30 fps отправка клиентам. Работает на смарт-ТВ.
Попробовать можно тут: howfastisgo.dev
————
Статья очень крутая, рекомендую.
Автор честно признаётся в разочаровании: Go без SIMD в чистых вычислениях проигрывает даже JavaScript, не говоря уж про Rust (который на одном ядре обошёл Go на восьми).
Но Go и не позиционируется как язык для тяжёлых вычислений — он про сетевые сервисы и конкурентность.
Мне понравился подход с g-buffer'ом — изящное решение проблемы масштабирования. Дельта-кодирование с зигзаг-кодированием в итоге забросили из-за сложности кода, но это правильный выбор — иногда простота важнее оптимальности.
#article #performance #websocket
1👍28🔥7❤5
if err != nil: почему мы (не) любим обработку ошибок в Go? Обзор предложений по её улучшению
https://habr.com/ru/companies/avito/articles/944824/
Павел Агалецкий из Авито в очередной раз поднимает вечную холиварную тему — обработку ошибок в Go.
Суть проблемы (а вдруг кто-то не в курсе?🙃 ):
Go не использует исключения (exceptions) — ошибки это просто значения, которые функции возвращают наравне с другими. Код выглядит многословно:
Сравнение с другими языками:
- Python — исключения есть, но из сигнатуры непонятно, выбрасывает метод исключение или нет
- Java — есть checked exceptions, но большинство функций в языке и библиотеках их не декларируют, т.к. это необязательно
- Rust — есть тип
Что было предложено за годы существования Go:
На GitHub 195 закрытых и 14 открытых proposal по улучшению обработки ошибок. Автор группирует их:
1. check/handle — специальные ключевые слова, чтобы обрабатывать ошибки единообразно
2. try/catch — как в других языках
3. Спецсимволы (? или !) — вместо ключевых слов
4. Упрощение if — тернарные операторы и подобное
Все отклонялись по схожим причинам:
- Можно частично реализовать в userland (через panic/recover + defer)
- Ломается при оборачивании ошибок (fmt.Errorf)
- Появляется неявный control flow
- Код становится менее читаемым
Последний proposal от Ian Lance Taylor:
Ян предложил писать знак вопроса после функций, возвращающих ошибки:
С возможностью обработки:
Реакция сообщества неоднозначная, больше против. Дизлайков в полтора раза больше лайков.
Финал истории:
Robert Griesemer опубликовал статью в блоге Go, где команда объявила, что они больше не будут рассматривать предложения по изменению обработки ошибок. Решили, что на этапе создания языка стоило подумать лучше, но сейчас менять поздно — будет только хуже.
————
Автор делает правильные выводы:
Ошибки как обычные значения — это нормально, явная сигнатура — это хорошо, отсутствие исключений — это прекрасно. Да, многословно, но явно. Да, занимает много места, но зато всё понятно. А современные IDE с autocomplete (особенно с LLM) и code folding помогают справляться с многословностью (у меня в IDE вообще давно настроен хоткей для создания error handling блока:
Я полностью согласен. Лучше писать чуть больше, но понимать что происходит, чем получить "волшебный" синтаксис, который будет работать неявно. Go Team приняли мудрое решение — оставить всё как есть. Язык не должен становиться зоопарком из разных способов сделать одно и то же.
Да, в первые годы работы с Go мне тоже хотелось всячески его "облагородить", изобретать разные способы "упрощения" работы с ошибками, но со временем это проходит🙃
P.S. Мне кажется, что все эти годы сообщество пытается запихнуть в Go механики из других языков, вместо того чтобы просто принять философию Go🤡
#article #error_handling #proposals
https://habr.com/ru/companies/avito/articles/944824/
Павел Агалецкий из Авито в очередной раз поднимает вечную холиварную тему — обработку ошибок в Go.
Суть проблемы (а вдруг кто-то не в курсе?
Go не использует исключения (exceptions) — ошибки это просто значения, которые функции возвращают наравне с другими. Код выглядит многословно:
func doSomething() error {
if err := one(); err != nil {
return err
}
if err := two(); err != nil {
return err
}
if err := three(); err != nil {
return err
}
return nil
}Сравнение с другими языками:
- Python — исключения есть, но из сигнатуры непонятно, выбрасывает метод исключение или нет
- Java — есть checked exceptions, но большинство функций в языке и библиотеках их не декларируют, т.к. это необязательно
- Rust — есть тип
Result<T, E> и оператор ? для проброса ошибок (более компактно)Что было предложено за годы существования Go:
На GitHub 195 закрытых и 14 открытых proposal по улучшению обработки ошибок. Автор группирует их:
1. check/handle — специальные ключевые слова, чтобы обрабатывать ошибки единообразно
2. try/catch — как в других языках
3. Спецсимволы (? или !) — вместо ключевых слов
4. Упрощение if — тернарные операторы и подобное
Все отклонялись по схожим причинам:
- Можно частично реализовать в userland (через panic/recover + defer)
- Ломается при оборачивании ошибок (fmt.Errorf)
- Появляется неявный control flow
- Код становится менее читаемым
Последний proposal от Ian Lance Taylor:
Ян предложил писать знак вопроса после функций, возвращающих ошибки:
func doSomething(in Dto) (err error) {
validate(in) ?
one() ?
two() ?
three() ?
return nil
}С возможностью обработки:
validate(in) ? {
return fmt.Errorf("validation failed: %w", err)
}Реакция сообщества неоднозначная, больше против. Дизлайков в полтора раза больше лайков.
Финал истории:
Robert Griesemer опубликовал статью в блоге Go, где команда объявила, что они больше не будут рассматривать предложения по изменению обработки ошибок. Решили, что на этапе создания языка стоило подумать лучше, но сейчас менять поздно — будет только хуже.
————
Автор делает правильные выводы:
Ошибки как обычные значения — это нормально, явная сигнатура — это хорошо, отсутствие исключений — это прекрасно. Да, многословно, но явно. Да, занимает много места, но зато всё понятно. А современные IDE с autocomplete (особенно с LLM) и code folding помогают справляться с многословностью (у меня в IDE вообще давно настроен хоткей для создания error handling блока:
CMD + J).Я полностью согласен. Лучше писать чуть больше, но понимать что происходит, чем получить "волшебный" синтаксис, который будет работать неявно. Go Team приняли мудрое решение — оставить всё как есть. Язык не должен становиться зоопарком из разных способов сделать одно и то же.
Да, в первые годы работы с Go мне тоже хотелось всячески его "облагородить", изобретать разные способы "упрощения" работы с ошибками, но со временем это проходит
P.S. Мне кажется, что все эти годы сообщество пытается запихнуть в Go механики из других языков, вместо того чтобы просто принять философию Go
#article #error_handling #proposals
Please open Telegram to view this post
VIEW IN TELEGRAM
❤41👍30
Forwarded from Николай Тузов
- Источник
- Перевод
Инженеры Cloudflare рассказали детективную историю о том, как они нашли редчайший баг в компиляторе Go, который проявлялся только на arm64 и только при стечении обстоятельств.
Заваривайте кофе или чаёк и устраивайтесь поудобнее, мы начинаем..
Начало истории:
Один из сервисов начал спорадически паниковать на arm64-машинах с ошибкой "traceback did not unwind completely" — признак повреждения стека. Решили, что это редкая проблема с памятью и не стали копать глубже.
Но паники продолжились.
Первая теория:
- Все критические паники происходили при раскрутке стека
- Коррелировали с recovered panic
- В коде было старое использование panic / recover для обработки ошибок
- Есть похожий баг на GitHub
Убрали panic / recover — паники прекратились. Вздохнули с облегчением.
Но через месяц паники вернулись:
Теперь до 30 в день. Без recovered panics вообще. Никакой корреляции с релизами, инфраструктурой, или положением Марса 🪐
Все крэши происходили в
(*unwinder).next — при раскрутке стека. Два типа:1. Явная критическая ошибка от среды выполнения
2.
SIGSEGV при разыменовании указателяЗаметили паттерн: все segfault'ы происходили при асинхронном вытеснении функции
NetlinkSocket.Receive из библиотеки go-netlink.Что такое асинхронное вытеснение:
До Go 1.14 планировщик был кооперативным — горутины сами решали, когда отдать управление. С 1.14 появилось асинхронное вытеснение: если горутина работает больше 10ms, среда выполнения отправляет SIGURG и принудительно вызывает
asyncPreempt.Прорыв:
Удалось получить дамп ядра и посмотреть в отладчике. Горутина была остановлена между двумя инструкциями в эпилоге функции:
nl_linux.go:779 0x555577cb287c ADD $80, RSP, RSP
nl_linux.go:779 0x555577cb2880 ADD $(16<<12), RSP, RSP
nl_linux.go:779 0x555577cb2884 RET
Вытеснение произошло между двумя ADD, которые корректируют указатель стека. Стек оказался в несогласованном состоянии!
Почему две инструкции ADD:
На arm64 непосредственный операнд в инструкции ADD — это 12 бит. Для больших значений компилятор разбивает на две операции:
ADD $x, RSP и ADD $(y<<12), RSP. Если прерывание происходит между ними, RSP указывает в середину стека, а не на его вершину.Простейшее воспроизведение:
//go:noinline
func big_stack(val int) int {
var big_buffer = make([]byte, 1 << 16)
// ... работа с буфером
}
func main() {
go func() {
for { runtime.GC() }
}()
for { _ = big_stack(1000) }
}
Функция со стековым кадром >64KB, GC в цикле для раскрутки стека, и бесконечные вызовы. Через пару минут — segfault!
Суть бага:
1. Асинхронное вытеснение между
ADD x, RSP и ADD (y<<12), RSP2. GC запускает раскрутку стека
3. Раскрутчик пытается прочитать родительский кадр по невалидному RSP
4. Крэш
Условие гонки в одну инструкцию. Невероятно редкий баг!
Исправление:
Для стеков >4KB компилятор теперь сначала строит смещение во временном регистре, потом делает одну атомарную операцию ADD. Вытеснение может произойти до или после, но никогда не во время.
Исправлено в go1.23.12, go1.24.6 и go1.25.0.
————
Очень люблю такие истории!
Месяцы расследования, работа с дампами ядра, изучение внутренностей среды выполнения и ассемблера. И в итоге — баг на уровне компилятора, который проявляется только при стечении обстоятельств: arm64 + большой стек + асинхронное вытеснение + GC
#compiler #debugging
Please open Telegram to view this post
VIEW IN TELEGRAM
The Cloudflare Blog
How we found a bug in Go's arm64 compiler
84 million requests a second means even rare bugs appear often. We'll reveal how we discovered a race condition in the Go arm64 compiler and got it fixed.
🔥49❤10👍7
Выжимаем из Go скорость до последних наносекунд
https://habr.com/ru/companies/ruvds/articles/957756/
Гайд по низкоуровневым оптимизациям Go для вычислительно-интенсивных задач. Автор работал над ускорением функций из
🟠 Это история не про красивый читаемый код, а про ситуации когда нужно выжать всё до последней наносекунды. Как правило, это взаимоисключающие параграфы.
TL;DR проблемы:
- Go может тормозить как Python-скрипт — из-за сборки мусора
- Многопоточный код не нагружает процессор больше 50-60%
- Частые аллокации/деаллокации убивают производительность
- Выделение памяти по природе однопоточное → узкое горлышко
Основные техники оптимизации:
- Избегаем аллокаций — размещаем переменные на стеке вместо кучи, не возвращаем указатели из функций
- Инициализация структур — копирование преаллоцированного экземпляра быстрее конструктора
- Сравнение с нулём быстрее чем с произвольным числом (процессор делает одно логическое И/ИЛИ вместо вычитания)
- Большое bool-выражение в условии на ~20% быстрее цепочки if'ов (но его сложнее написать правильно)
- Избегаем отрицаний в условиях — замена !func() на другую функцию дала +20%
- Множественное присваивание
- int может работать быстрее bool в возвращаемых значениях (странно, но факт)
- Метки и goto иногда быстрее for-циклов для until-подобной логики
Измерение производительности:
- Бенчмарки с фиксированным числом итераций вместо времени
- Измерение в тактах процессора через CGo (rdtsc)
- Смотрим ассемблерный код через go tool objdump
————
Статья хардкорная — сплошные антипаттерны и "грязные трюки". Но когда действительно нужна максимальная производительность, на красоту уже не смотрят, ей жертвуют.
Результат у автора впечатляющий: функция инверсии в 3.4 раза быстрее math.big и всего на 3% медленнее C++. При этом Go остаётся Go с его простотой и сетевыми возможностями.
Для 99% задач такие оптимизации не нужны. Но если пишете что-то вычислительно-интенсивное и каждая наносекунда на счету — может быть полезно. В любом случае, для расширения кругозора точно стоит ознакомиться.
#article #performance #optimization #hardcore
https://habr.com/ru/companies/ruvds/articles/957756/
Гайд по низкоуровневым оптимизациям Go для вычислительно-интенсивных задач. Автор работал над ускорением функций из
math/big и библиотеки uint256 (используется в Ethereum) и делится своим опытом.TL;DR проблемы:
- Go может тормозить как Python-скрипт — из-за сборки мусора
- Многопоточный код не нагружает процессор больше 50-60%
- Частые аллокации/деаллокации убивают производительность
- Выделение памяти по природе однопоточное → узкое горлышко
Основные техники оптимизации:
- Избегаем аллокаций — размещаем переменные на стеке вместо кучи, не возвращаем указатели из функций
- Инициализация структур — копирование преаллоцированного экземпляра быстрее конструктора
- Сравнение с нулём быстрее чем с произвольным числом (процессор делает одно логическое И/ИЛИ вместо вычитания)
- Большое bool-выражение в условии на ~20% быстрее цепочки if'ов (но его сложнее написать правильно)
- Избегаем отрицаний в условиях — замена !func() на другую функцию дала +20%
- Множественное присваивание
z[0], z[1], z[2] = ... работает на десятки процентов быстрее последовательного- int может работать быстрее bool в возвращаемых значениях (странно, но факт)
- Метки и goto иногда быстрее for-циклов для until-подобной логики
Измерение производительности:
- Бенчмарки с фиксированным числом итераций вместо времени
- Измерение в тактах процессора через CGo (rdtsc)
- Смотрим ассемблерный код через go tool objdump
————
Статья хардкорная — сплошные антипаттерны и "грязные трюки". Но когда действительно нужна максимальная производительность, на красоту уже не смотрят, ей жертвуют.
Результат у автора впечатляющий: функция инверсии в 3.4 раза быстрее math.big и всего на 3% медленнее C++. При этом Go остаётся Go с его простотой и сетевыми возможностями.
Для 99% задач такие оптимизации не нужны. Но если пишете что-то вычислительно-интенсивное и каждая наносекунда на счету — может быть полезно. В любом случае, для расширения кругозора точно стоит ознакомиться.
#article #performance #optimization #hardcore
Please open Telegram to view this post
VIEW IN TELEGRAM
👍31🔥6❤2
Forwarded from Николай Тузов
Media is too big
VIEW IN TELEGRAM
Они до сих пор не знают 😩
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥43❤7👍2
Forwarded from Николай Тузов
Ну что, 18-й выпуск GoGetPodcast, да?
Трансляция будет 01.11 (в эту субботу), а через пару дней выйдет запись в хорошем качестве и аудиоверсия, как обычно..
————
Недавно у нас был выпуск с Арсением, где он рассказал много интересного про разработку GoLand. Получилось очень круто
Во время разговора выяснилось, что у Арсения есть много вопросов к дизайну Go. Не абстрактных, а самых что ни на есть конкретных — про те места, где разработчики регулярно
append.Эти темы не вписались в тот выпуск, но они явно заслуживали отдельного разговора, который сулил стать новым крутым выпуском. Особенно если позвать Глеба и Диму — наших постоянных гостей, с которыми мы недавно обсуждали Go v1.25. Мало кто может так же глубоко ответить на такие вопросы.
Да, критики Go хватает, все темы давно изъезжены. Но кто обычно критикует? Сами Go-разработчики которые давно уже привыкли и приняли правила игры? Или разработчики с других платформ, которые Go знают поверхностно?
А вот у Арсения — уникальная позиция. Он опытный инженер, но пишет на Kotlin. При этом работает над IDE для Go, то есть погружён в язык глубже многих гоферов и видит, где они чаще всего наступают на грабли. Ведь его работа — помогать им этого избегать. При этом он сохраняет свежий трезвый взгляд и спрашивает: «А почему вообще так устроено?»
Идеальная комбинация: глубокое знание языка и независимый, непредвзятый взгляд.
Что будем обсуждать:
— Nil safety в Go: почему компилятор не защищает и где это стреляет в проде (кстати, в недавнем выпуске Арсений рассказывал про новую фичу GoLand, которая помогает с этим бороться)
— Слайсы: append выглядит как иммутабельная операция, но мутирует данные
— Замыкания в горутинах: почему они ловят переменные по ссылке и как это приводит к классическим багам
— Shadowing переменных: визуально не отличишь, а линтер не всегда спасает
— Data races при чтении слайсов — да, даже при чтении
— Практические советы: как жить с этим в реальных проектах
И другое
Это не подкаст про хейт Go и не холивар «Go vs Kotlin».
Это честный технический разговор о трейдофах в дизайне языка — о местах, где Go поможет выстрелить в ногу, и о том, как этого избежать.
Ведь хороший инженер спрашивает не «почему тупые авторы этого не предусмотрели?», он спрашивает «ради чего они решили пойти на эту жертву?».
Состав:
- Николай Тузов
- Арсений Терехов — JetBrains,
- Глеб Яльчик
- Дима Матрёничев
————
Предупреждение: после этого разговора ваши код-ревью станут более параноидальными. Но это к лучшему
Заваривайте кофе, будет интересно ☕️
#golang #gogetpodcast
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥20👍5❤2
🍵 Green Tea GC: новый сборщик мусора в Go 1.25
- Источник
- Перевод
Свежая статья в официальном блоге разработчиков Go про экспериментальный сборщик мусора Green Tea, основанная на докладе Michael Knyszek с GopherCon 2025.
————
Go Team представили новый GC, который снижает время работы сборщика мусора на 10-40% в зависимости от нагрузки. Доступен в Go 1.25 через
Как работает классический mark-sweep:
- Mark фаза: обходим граф объектов от корней (глобальные/локальные переменные), помечаем всё достижимое
- Sweep фаза: всё, что не посетили — мусор, освобождаем память
- По сути простой graph flood алгоритм, как обход графа в ширину/глубину
Суть проблемы:
- Около 90% времени GC тратится на marking, только 10% на sweep
- Из этих 90% минимум 35% уходит на простои при обращении к памяти
- Graph flood алгоритм прыгает по всей хипе, выполняя маленькие кусочки работы — CPU не может предсказать следующий шаг
- Плохо использует кэши: два объекта со ссылкой друг на друга могут быть где угодно в памяти
- CPU постоянно стопорится на обращениях к main memory (до 100x медленнее кэша)
Автор использует хорошую аналогию: если ехать на машине по городским улицам — постоянно тормозить на поворотах и светофорах, то большую скорость не набрать.
Что хуже — тренды железа играют против GC: NUMA-архитектуры, снижение memory bandwidth на ядро, всё больше ядер (конкуренция за shared queue), новые фичи типа векторных инструкций не используются.
🟢 Решение Green Tea — гениально простое:
- Работать со страницами памяти, а не с отдельными объектами
- Вместо сканирования объектов — сканируем целые страницы
- Трекаем страницы в work list, а не объекты
- Метаданные о marked объектах хранятся локально для каждой страницы
Результат: делаем меньше, но более длинные проходы по памяти. Это как выехать из города на шоссе — наконец-то можно разогнаться.
Бонусом используют AVX-512 векторные инструкции для турбо-ускорения сканирования — держат всю метаданные страницы в двух 512-битных регистрах прямо на CPU, обрабатывают целую страницу за несколько инструкций. Это было невозможно для graph flood из-за непредсказуемости размеров объектов.
Правда, есть воркллоады, которые не выигрывают или даже регрессируют (когда на странице обычно только один объект для сканирования). Но таких меньшинство, и даже сканирование 2% страницы уже даёт профит над graph flood.
————
🟠 Отличная статья, must read для всех.
Особенно радует, что они не просто предлагают "попробуйте новую фичу", а уже раскатили это внутри Google и просят фидбек. Production-ready, а не просто эксперимент.
P.S. Очень понравилась история происхождения названия — когда Austin делал прототип, он был в Японии и пил матчу литрами 🍵
Я через пару недель собираюсь делать то же самое 🇯🇵 ✨
#go1_25 #go_official #gc #performance
- Источник
- Перевод
Свежая статья в официальном блоге разработчиков Go про экспериментальный сборщик мусора Green Tea, основанная на докладе Michael Knyszek с GopherCon 2025.
————
Go Team представили новый GC, который снижает время работы сборщика мусора на 10-40% в зависимости от нагрузки. Доступен в Go 1.25 через
GOEXPERIMENT=greenteagc при сборке, уже используется в продакшене у Google, планируют сделать дефолтным в Go 1.26.Как работает классический mark-sweep:
- Mark фаза: обходим граф объектов от корней (глобальные/локальные переменные), помечаем всё достижимое
- Sweep фаза: всё, что не посетили — мусор, освобождаем память
- По сути простой graph flood алгоритм, как обход графа в ширину/глубину
Суть проблемы:
- Около 90% времени GC тратится на marking, только 10% на sweep
- Из этих 90% минимум 35% уходит на простои при обращении к памяти
- Graph flood алгоритм прыгает по всей хипе, выполняя маленькие кусочки работы — CPU не может предсказать следующий шаг
- Плохо использует кэши: два объекта со ссылкой друг на друга могут быть где угодно в памяти
- CPU постоянно стопорится на обращениях к main memory (до 100x медленнее кэша)
Автор использует хорошую аналогию: если ехать на машине по городским улицам — постоянно тормозить на поворотах и светофорах, то большую скорость не набрать.
Что хуже — тренды железа играют против GC: NUMA-архитектуры, снижение memory bandwidth на ядро, всё больше ядер (конкуренция за shared queue), новые фичи типа векторных инструкций не используются.
- Работать со страницами памяти, а не с отдельными объектами
- Вместо сканирования объектов — сканируем целые страницы
- Трекаем страницы в work list, а не объекты
- Метаданные о marked объектах хранятся локально для каждой страницы
Результат: делаем меньше, но более длинные проходы по памяти. Это как выехать из города на шоссе — наконец-то можно разогнаться.
Бонусом используют AVX-512 векторные инструкции для турбо-ускорения сканирования — держат всю метаданные страницы в двух 512-битных регистрах прямо на CPU, обрабатывают целую страницу за несколько инструкций. Это было невозможно для graph flood из-за непредсказуемости размеров объектов.
Правда, есть воркллоады, которые не выигрывают или даже регрессируют (когда на странице обычно только один объект для сканирования). Но таких меньшинство, и даже сканирование 2% страницы уже даёт профит над graph flood.
————
Особенно радует, что они не просто предлагают "попробуйте новую фичу", а уже раскатили это внутри Google и просят фидбек. Production-ready, а не просто эксперимент.
P.S. Очень понравилась история происхождения названия — когда Austin делал прототип, он был в Японии и пил матчу литрами 🍵
#go1_25 #go_official #gc #performance
Please open Telegram to view this post
VIEW IN TELEGRAM
❤25🔥13👍6🤔2
Forwarded from Николай Тузов
https://youtu.be/8vdBg8jlx2M
Уже не в первый раз это говорю, но.. Пожалуй, это один из лучших выпусков нашего подкаста (что поделать, выпуски становится всё лучше и лучше).
У Арсения были очень интересные вопросы и свежий взгляд, а Глеб с Димой, прекрасно на них отвечали, дополняя друг друга.
Получилась беседа с глубоким погружением в философию и внутреннее устройство языка.
————
Этот выпуск вышел при поддержке AvitoTech 💙
Наш подкаст очень нишевой, и его довольно сложно развивать. Поэтому поддержка крайне важна и ценна. Особенно круто, что они ни как не вмешиваются в производство и не мешают автору заниматься творчеством.
Только благодаря им выпуски теперь будут выходить стабильно и регулярно. Больше никакого ожидания по полгода!
Ребята также ведут свой блог на хабре и проводят митапы. Недавно я делился их отличной статьей про обработку ошибок в Go.
Please open Telegram to view this post
VIEW IN TELEGRAM
YouTube
Тёмные стороны Go — с разработчиком GoLand | GoGetPodcast №18
Обсуждаем проблемные места Go с Арсением Тереховым — разработчиком из команды GoLand в JetBrains. Nil safety, слайсы, замыкания, shadowing и другие спорные места — разбираем, где в Go легко выстрелить себе в ногу и почему так происходит.
Арсений смотрит…
Арсений смотрит…
4🔥12👍7❤5
https://habr.com/ru/articles/960654/
Автор столкнулся с классической проблемой: его сервис жрёт память, и оказалось, что почти 30 МБ из них уходят на... метрики. Точнее, на Summary метрики из prometheus/client_golang, которые вычисляют квантили на стороне приложения.
Решил попробовать альтернативу — библиотеку github.com/VictoriaMetrics/metrics.
Чем отличается:
У Виктории проще API:
- Нет разделения на векторы и скаляры
- Имя метрики и лейблы идут вместе в одной строке:
metric_name{label1="value1",label2="value2"}- Рекомендуется группировать метрики по Set'ам (удобно для организации)
- Не поддерживает HELP метадату (VictoriaMetrics её игнорирует)
Кастомные коллекторы:
- У Prometheus есть Collector интерфейс из коробки
- У Виктории придётся писать самому (запускать по таймеру, собирать статистику)
- Метрики нужно явно дерегистрировать при удалении
Результат:
- Экономия 25-30% памяти (квантили считаются через более легковесную библиотеку)
- Но выросла нагрузка на GC (из-за создания строк при каждом обновлении метрики)
————
Честно говоря, VictoriaMetrics как-то всегда проходила мимо меня, поэтому с интересом почитал статью. Неплохая альтернатива устоявшемуся решению. Виктория явно минималистичнее и проще, но за это придётся платить гибкостью — нет удобных коллекторов, нет нормальной поддержки хуков для всех типов метрик (а надо ли?).
В общем, если у вас много Summary метрик и память поджимает — стоит посмотреть. Для остальных случаев Prometheus вполне достаточен.
#article #metrics #prometheus #victoriametrics
Please open Telegram to view this post
VIEW IN TELEGRAM
👍13❤4🔥4🤔3
This media is not supported in your browser
VIEW IN TELEGRAM
Идём на Avito Infra DrinkUp 12 ноября, без вариантов ☄️
Коллеги из Авито зовут на встречу по инфраструктуре. Обещают брейншторм об инструментах IaC, разработке в SRE, базах данных, Kubernetes и многом другом. Но есть подвох: никаких записей и трансляций — только офлайн, только хард-кор.
Как будто итог один — пропускать нельзя. Советуем уже сейчас кликать по ссылке и регистрироваться с коллегами и друзьями, пока не закончились места.
#промо #текст_прислан
Коллеги из Авито зовут на встречу по инфраструктуре. Обещают брейншторм об инструментах IaC, разработке в SRE, базах данных, Kubernetes и многом другом. Но есть подвох: никаких записей и трансляций — только офлайн, только хард-кор.
Как будто итог один — пропускать нельзя. Советуем уже сейчас кликать по ссылке и регистрироваться с коллегами и друзьями, пока не закончились места.
#промо #текст_прислан
Please open Telegram to view this post
VIEW IN TELEGRAM
❤4👍3🔥2🤔1
https://www.datadoghq.com/blog/engineering/go-swiss-tables/
Nayef Ghattas из Datadog рассказывает про неожиданный сюрприз после обновления на Go 1.24. Это продолжение их истории про memory regression, но с хорошим концом.
TL;DR: после обновления на Go 1.24, словили баг с утечкой памяти, но при этом в высоконагруженных средах потребление памяти внезапно СОКРАТИЛОСЬ на сотни мегабайт. Виновник — новая реализация map через Swiss Tables.
Суть проблемы:
В Datadog используют огромную мапу
shardRoutingCache (3.5 миллиона элементов в высоконагруженных окружениях) для роутинга данных по шардам. После обновления на Go 1.24 эта мапа стала занимать ~500 MiB меньше в памяти.Старая реализация (Go 1.23):
- Hash table с бакетами, по 8 слотов в каждом
- Load factor максимум 13/16 (81.25%)
- При росте мапы старые бакеты висят в памяти, пока не скопируются инкрементально
- Overflow buckets — цепочки дополнительных бакетов при переполнении основных
Итог: 726 MiB для 3.5M элементов
Новая реализация (Go 1.24) — Swiss Tables:
- Данные хранятся в группах по 8 слотов с control word (8 байт)
- Control word — это 8 байт метаданных, каждый байт хранит последние 7 бит хеша ключа
- SIMD-оптимизация: сравнение хеша со всеми 8 слотами за одну CPU-инструкцию
- Load factor теперь 7/8 (87.5%) — меньше пустого места
- Нет overflow buckets — просто идём в следующую группу
- Extendible hashing: мапа делится на независимые таблицы по 128 групп максимум
Итог: 217 MiB для тех же 3.5M элементов
Математика:
Go 1.23: ~1.5M бакетов × 464 байта = 726 MiB
Go 1.24: ~3900 таблиц × 58 KiB = 217 MiB
Экономия: ~509 MiB live heap (≈1 GiB RSS с учётом GOGC)
Бонусный раунд оптимизации:
Авторы заметили, что в структуре Response хранятся неиспользуемые поля: пустая строка RoutingKey и nil-указатель LastModified. Плюс ShardType был int (8 байт) для enum из 3 значений.
Что сделали:
- ShardType: int → uint8 (1 байт вместо 8)
- Создали отдельный тип cachedResponse без лишних полей
- Key-value пара: 56 байт → 24 байта
Результат: ещё 250 MiB RSS экономии на под, 200 TiB памяти сэкономили по всему флоту.
————
Отличная статья, показывающая насколько важны детали в Go. Swiss Tables — это не просто "новая реализация map", это значимая экономия ресурсов в проде.
Из забавного: в low-traffic окружениях экономия была всего ~28 MiB, что не покрыло регрессию из mallocgc. Зато в high-traffic всё окупилось с лихвой.
P.S. SIMD для map пока только на amd64, для arm64 работа в процессе. Но даже без SIMD Swiss Tables экономят память за счёт более высокого load factor и отсутствия переполнения бакетов.
#article #go1_24 #performance #memory #english
Please open Telegram to view this post
VIEW IN TELEGRAM
1❤15👍11🔥10🤔1
Бесплатные программы «Менеджер:101» и «Директор:101» от Стратоплана
Менеджер или директор — отдельные роли, которые предполагают соответствующие знания, навыки, привычки и образ мыслей. И мы хотим помочь вам сделать первый шаг в сторону транзишна к этим ролям, потому задумали наши проекты абсолютно бесплатными.
Если вы — менеджер, ключевой вызов — переключиться из майндсета исполнителя в майндсет руководителя. Для этого нужно понять, из чего на самом деле состоит роль управленца, каковы его фокусы и как действовать в непростых управленческих ситуациях.
Если вы становитесь директором, перед вами новая реальность: теперь никто не скажет, как правильно — решения принимаете вы. Вам нужно не просто реагировать на проблемы, а стратегически смотреть в будущее компании.
И совсем этим мы будем разбираться по ссылкам ниже:
Пройти регистрацию на Менеджер:101
Пройти регистрацию на Директор:101
🗓 Даты и время:
Менеджер:101— 17-18 ноября, с 16 по 19 GMT+3
Директор:101— 19-20 ноября, с 16 по 19 GMT+3
На выходе:
✔️ инструменты, которые можно забрать в работу завтра
✔️ сертификат, который можно добавить себе в Linkedin
✔️ материалы от лучшей Школы для руководителей в 2024 по результатам исследования Devcrowd
Присоединяйтесь к нашим открытым проектам, как это сделали уже 14843 человека с начала этого года!
#промо #текст_прислан
Менеджер или директор — отдельные роли, которые предполагают соответствующие знания, навыки, привычки и образ мыслей. И мы хотим помочь вам сделать первый шаг в сторону транзишна к этим ролям, потому задумали наши проекты абсолютно бесплатными.
Если вы — менеджер, ключевой вызов — переключиться из майндсета исполнителя в майндсет руководителя. Для этого нужно понять, из чего на самом деле состоит роль управленца, каковы его фокусы и как действовать в непростых управленческих ситуациях.
Если вы становитесь директором, перед вами новая реальность: теперь никто не скажет, как правильно — решения принимаете вы. Вам нужно не просто реагировать на проблемы, а стратегически смотреть в будущее компании.
И совсем этим мы будем разбираться по ссылкам ниже:
Пройти регистрацию на Менеджер:101
Пройти регистрацию на Директор:101
🗓 Даты и время:
Менеджер:101— 17-18 ноября, с 16 по 19 GMT+3
Директор:101— 19-20 ноября, с 16 по 19 GMT+3
На выходе:
✔️ инструменты, которые можно забрать в работу завтра
✔️ сертификат, который можно добавить себе в Linkedin
✔️ материалы от лучшей Школы для руководителей в 2024 по результатам исследования Devcrowd
Присоединяйтесь к нашим открытым проектам, как это сделали уже 14843 человека с начала этого года!
#промо #текст_прислан
👍15🔥9❤7🤔1