Forwarded from Николай Тузов
Golang Дайджест
Bubble Tea — TUI-фреймворк для Go https://habr.com/ru/articles/939574/ Коротенький обзор фреймворка Bubble Tea. Сам обзор ни чем особо не примечателен, но фреймворк шикарный, очень рекомендую. Если вы любите красивые консольные утилиты. вам точно понравится.…
Раз уж на то пошло, советую обратить внимание вообще на все проекты Charm, там много интересного. Например:
- Bubbles — готовые компоненты для Bubble Tea: текстовые поля, списки, таблицы, прогресс-бары, спиннеры.
- Lip Gloss — библиотека стилей для терминальных приложений. Цвета, рамки, отступы, выравнивание — всё как в CSS, только для консоли. Используется как база для Bubble Tea.
- Gum — эдакий мост между Bubbles / Lip Gloss и shell-скриптами. То есть, можно делать те же красивые скрипты, но без кода на Go.
- Glow — рендеринг Markdown прямо в терминале с подсветкой синтаксиса и красивым форматированием. Можно читать README не выходя из консоли.
- VHS — записывает GIF/видео демонстраций терминальных приложений через простой скрипт. Идеально для документации и README.
- Soft Serve — self-hosted Git-сервер с TUI интерфейсом. Можно красиво смотреть репозитории, коммиты и файлы прямо через SSH
Ну и наше любимое:
- Crush — красивый консольный ИИ-агент . Поддерживает разные LLM (Claude, GPT, Gemini), использует LSP для контекста как в IDE, можно переключаться между моделями на лету.
Это только самое интересное, но там ещё много всякого. Советую пройтись по всему списку проектов и ознакомиться с каждым.
Вся экосистема построена вокруг идеи: терминал может быть красивым и удобным. И у них это отлично получается
Теперь подумываю сделать большое обзорное видео про всё это. Будет ли вам такое интересно?
#cli #tui
Please open Telegram to view this post
VIEW IN TELEGRAM
GitHub
Charm
We make the command line glamorous. Charm has 52 repositories available. Follow their code on GitHub.
1👍18🔥4❤2
🐳 Container-aware GOMAXPROCS: Go наконец-то видит лимиты контейнеров
Пост в официальном блоге разработчиков Go, где они подробно рассказывают суть проблемы и как устроено недавнее техническое решение.
Go Team решили давнюю проблему совместимости с контейнерами. До версии 1.25 Go определял все ядра хоста, не учитывая CPU limits контейнера. Теперь GOMAXPROCS выставляется в соответствии с лимитами автоматически.
Суть проблемы:
- Контейнеру выделено 2 CPU на машине с 128 ядрами
- Go видит 128 ядер, создаёт кучу тредов
- Linux throttling тормозит приложение каждые 100ms
- Tail latency растёт
Что изменилось в 1.25:
- Go читает cgroup CPU limits и ставит GOMAXPROCS по ним
- Динамически обновляется при изменении лимитов
- Работает из коробки — просто обновите go.mod
- CPU requests игнорируются (только limits)
————
Наконец-то.. Проблеме много лет, и всё это время приходилось костылить через uber/automaxprocs или ENV-переменные. А теперь оно работает из коробки, как и должно было уже давно.
P.S. В Java эту проблему решили ещё в 2017 году😩
#go1_25 #go_official #kubernetes #docker
Пост в официальном блоге разработчиков Go, где они подробно рассказывают суть проблемы и как устроено недавнее техническое решение.
Go Team решили давнюю проблему совместимости с контейнерами. До версии 1.25 Go определял все ядра хоста, не учитывая CPU limits контейнера. Теперь GOMAXPROCS выставляется в соответствии с лимитами автоматически.
Суть проблемы:
- Контейнеру выделено 2 CPU на машине с 128 ядрами
- Go видит 128 ядер, создаёт кучу тредов
- Linux throttling тормозит приложение каждые 100ms
- Tail latency растёт
Что изменилось в 1.25:
- Go читает cgroup CPU limits и ставит GOMAXPROCS по ним
- Динамически обновляется при изменении лимитов
- Работает из коробки — просто обновите go.mod
- CPU requests игнорируются (только limits)
————
Наконец-то.. Проблеме много лет, и всё это время приходилось костылить через uber/automaxprocs или ENV-переменные. А теперь оно работает из коробки, как и должно было уже давно.
P.S. В Java эту проблему решили ещё в 2017 году
#go1_25 #go_official #kubernetes #docker
Please open Telegram to view this post
VIEW IN TELEGRAM
go.dev
Container-aware GOMAXPROCS - The Go Programming Language
New GOMAXPROCS defaults in Go 1.25 improve behavior in containers.
❤22🔥18🤯2🤔1
Николай Тузов
https://youtu.be/fHuJNsZPCJ0
Этого ролика вам точно будет достаточно для полного понимания нововведений, вне зависимости от вашего опыта
Подробно обсудили, что нового в новой версии, зачем это нужно, как оно устроено, в сложных местах делали ликбез для лучшего понимания изменений.
Дима был очень хорош, такого подробного и глубокого разбора вы больше нигде не увидите, а с комментариями Глеба оно ещё круче, очень рекомендую.
#gogetpodcast #podcast
Please open Telegram to view this post
VIEW IN TELEGRAM
YouTube
Go v1.25 — Глубокий разбор всех изменений | GoGetPodcast №17
Подробно обсудили - что добавилось в новой версии Go 1.25, зачем всё это нужно, как с этим работать.
Go 1.25 Release Notes: https://tip.golang.org/doc/go1.25
Состав:
- Николай Тузов
- Глеб Яльчик
- Дмтрий Матрёничев, ведёт шикарный ТГ-канал по Go: http…
Go 1.25 Release Notes: https://tip.golang.org/doc/go1.25
Состав:
- Николай Тузов
- Глеб Яльчик
- Дмтрий Матрёничев, ведёт шикарный ТГ-канал по Go: http…
🔥20👍6❤3
Forwarded from Go Update
🏗️ gogrep — инструмент для семантического поиска внутри вашей кодовой базы. 🏗️
Наверняка много кому приходилось сталкиваться с ситуацией «надо по коду найти вызовы по определенному паттерну». Чаще всего для этого мы используем регулярные выражения (grep, ag, rg). Однако у них всех есть один минус — они интерпретируют файлы как текст, а не как код. Из-за этого в результаты поиска попадают как нужные нам места вызова, так и комментарии, участки текста и прочая.
Решение этой проблемы: семантический поиск. Это когда утилита разбивает файл на синтаксическое дерево и производит поиск уже по самому дереву. Приведу простой пример:
Здесь мы явно говорим: найди все вызовы, у которых слева есть идентификатор
Документация по gogrep доступна тут. С описанием синтаксиса немного сложнее: большую (и лучшую) часть информации по мэтчингу и по фильтрам можно найти в тестах. Сама тулза является часть куда более мощной тулзы go-ruleguard (которая кстати входит в golangci-lint).
За обе утилиты огромнейшее спасибо Искандеру Шарипову.
Наверняка много кому приходилось сталкиваться с ситуацией «надо по коду найти вызовы по определенному паттерну». Чаще всего для этого мы используем регулярные выражения (grep, ag, rg). Однако у них всех есть один минус — они интерпретируют файлы как текст, а не как код. Из-за этого в результаты поиска попадают как нужные нам места вызова, так и комментарии, участки текста и прочая.
Решение этой проблемы: семантический поиск. Это когда утилита разбивает файл на синтаксическое дерево и производит поиск уже по самому дереву. Приведу простой пример:
~/mws/api > gogrep . 'ptr.Get($_)'
Здесь мы явно говорим: найди все вызовы, у которых слева есть идентификатор
ptr а внутри вызова идет только один аргумент (не важно выражение, их сумма, или переменная). Таким образом мы идентифицируем именно конкретные места которые будут частью компилируемого кода.Документация по gogrep доступна тут. С описанием синтаксиса немного сложнее: большую (и лучшую) часть информации по мэтчингу и по фильтрам можно найти в тестах. Сама тулза является часть куда более мощной тулзы go-ruleguard (которая кстати входит в golangci-lint).
За обе утилиты огромнейшее спасибо Искандеру Шарипову.
GitHub
GitHub - quasilyte/gogrep: Syntax-aware Go code search, based on the mvdan/gogrep
Syntax-aware Go code search, based on the mvdan/gogrep - quasilyte/gogrep
🔥11👍1
Go Update
🚀 Расширение функции new для создания указателя на значения 🚀 Отличные новости! Предложение, обсуждение которого которого длится уже больше четырех лет и которое выдвинул сам Роб Пайк, наконец-то подходит к принятию. В чем суть: есть у нас встроенная функция…
Наконец-то можно будет выкинуть из проектов мой "любимый" lo.ToPtr()
Правда остаётся ещё lo.FromPtr(), но надежда уже есть.
Если что, мне жутко не нравится идея тащить в проект samber/lo, но не всегда удаётся с этим бороться. При этом, я даже не знаю что хуже — тащить этого монстра ради пары строк кода, или же желание использовать ещё больше его функционала😩
К слову, в предыдущем выпуске подкаста тема `lo` очень подробно обсуждалась в самом конце. Советую послушать, если нужны аргументы.
Правда остаётся ещё lo.FromPtr(), но надежда уже есть.
Если что, мне жутко не нравится идея тащить в проект samber/lo, но не всегда удаётся с этим бороться. При этом, я даже не знаю что хуже — тащить этого монстра ради пары строк кода, или же желание использовать ещё больше его функционала
К слову, в предыдущем выпуске подкаста тема `lo` очень подробно обсуждалась в самом конце. Советую послушать, если нужны аргументы.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7🔥3❤2
Forwarded from Go Update
✔️ errors.AsType — типобезопастная замена errors.As ✔️
Тем временем, в 1.26 нас (вероятно) ждет еще одно приятное изменение: дженерики наконец доберутся до пакета errors.
Все изменение проще описать тремя строчками кода. В 1.25 у нас вот так:
А в 1.26 можно будет вот так:
Вроде и небольшое изменение, но оно ведет, как минимум, к двум положительным вещам:
• Зона видимости типизированной ошибки во многих участках у нас теперь будет меньше, а значит меньше захламляется пространство имен и снижается необходимость думать над правильным именем для ошибки.
• В отличии от errors.As, который вторым аргументом принимал
Кстати, причина по которой сигнатура текущей функции выглядит как
компиляцию не пройдет. А причина в том, что интерфейсы у нас это отдельная сущность которая существует не только во время компиляции, но и во время выполнения.
Тем временем, в 1.26 нас (вероятно) ждет еще одно приятное изменение: дженерики наконец доберутся до пакета errors.
Все изменение проще описать тремя строчками кода. В 1.25 у нас вот так:
var pe *fs.PathError
if errors.As(err, &pe) {
fmt.Println("Failed at path:", pe.Path)
}
А в 1.26 можно будет вот так:
if pe, ok := errors.AsType[*fs.PathError](err); ok {
fmt.Println("Failed at path:", pe.Path)
}
Вроде и небольшое изменение, но оно ведет, как минимум, к двум положительным вещам:
• Зона видимости типизированной ошибки во многих участках у нас теперь будет меньше, а значит меньше захламляется пространство имен и снижается необходимость думать над правильным именем для ошибки.
• В отличии от errors.As, который вторым аргументом принимал
any, новая функция принимает только тех, кто реализует интерфейс error. Несмотря на то, что у нас есть проверка внутри go vet проверяющая второй аргумент у As, всегда приятнее когда компилятор может самостоятельно поймать ошибку на этапе сборки приложения.Кстати, причина по которой сигнатура текущей функции выглядит как
As(err error, target any) bool заключается в том, что указатель на интерфейс и указатель на тип реализующий интерфейс для компилятора две несовместимые конструкции. Иначе говоря, вот такой код
func As(err error, target *error) bool {
panic("unimplemented")
}
…
pe *fs.PathError
if As(err, &pathError) {
fmt.Println("Failed at path:", pathError.Path)
}
…
компиляцию не пройдет. А причина в том, что интерфейсы у нас это отдельная сущность которая существует не только во время компиляции, но и во время выполнения.
GitHub
errors: AsType (As with type parameters) · Issue #51945 · golang/go
Currently in 1.18 and before, when using the errors.As method, an error type you would like to write into must be predeclared before calling the function. For example: var myErr *MyCustomError if e...
🔥30❤4
Forwarded from go-with-me
В предыдущем посте мы немного напутали с определением такого многопоточного паттерна как Fan Out
На деле мы показывали Tee, который распространяет одно и то же значение V из канала-источника на N каналов-потребителей
Отличие Fan Out от Tee в том, что на N каналов распространяются разные значения из одного канала-источника. Тобишь, воркеры тянут значения из одного канала, борясь за них насмерть
Лирическое отступление закончено, наша совесть чиста, а сегодняшняя тема будет посвящена Fan In
Этот паттерн является обратным для Fan-Out. Мы собираем данные из нескольких каналов-источников и направляем их в один общий канал-потребитель
1. Default
Итак, что мы имеем?
— Есть воркеры — они кладут значения в N каналов и являются продьюсерами
— Каждый из этих N каналов будет получать значения от своего продьюсера. Назовем такие каналы "стоковыми"
— Есть один общий канал out, туда будет нужно отправить все значения из стоковых каналов
— Для этого мы запускаем N потоков, каждый из которых слушает свой стоковый канал, куда кладет значения продьюсер и редиректит все значения в out
Playground пример
Но что же будет, если какой-то наш продьюсер потух и больше не шлет никаких значений, а передаггый контекст не отменен? Как бы нам понять, что воркер не является активным и перезапустить его? — в этом нам поможет такой механизм как "Heartbeats"
Heartbeat — это регулярное сообщение от продьюсера/воркера, подтверждающее, что он жив и работает
2. Heartbeats
Приступим к рассмотрению этого чуда!
Основная идея проста:
— Имеем структуру, которая хранит в себе стоковый канал, используемый как пайп между воркером и стоком, и канал "сердцебиений"
— Функция Supervise ответственна за отслеживание "сердцебиений" и перезапуск воркера при их отсутствии по TTL
— Функция FanIn принимает на вход стоковые каналы и возвращает результирующий канал, из которого можно читать данные
Всмотримся в наши функции поподробнее
2.1. FanIn
— Не отклоняемся от цели: выкачиваем данные из стоковых каналов и перекладываем в out, реагируя на контекст и неблокирующе отправляя "сердцебиение" нашему супервизору, который пристально наблюдает за нашими воркерами
— WaitGroup здесь так же используется для того, чтобы дождаться конца работы наших стоков и отдать управление основному потоку
после дренажа всех "живых" значений
2.2 Supervise
— Создаем стоковый канал и канал "сердцебиений", агрегируем эти значения в структуре Source и возвращаем ее
— В отдельном потоке запускаем нашу рутину по отслеживанию и перезапуску воркеров
2.2.1 Смотрим на внутренности запущенного потока внутри Supervise
— Изначально происходит создание дочернего контекста с отменой для нашего воркера. Этот контекст будет рулить в тот момент, когда наш TTL пройдет и надо будет потушить воркера
— Создаем ticker, который будет слать ивенты, семантически значащие следующее: "в нашего воркера стреляли и он упал в лужу на..."
После получения ивента мы отменяем контекст и воркер окончательно "задыхается в луже"
— Первично запускаем работягу в отдельном потоке
— Если ловим ивент от тикера: производим отмену контекста, переназначаем этот же контекст и функцию отмены, сбрасываем таймер, и запускаем нового воркера в отдельном потоке
— В случае, когда из стокового потока нам пришло "сердцебиение" мы просто сбрасываем таймер и движемся дальше!
Стоит отметить, что воркеры должны "реагировать" на переданный контекст. Без этого мы получим утечку потоков и черт его знает, чем нам это грозит (профилированием и
устранением проблемы, которой можно было бы и избежать)
Таким образом, мы получаем более надежный Fan In, где все источники данных контролируемы и восстанавливаемы при зависаниях
Playground пример
Статью писали с Дашей: @dariasroom
Stay tuned
Please open Telegram to view this post
VIEW IN TELEGRAM
2👍10❤7🔥4🤯1
conc — удобные примитивы для конкурентного кода
https://github.com/sourcegraph/conc
⭐️ ~10.1k
Небольшая библиотека от Sourcegraph, которая предлагает более безопасные и удобные обёртки над стандартными инструментами конкурентности в Go.
Впервые встретил её на своей текущей работе, и в целом мне понравилось, поэтому грех не поделиться.
Что в ней есть интересного:
- conc.WaitGroup — альтернатива стандартному sync.WaitGroup: ловит паники в горутинах и возвращает их в Wait(), а также не даёт забыть вызвать Wait()
- pool — конкурентный пул задач с ограничением числа горутин (WithMaxGoroutines), сбором ошибок (WithErrors, WithFirstError) и отменой задач при ошибках (WithContext)
- stream — параллельное выполнение задач с сохранением порядка результатов (удобно, когда порядок важен, но хочется параллелизма)
- iter.Map / ForEach — упрощённые хелперы для конкурентной обработки слайсов
🟠 Библиотека пока pre-1.0 (последний релиз в феврале 2023), API может меняться.
#library #concurrency #goroutines
https://github.com/sourcegraph/conc
⭐️ ~10.1k
Небольшая библиотека от Sourcegraph, которая предлагает более безопасные и удобные обёртки над стандартными инструментами конкурентности в Go.
Впервые встретил её на своей текущей работе, и в целом мне понравилось, поэтому грех не поделиться.
Что в ней есть интересного:
- conc.WaitGroup — альтернатива стандартному sync.WaitGroup: ловит паники в горутинах и возвращает их в Wait(), а также не даёт забыть вызвать Wait()
- pool — конкурентный пул задач с ограничением числа горутин (WithMaxGoroutines), сбором ошибок (WithErrors, WithFirstError) и отменой задач при ошибках (WithContext)
- stream — параллельное выполнение задач с сохранением порядка результатов (удобно, когда порядок важен, но хочется параллелизма)
- iter.Map / ForEach — упрощённые хелперы для конкурентной обработки слайсов
#library #concurrency #goroutines
Please open Telegram to view this post
VIEW IN TELEGRAM
GitHub
GitHub - sourcegraph/conc: Better structured concurrency for go
Better structured concurrency for go. Contribute to sourcegraph/conc development by creating an account on GitHub.
❤15👍11🤯2
Как я пишу 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