Релиз Golang 1.25 за 10 минут — что улучшили и добавили в новой версии языка
Обзор заметок релиза Go 1.25. Изменения в инструментах, среде выполнения, компиляторе, компоновщике и стандартной библиотеке.
1К открытий4К показов
Релиз Golang 1.25 направлен на производительность и улучшение инструментов. Добавили новый сборщик мусора, автонастройку GOMAXPROCS для контейнеров, пакеты testing/synctest, json/v2. В публикации собрали самые заметные изменения Go 1.25 и рассказали про сценарии их применения.
Сборщик мусора Green Tea
Приложения на Go создают огромное количество мелких объектов. Сборщик мусора тратит время на обработку этих объектов, из-за чего в приложении возникают паузы GC. Новый сборщик должен решить проблему использования памяти.
Green Tea — это альтернативный алгоритм сборки мусора. Он настроен на борьбу с мелкими объектами. Пока что это экспериментальная функция, судьба Green Tea зависит от результатов тестирования сообществом.
Green Tea очищает последовательные блоки памяти (батчи). Размер блоков 8 КБ, в них хранятся объекты до 512 МБ. Объекты больше этого лимита обрабатываются по старому алгоритму.
Green Tea показывает впечатляющие результаты в тестах производительности. Например, 32-кратное ускорение маркировки в алгоритмах обхода графов. Green Tea GC эффективнее использует преимущества последовательного размещения данных в памяти.
В нагрузочных тестах с миллионом объектов Green Tea использовал на 22% меньше CPU, на 8% меньше потреблял память. Время выполнения тоже улучшилось на 5%.
Время пауз в основном остаётся коротким, но 99-й процентиль показал более длительные остановки — 1.84 мс против 0.92 мс у классического GC. То есть в редких случаях всё же могут возникать заметные задержки.
Пакет synctest
Ситуация: вы написали код с таймерами и хотите его протестировать. Паузы длятся по несколько секунд — совсем немного. Проблема в том, что у нас 100 тестов, т. е. одна итерация тестирования займёт несколько минут.
До synctest у вас было на выбор четыре сценария:
- пропускать тесты,
- ждать все таймауты,
- создавать интерфейсы-обёртки,
- передавать функции времени как параметры.
В Golang 1.25 можно сделать проще — обернуть тест в synctest.Run() и пропустить время ожидания.
Как работает synctest
Функция подменяет системное время. Например, когда код вызывает time.Sleep(5 * time.Second), виртуальные часы переводятся на 5 секунд вперёд, и выполнение теста мгновенно продолжается.
Ограничения
Synctest работает только со стандартными функциями. Подменять системное время бесполезно, если ваш код обращается, например, к PostgreSQL, или читает timestamp из файла.
Пакет encoding/json/v2
Разработчики Golang не стали изменять существующий пакет, чтобы сохранить обратную совместимость. Вместо этого добавили вторую версию пакета — encoding/json/v2.
По скорости новая версия сравнима с внешними библиотеками вроде jsoniter. При декодировании v2 быстрее предыдущей версии в 3-10 раз. Ещё json/v2 в 38,6 раза быстрее читает JSON.
Прямая работа с Reader и Writer
Функции MarshalWrite и UnmarshalRead работают напрямую с io.Writer и io.Reader. Они экономят память при обработке большого JSON, т. к. не создаются промежуточные байтовые буферы Encoder и Decoder.
Настройки прямо при вызове
В encoding/json/v2 можно передавать опции прямо в функции маршалинга.
Например, добавлять отступы для читаемости, представлять числа как строки в месте вызова. Раньше для каждого варианта форматирования создавали отдельный Encoder.
Новые теги для структур
inlineвстраивает поля вложенной структуры на уровень родителя — больше не нужно дублировать поля адреса в структуре пользователя.caseстрого сопоставляет имена полей при парсинге JSON — с настройкой «ignore» программа будет считать одинаковыми userName, user_name и UserName. С настройкой «strict» потребует точного совпадения регистра и символов.unknownсобирает все неизвестные поля в одну map — удобно для работы с динамическими API, где набор полей может меняться.formatуказывает формат для времени, дат и других типов прямо в структуре.
Кастомные преобразователи без изменения типов
В Go 1.24 для кастомной логики сериализации приходилось создавать новый тип и реализовывать Marshaler/Unmarshaler. Теперь можно написать функцию-преобразователь через MarshalFunc и использовать её только там, где нужно.
Изменения в поведении по умолчанию
encoding/json/v2 может сломать существующий код:
- Nil-слайсы теперь кодируются как пустые массивы [] вместо null, nil-карты — как пустые объекты {} вместо null.
- Байтовые массивы автоматически кодируются в base64-строки, а не в массивы чисел.
- При декодировании имена полей сравниваются с учётом регистра — поле «name» в JSON не найдёт поле Name в структуре.
Улучшена потоковая обработка
Добавили функции UnmarshalDecode и MarshalEncode для работы с большими JSON-файлами, где данные идут потоком (логи, экспорты БД). Они работают с jsontext.Decoder и jsontext.Encoder, обрабатывая по одному JSON-объекту за раз без загрузки файла в память.
Композиция настроек и маршалеров
Опции и маршалеры объединяются через JoinOptions и JoinMarshalers. Т. е. можно создавать переиспользуемые наборы настроек для разных сценариев.
Например, один набор для внешнего API с красивым форматированием, другой для внутренних сервисов с компактным выводом.
Метод WaitGroup.Go
Добавили новый метод Go(), чтобы упростить работу с горутинами. Изменение небольшое, но очень полезное — ваш код в версии 1.25 станет чище и безопаснее.
Метод упаковывает часто используемый код в удобную функцию. WaitGroup.Go() автоматически вызывает wg.Add(1), запускает переданную функцию в новой горутине и добавляет defer wg.Done() в начало выполнения функции.
Work Pattern для go.work
Представьте, что у вас есть большой проект с множеством микросервисов: сервис A, сервис B, сервис C и так далее. Раньше, чтобы запустить тесты во всех сервисах, приходилось писать длинные команды, явно указывая каждый модуль:
В Go 1.25 можно просто написать:
И тесты запустятся автоматически во всех модулях, указанных в файле go.work.
Трассировка runtime/trace.FlightRecorder
Трейсинг в Go — это дорогое удовольствие. Обычное трассирование записывает всё подряд с самого начала до конца программы. Flight Recorder работает по-другому:
- использует круговой буфер — как кольцевая память;
- хранит только последние события программы;
- автоматически удаляет старую информацию;
- в результате получается компактный файл с только нужными данными.
FlightRecorder вызовы функций, активность горутин, время выполнения операций, использование памяти, работу сборщика мусора.
Группировка атрибутов в slog
За группировку атрибутов отвечает метод slog.Group(). Он принимает второй параметр типа []any, куда можно передать абсолютно любые данные — строки, числа, объекты. Проблема в том, что компилятор не знает, действительно ли вы передаёте корректные атрибуты для логирования.
В Go 1.25 появился метод groupAttrs, который принимает строго типизированный слайс атрибутов []slog.Attr. В этом случае компилятор сможет проверить корректность данных на этапе компиляции.
Если попытаетесь добавить в слайс что-то кроме slog.Attr, получите ошибку компиляции.
GOMAXPROCS учитывает запуск в контейнерах
Эта переменная окружения определяет максимальное количество потоков ОС для одновременного выполнения горутин. По умолчанию GOMAXPROCS равняется количеству логических процессоров на машине.
Например, ваше приложение запущено в Docker-контейнере с ограничением 2 CPU на сервере с 16 ядрами. В старых версиях Go пытается использовать все 16 ядер. Чтобы приложение соблюдало ограничения, разработчики вынужденно ставили стороннюю библиотеку automaxprocs.
В новой версии Go автоматически:
- Читает настройки cgroups.
- Определяет доступное количество CPU для контейнера.
- Устанавливает GOMAXPROCS в соответствии с этими ограничениями.
Если контейнер ограничен 2 CPU, Go автоматически установит GOMAXPROCS=2, независимо от количества ядер у физического сервера.
Исправление бага в компиляторе Go 1.21+
Компилятор переставляет местами строки кода во время компиляции. Если вы писали код, который использовал результат функции без предварительной проверки ошибки, то могли наткнуться на этот баг.
Например, вы открываете файл и сразу пытаетесь с ним работать. По логике Go, если файл не открылся, вы должны получить панику при попытке обратиться к nil-указателю:
В Go 1.21-1.24 компилятор «оптимизировал» код, переставляя строку name = f.Name() после проверки ошибки. Поэтому программа завершалась без паники.
В Go 1.25 компилятор выдаёт «invalid memory address or nil pointer dereference», как и должно быть.
Ждёте стабильную версию GC Green Tea в Golang 1.26? Расскажите в комментариях, как новый сборщик показал себя на реальных рабочих нагрузках — другим разработчикам будет интересно почитать про ваш опыт.
1К открытий4К показов




