LINUX.ORG.RU

Go 1.24

 ,


0

7

Новый выпуск языка Go, версия 1.24, выходит спустя шесть месяцев после Go 1.23. Большинство изменений в реализации тулчейна, рантайма и библиотек. Как всегда, релиз обеспечивает обещание совместимости Go 1. Разработчики языка ожидают, что почти все программы Go продолжат компилироваться и работать как прежде.

Изменения в языке

Go 1.24 теперь полностью поддерживает обобщённые алиасы типов: алиас типа может быть параметризирован как объявленный тип. Подробности в спецификации языка. Пока возможность может быть отключена установкой GOEXPERIMENT=noaliastypeparams; однако опция aliastypeparams будет удалена в Go 1.25.

Инструменты

Команда go

Модули go теперь могут отслеживать исполняемые зависимости используя директиву tool в go.mod. Это убирает необходимость в предыдущем обходном решении по добавлению инструментов как пустых импортов в файл, обычно называемый “tools.go”. Команда go tool теперь может запускать эти инструменты в добавок к инструментам поставляемым вместе с Go. Больше информации можно увидеть в документации.

Новый флаг -tool для go get приводит к добавлению директивы инструмента в текущий модуль для указанных пакетов в добавок к добавлению директив требования.

Новый мета-паттерн tool ссылается на все инструменты в текущем модуле. Это может быть использовано для обновления их всех через go get tool или для установки их в свою GOBIN директорию через go install tool.

Исполняемые файлы созданные через go run и новое поведение go tool теперь кэшируются в кэше сборки Go. Это делает повторяющиеся запуски за счёт увеличенного кэша. #69290.

Команды go build и go install теперь принимают флаг -json, который сообщает вывод и ошибки сборки как структурированный вывод JSON в стандартном выводе. Детали формата можно увидеть в go help buildjson.

Более того, go test -json теперь сообщает вывод и ошибки сборки в JSON, вперемешку с JSON’ом результата тестирования. Их можно различить по новым типам Action, но если они вызывают проблемы в системе интеграции тестов, можно откатиться к текстовому выводу сборки через настройку GODEBUG gotestjsonbuildtext=1.

Новая переменная окружения GOAUTH предоставляет гибкий способ авторизовывать приватные стягивания модулей. Увидеть подробности можно в go help goauth.

Команда go build теперь устанавливаем версию основного модуля в скомпилированном бинарнике, основываясь на теге и/или коммите системы контроля версии. Суффикс +dirty будет добавлен при наличии незакоммиченных изменений. Можно использовать флаг -buildvcs=false для того, чтобы опустить информацию контроля версии из бинарника.

Новая настройка GODEBUG toolchaintrace=1 теперь может быть использована для отслеживания процесса выбора тулчейна в команде go.

Cgo

Cgo поддерживает новые аннотации для функций C для улучшения производительности времени выполнения. #cgo noescape cFunctionName говорит компилятору, что переданная в функцию C cFunctionName память не ускользает. #cgo nocallback cFunctionName говорит компилятору, что функция C cFunctionName обратно не вызывает никаких функций Go. Большую информацию можно увидеть в документации cgo.

Cgo на данный момент отказывается компилирован вызовы С функции, которая имеет несколько несовместимых объявлений. Например, если f объявлен как одновременно void f(int) и void f(double), cgo сообщит ошибку вместо возможной генерации неправильной последовательности вызова f(0). Новым в этом релизе является улучшенное обнаружение этого условия ошибки, когда несовместимые объявления проявляются в разных файлах. #67699.

Objdump

Инструмент objdump теперь поддерживает дизассемблирование на 64-битном LoongArch (GOARCH=loong64), RISC-V (GOARCH=riscv64) и S390X (GOARCH=s390x).

Vet

Новый анализатор tests сообщает распространённые ошибки в объявлениях тестов, фаззеров, бенчмарков и примеров в тестовых пакетах, такие как неправильно сформированные имена, неверные сигнатуры или примеры, которые документируют несуществующие идентификаторы. Некоторые из этих ошибок могут привести к невыполнению тестов.

Существующий анализатор printf теперь сообщает диагностику вызовов формы fmt.Printf(s), где s — строка неконстантного формата, без других аргументов. Такие вызовы почти всегда являются ошибкой, поскольку значение s может содержать символ %; взамен используйте fmt.Print. 60529. Эта проверка имеет тенденцию делать находки в существующем коде, и поэтому применяется только когда версия языка (как указано директивой go файла go.mod или комментариями `//go:build) по крайне мере Go 1.24, чтобы избежать возникновения сбоев продолжительной интеграции при обновлении на тулчейн Go 1.24.

Существующий анализатор buildtag теперь сообщает диагностику, когда есть неправильное ограничение сборки старшей версии Go в директиве //go:build. Например, //go:build go1.23.1 ссылается на точечный релиз; взамен используйте //go:build go1.23. #64127.

Существующий анализатор copylock теперь сообщает диагностику, когда переменная, объявленая в тройном “for” цикле, таком как for i := iter(); done(i); i = next(i) { … }, содержит sync.Locker, такой как sync.Mutex. Go 1.22 изменил поведение таких циклов на создание новой переменной на каждую итерацию, копируя значения с предыдущей итерации; это копирование небезопасно для локов. #66387.

GOCACHEPROG

Внутренний бинарник cmd/go и механизм кэширования тестов теперь могут быть реализованы дочерними процессами, реализующими протокол JSON между инструментом cmd/go и дочерним процессом, названным переменной окружения GOCACHEPROG. Прежде это была за GOEXPERIMENT. Детали протокола можно увидеть в документации.

Время исполнения

Несколько улучшений производительности в рантайм сократили накладные расходы на CPU на 2-3% в среднем среди набора репрезентативных бенчмарков. Результаты могут варьироваться в зависимости от приложения. Эти улучшения включают новую встроенную реализацию map на основе Шведских Таблиц, более эффективного выделения памяти маленьких объектов и новой внутренней рантаймовой реализации мьютекса.

Новая встроенная реализация map и новый внутренний рантаймовый мьютекс могут быть отключены настройками GOEXPERIMENT=noswissmap и GOEXPERIMENT=nospinbitmutex во время сборки соответственно.

Компилятор

Компилятор уже запрещал определять новые методы с типами получателя, которые были сгенерированы cgo, но было возможно обойти это ограничение через алиас типа. Go 1.24 теперь всегда сообщает ошибку, если получатель обозначает сгенерированный cgo тип, напрямую или косвенно (через тип алиаса).

Линкер

Линкер теперь генерирует идентификатор сборки GNU (запись ELF NT_GNU_BUILD_ID) на платформах ELF и UUID (команда загрузки Mach-O LC_UUID) на macOS по-умолчанию. Идентификатор сборки или UUID выводится из идентификатора сборки Go. Это может быть выключено флагом линкера -B none, либо переопределено флагом линкера -B 0xNNNN с указанным пользователем шестнадцатеричным значением.

Раскрутка

Как было указано в заметках релиза Go 1.22, Go 1.24 теперь требует для раскрутки Go 1.22.6 или позже. Разработчики ожидают, что Go 1.26 будет требовать для раскрутки точечный релиз Go 1.24 или позже.

Стандартная библиотека

Ограниченный директорией доступ к файловой системе

Новый тип os.Root предоставляет возможность выполнять операции файловой системы в рамках определённой директории.

Функция os.OpenRoot открывает директорию и возвращает os.Root. Методы на os.Root оперируют в этой директории и не позволяют путям ссылаться на локации за пределами директории, включая те, которые следуют символическим ссылкам за пределы директории. Методы на os.Root отражают большинство операций файловой системы доступных в пакете os, включая, к примеру, os.Root.Open, os.Root.Create, os.Root.Mkdir и os.Root.Stat.

Новая функция бенчмарка

Бенчмарки теперь могут использовать более быстрый и менее подверженный ошибкам метод testing.B.Loop для совершения итераций бенчмарка вроде for b.Loop() { … } заместо типичных циклических структур с участием b.N вроде for range b.N. Это предлагает два значительных преимущества:

  • Функция бенчмарка исполняется точно один раз на каждый -count, таким образом дорогостоящие шаги установки и очистки исполняются только единожды.
  • Параметры вызова функции и результаты продолжают жить, предотвращая компилятор от полной оптимизации тела цикла.

Улучшенные финализаторы

Новая функция runtime.AddCleanup является механизмом завершения, который более гибок, более эффективен и менее подвержен ошибкам чем runtime.SetFinalizer. AddCleanup прикрепляет функцию очистки к объекту, которая запустится как только объект станет недоступным. Однако, в отличие от SetFinalizer, несколько очисток могут быть прикреплены к одному объекту, очистки могут быть прикреплены к внутренним указателям, очистки обычно не вызывают утечки, когда объекты формируют цикл, и очистки не откладывают освобождение объекта или объектов, на которые тот указывает. Новый код должен предпочитать AddCleanup перед SetFinalizer.

Новый пакет weak

Новый пакет weak предоставляет слабые указатели.

Слабые указатели являются низкоуровневым примитивом, предоставляемым для создания структур, эффективно использующих память, таких как слабые словари для сопоставления значений, словарей канонизации для чего угодно, что не покрыто пакетом unique, и различных видов кэшей. Для поддержки этих юзкейсов, данный релиз также предоставляет runtime.AddCleanup и maphash.Comparable.

Новый пакет crypto/mlkem

Новый пакет crypto/mlkem реализует ML-KEM-768 и ML-KEM-1024.

ML-KEM является пост-квантовым механизмом обмена ключом, ранее известный как Kyber и специфицированный в FIPS 203.

Новые пакеты crypto/hkdf, crypto/pbkdf2 и crypto/sha3

Новый пакет crypto/hkdf реализует основанную на HMAC функцию вывода ключа “Extract-and-Expand” HKDF, как определено в RFC 5869.

Новый пакет crypto/pbkdf2 реализует основанную на пароле функцию вывода ключа PBKDF2, как определено в RFC 8018.

Новый пакет crypto/sha3 реализует хэш-функцию SHA-3 и SHAKE и cSHAKE функции расширяемого вывода, как определено в FIPS 202.

Все три пакета основаны на существующих ранее пакетах golang.org/x/crypto/….

Комплаенс FIPS 140-3

Этот релиз включает новый набор механизмов для обеспечения комплаенса FIPS 140-3.

Криптографический модуль Go является набором внутренних пакетов стандартной библиотеки, которые прозрачно используются для реализации одобренных FIPS 140-3 алгоритмов. Приложения не требуют изменений для использования криптографического модуля Go для одобренных алгоритмов.

Новая переменная окружения GOFIPS140 может быть использована для выбора версии криптографического модуля Go для использования в сборке. Новая настройка GODEBUG fips140 может быть использована для включения режима FIPS 140-3 во время исполнения.

Go 1.24 включает криптографический модуль Go версии v1.0.0, который в текущий момент тестируется с аккредитованной CMVP лабораторией.

Новый экспериментальный пакет testing/synctest

Новый экспериментальный пакет testing/synctest предоставляет поддержку тестирования конкурентного кода.

  • Функция synctest.Run запускает группу горутин в изолированном «пузыре». В пузыре функции пакета time оперируют на ложных часах.
  • Функции synctest.Wait ждут когда все горутины заблокируются в текущем пузыре.

Подробности можно увидеть в документации пакета.

Пакет synctest является экспериментальным и должен быть включен установкой GOEXPERIMENT=synctest. API пакета может измениться в будущих релизах. В #67434 можно увидеть больше подробностей и предоставить обратную связь.

Минорные изменения в библиотеке

archive

Реализации (*Writer.AddFS) в archive/zip и archive/tar теперь пишут заголовок директории для пустой директории.

bytes

Пакет bytes добавляет несколько функций, которые работают с итераторами:

  • Lines возвращает итератор по разделённым новой линией строкам в слайсе байтов.
  • SplitSeq возвращает итератор по всем подслайсам слайса байтов, разделённого сепаратором.
  • SplitAfterSeq возвращает итератор по подслайсам слайса байтов, разделённого после каждого вхождения сепаратора.
  • FieldsSeq возвращает итератор по подслайсам слайса байтов вокруг последовательностей символов пробела, как определено unicode.IsSpace
  • FieldsFuncSeq возвращает итератор по подслайсам слайса байтов вокруг последовательностей кодовых точек юникода, удовлетворяющих предикату.

crypto/aes

Значение, возвращаемое NewChipher больше не реализует методы NewCTR, NewGCM, NewCBCEncrypter и NewCBCDecrypter. Эти методы были незадокументированы и не были доступны на всех архитектурах. Теперь значение Block должно передаваться напрямую в соответствующие функции crypto/cipher. На данный момент crypto/cipher всё ещё проверяет эти методы на значениях Block, даже если они больше не поддерживаются стандартной библиотекой.

crypto/cipher

Новая функция NewGCMWithRandomNonce возвращает AEAD, который реализует AES-GCM, генерируя случайный одноразовый номер во время Seal и добавляя его в начало зашифрованного текста.

Реализация Stream, возвращаемая NewCTR при использовании с crypto/aes теперь в несколько раз быстрее на amd64 и arm64.

NewOFB, NewCFBEncrypter и NewCFBDecrypter теперь объявлены устаревшими. Режимы OFB и CFB неаутентифицированы, что в общем случае позволяет активным атакам манипулировать и восстанавливать открытый текст. Приложениям рекомендуется использовать AEAD взамен. Если неаутентифицированный режим Stream необходим, можно использовать NewCTR взамен.

crypto/ecdsa

PrivateKey.Sign теперь создаёт детерминистичную сигнатуру в соответствии с RFC 6979, если источник случайности nil.

crypto/md5

Значение, возвращаемое md5.New, теперь также реализует интерфейс encoding.BinaryAppender.

crypto/rand

Функция Read теперь гарантирует отсутствие неудач. Если Read встретит ошибку во время чтения Reader, программа безвозвратно завершит работу. Обратите внимание, что умолчальный Reader задокументирован всегда работать успешно, поэтому это изменение должно затронуть только те программы, которые переопределяют переменную Reader. Одним исключением являются ядра Linux до версии 3.17, где умолчальный Reader всё ещё открывает /dev/urandom и может потерпеть неудачу.

На Linux 6.11 и позже Reader теперь использует системный вызов getrandom через vDSO. Это в несколько раз быстрее, обычно для небольших чтений.

На OpenBSD Reader теперь использует arc4random_buf(3).

Новая функция Text теперь может генерировать криптографически безопасные случайные строки текста.

crypto/rsa

GenerateKey теперь возвращает ошибку если запрошен ключ длинной менее 1024 битов. Все методы Sign, Verify, Encrypt и Decrypt теперь возвращают ошибку, если используются с ключом размера менее 1024 битов. Такие ключи небезопасны и не должны использоваться. Настройка GODEBUG rsa1024min=0 восстанавливает старое поведение, но разработчики Go рекомендуют делать это только при необходимости и только в тестах, например добавляя строку //go:debug rsa1024min=0 в файл теста. Новый пример GenerateKey предоставляет простой для использования стандартный 2024-битный тестовый ключ.

Теперь безопасно и более эффективно вызывать PrivateKey.Precompute до PrivateKey.Validate. Precompute теперь быстрее в присутствии частично заполненного PrecomputedValues, например при извлечении ключа из JSON.

Пакет теперь отклоняет больше неправильный ключей, даже когда Validate не вызывается, и GenerateKey теперь может вернуть новые ошибки для сломанных источников случайности. Поля Primes и Precomputed структуры PrivateKey теперь используются и валидируются даже когда некоторые значения отсутствуют. Также внесены изменения в crypto/x509 по разбору и извлечению ключей RSA, описанные ниже.

SignPKCS1v15 и VerifyPKCS1v15 теперь поддерживают SHA-512/224, SHA-512/256 и SHA-3.

GenerateKey теперь использует немного другой метод для генерации приватной экспоненты (функция Кармайкла вместо функции Эйлера). Редкие приложения, которые извне пересоздают ключи только из простых чисел, могут создать разные, но совместимые результаты.

Операции над публичными и приватными ключами теперь до двух раз быстрее на wasm.

crypto/sha*

crypto/subtle

Новая функция WithDataIndependentTiming позволяет пользователю исполнять функцию с включенными функциями, специфичными для архитектуры, которые гарантируют неизменность определённых инструкций относительно времени значения данных. Это может быть использовано для того, чтобы убедиться в том, что код, созданный для работы за константное время, не был оптимизирован функциями уровня процессора таким образом, что он работает в переменное время. На данный момент WithDataIndependentTiming использует бит PSTATE.DIT на arm64 и ничего не делает на всех остальных архитектурах. Настройка GODEBUG dataindependenttiming=1 включает режим DIT на всю программу Go.

Вывод XORBytes должен перекрываться полностью или никак с вводом. Ранее поведение было неопределено в противном случае, тогда как сейчас XORBytes будет паниковать.

crypto/tls

Сервер TLS теперь поддерживает Encrypted Client Hello (ECH). Эта возможность может быть включена заполнением поля Config.EncryptedClientHelloKeys.

Новый пост-квантовый механизм обмена ключом X25519MLKEM768 теперь поддерживается и включен по умолчанию когда Config.CurvePreferences является nil. Настройка GODEBUG tlsmlkem=0 возвращает дефолт.

Поддержка экспериментального обмена ключом X25519Kyber768Draft00 была убрана.

Порядок обмена ключом теперь обрабатывается полностью пакетом crypto/tls. Порядок Config.CurvePreferences теперь игнорируется, а содержимое используется только для определения того, какие обмены ключом включить, когда поле заполнено.

Новое поле ClientHelloInfo.Extensions перечисляет список идентификаторов расширений, полученных в сообщении Client Hello. Это может быть полезно для снятия отпечатков клиентов TLS.

crypto/x509

Настройка GODEBUG x509sha1 была убрана. Certficicate.Verify больше не поддерживает подписи, основанные на SHA-1.

OID теперь реализует интерфейсы encoding.BinaryAppender и encoding.TextAppender.

Умолчальное поле политик сертификата было изменено с Certificate.PolicyIdentifiers на Certificate.Policies. При разборе сертификатов оба поля будут заполнены, но при создании политики сертификатов будут взяты из поля Certificate.Policies вместо Certificate.PolicyIdentifiers. Это изменение может быть возвращено настройкой GODEBUG x509usepolicies=0.

CreateCertificate теперь будет генерировать серийный номер используя RFC 5280 совместимый метод при передаче шаблона полем Certificate.SerialNumber nil, вместо сбоя.

Certificate.Verify теперь поддерживает валидацию политики, как определено в RFC 5280 и RFC 9618. Новое поле VerifyOptions.CertificatePolicies может быть установлено на приемлемый набор политик OIDs. Только цепи сертификатов с валидными графами политик будут возвращены из Certificate.Verify.

MarshalPKCS8PrivateKey теперь возвращает ошибку вместо извлечения неправильного ключа RSA. (MarshalPKCS1PrivateKey не имеет возвращения ошибки и его поведение при предоставленных неправильных ключах продолжает быть неопределено.)

ParsePKCS1PrivateKey и ParsePKCS8PrivateKey теперь используют и валидируют закодированные значения CRT, поэтому могут отклонять неверные ключи RSA, которые были ранее принятыми. Использование настройки GODEBUG x509rsacrt=0 возвращает к пересчитыванию значений CRT.

debug/elf

Пакет debug/elf добавляет поддержку обработки версий символов в динамичных файлах ELF (Executable and Linkable Format). Новый метод File.DynamicVersions возвращает список динамичных версий, определённых в файле ELF. Новый метод File.DynamicVersionNeeds возвращает список динамических версий, требуемых этим файлом ELF, которые определены в других объектах ELF. Наконец, новые поля Symbol.HasVersion и Symbol.VersionIndex указывают версию символа.

encoding

Два новых интерфейса TextAppender и BinaryAppender были введены для добавления текстового или бинарного представления объекта к слайсу байтов. Эти интерфейсы предоставляют такую же функциональность, что и TextMarshaler и BinaryMarshaler, но вместо выделения нового слайса каждый раз, они добавляют данные напрямую в существующий слайс. Эти интерфейсы сейчас реализованы типами стандартной библиотеки, которые уже реализуют TextMarshaler и/или BinaryMarshaler.

encoding/json

При сборке поле структуры с новой опцией omitzero в теге поля структуры будет опущено, если его значением является ноль. Если тип поля имеет метод IsZero() bool, он будет использован для определения, является ли значение нулём. Иначе значение будет нулём если оно нулевое значение для его типа. Тег поля omitzero чище и менее подвержен ошибкам чем omitempty, когда намерением является опустить нулевые значения. В частности, в отличие от omitempty, omitzero опускает нулевые time.Time значения, что является частым источником проблем.

Если указаны оба omitempty и omitzero, поле будет опущено если значение пустое или нулевое (или сразу вместе).

UnmarshalTypeError.Field теперь включает встроенные структуры для предоставления более детальных сообщений ошибки.

go/types

Все структуры данных go/types, которые раскрывают последовательности пары методов, как Len() int и At(int) T, теперь также имеют методы, которые возвращают итераторы, позволяя упростить код подобный этому:

params := fn.Type.(*types.Signature).Params()
for i := 0; i < params.Len(); i++ {
  use(params.At(i))
}

На этот:

for param := range fn.Signature().Params().Variables() {
  use(param)
}

Методы: Interface.EmbeddedTypes Interface.ExplicitMethods Interface.Methods MethodSet.Methods Named.Methods Scope.Children Struct.Fields Tuple.Variables TypeList.Types TypeParamList.TypeParams Union.Terms

hash/*

log/slog

Новый DiscardHandler является обработчиком, который никогда не включен и всегда отбрасывает свой вывод.

Level и LevelVar теперь реализуют интерфейс encoding.TextAppender.

math/*

net

ListenCondig теперь использует MPTCP по умолчанию на системах, где это поддерживается (пока только Linux).

IP теперь реализует интерфейс encoding.TextAppender.

net/http

Изменилось ограничение Transport на полученные информационные ответы 1xx в ответ на запрос. Ранее это останавливало запрос и возвращало ошибку после получения более 5 ответов 1xx. Теперь это возвращает ошибку только если весь размер всех ответов 1xx превышает настройку конфигурации Transport.MaxResponseHeaderBytes.

Кроме того, когда запрос имеет хук для отслеживания net/http/httptrace.ClientTrace.Got1xxResponse, теперь нет ограничения на общее число ответов 1xx. Хук Got1xxResponse может вернуть ошибку для остановки запроса.

Transport и Server теперь имеют поле HTTP2, которое разрешает конфигурирование настроек протокола HTTP/2.

Новые поля Server.Protocols и Transport.Protocols предоставляют простой способ сконфигурировать какие протоколы HTTP сервер или клиент используют.

Сервер и клиент могут быть сконфигурированы поддерживать незашифрованные подключения HTTP/2.

Когда Server.Protocols содержит UnencrypterHTTP2, сервер примет подключения HTTP/2 на незашифрованные порты. Сервер может принять сразу HTTP/1 и незашифрованный HTTP/2 на один и тот же порт.

Когда Transport.Protocols содержит UnencryptedHTTP2 и не содержит HTTP1, траспорт будет использовать незашифрованный HTTP/2 для адресов http://. Если транспорт сконфигурирован использовать сразу HTTP/1 и незашифрованный HTTP/2, он будет использовать HTTP/1.

Поддержка незашифрованного HTTP/2 использует «HTTP/2 с предварительным знанием» (RFC 9113, секция 3.3). Устаревший заголовок “Upgrade: h2c” не поддерживается.

net/netip

Addr, AddrPort и Prefix теперь реализуют интерфейсы encoding.BinaryAppender и encoding.TextAppender.

net/url

URL теперь также реализует интерфейс encoding.BinaryAppender.

os/user

В Windows Current теперь может быть использован в Windows Nano Server. Реализация была обновления для избежания использования функций из библиотеки NetApi32, которая отсутствует в Nano Server.

В Windows Current, Lookup и LookupId теперь поддерживают следующие встроенные сервисные аккаунты пользователя:

  • NT AUTHORITY\SYSTEM
  • NT AUTHORITY\LOCAL SERVICE
  • NT AUTHORITY\NETWORK SERVICE

В Windows Current был значительно ускорен, когда текущий пользователь присоединён к медленному домену, что является обычным случаем для многих корпоративных пользователей. Новая производительность реализации теперь в порядке миллисекунд, в сравнении с предыдущей реализации, которая могла занять несколько секунд, даже минут, до завершения.

В Windows Current теперь возвращает пользователя владельца процесса, когда текущий поток выдаёт себя за другого пользователя. Ранее это возвращало ошибку.

regexp

Regexp теперь реализует интерфейс encoding.TextAdapter.

runtime

Функция GOROOT теперь объявлена устаревшей. В новом когде следует предпочесть использование системного пути для определения бинарника “go”, и использовать go env GOROOT для определения GOROOT.

strings

Пакет strings добавляет несколько функций для работы с итераторами:

  • Lines возвращает итератор по разделённым новой линией строкам в строке.
  • SplitSeq возвращает итератор по всем подстрокам строки, разделённой сепаратором.
  • SplitAfterSeq возвращает итератор по подстрокам строки, разделённой после каждого вхождения сепаратора.
  • FieldsSeq возвращает итератор по подстрокам строки вокруг последовательностей символов пробела, как определеноunicode.IsSpace
  • FieldsFuncSeq возвращает итератор по подстрокам строки вокруг последовательностей кодовых точек юникода, удовлетворяющих предикату.

sync

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

Если вы встретите какие-либо проблемы, установите GOEXPERIMENT=nosynchashtriemap во время сборки, чтобы вернуться назад к старой реализации и, пожалуйста, заполните форму проблемы.

testing

Новые методы T.Context и B.Context возвращают контекст, который отменяется после завершения теста и до выполнения функций очистки теста.

Новые методы T.Chdir и B.Chdir могут быть использованы для изменения рабочий директории на период работы теста или бенчмарка.

text/template

Шаблоны теперь поддерживают range-over-func и range-over-int.

time

Time теперь реализует интерфейсы encoding.BinaryAppender и encoding.TextAppender.

Ports

Linux

Как было объявлено в заметках релиза Go 1.23, Go 1.24 требует ядро Linux версии 3.2 или позже.

Darwin

Go 1.24 является последним релизом, который будет работать на macOS 11 Big Sur. Go 1.25 будет требовать macOS 12 Monterey или позже.

WebAssembly

Директива компилятора go:wasmexport добавлена в программы Go для экспорта функций в хост WebAssembly.

В WebAssembly System Interface Preview 1 (GOOS=wasip1 GOARCH=wasm) Go 1.24 поддерживает сборку программы Go как reactor/library через указание флага сборки -buildmode=c-shared.

Больше типов теперь разрешены как тип аргумента или результата для функций go:wasmimport. В особенности, bool, string, uintptr и указатели на определённые типы разрешены (подробности можно увидеть в документации), вместе с 32-битными и 64-битными типами целых чисел и с плавающей точкой, и unsafe.Pointer, которые уже разрешены. Эти типы также разрешены как типы аргумента или результата для функций go:wasmexport.

Файлы поддержки для WebAssembly были перемещены в lib/wasm из misc/wasm.

Исходный размер памяти значительно сокращён, особенно для маленьких приложений WebAssembly.

Windows

32-битный порт windows/arm (GOOS=windows GOARCH=arm) был отмечен как сломанный. Подробности в #70705

>>> Go 1.24 Release Notes



Проверено: hobbit ()
Последнее исправление: hobbit (всего исправлений: 4)
Ответ на: комментарий от th3m3

Это было лет 5 назад. Сейчас может быть уже пофиксили.

https://pet2cattle.com/2024/11/golang-gc-settings

https://tip.golang.org/doc/gc-guide

Судя по шикарному количеству настроек для GC [сарказм], ничерта не изменилось. Какая-то пародия на рантайм.

X-Pilot ★★★★★
()
Ответ на: комментарий от liksys

Но я понял что проблема ушла … судя по словам.

Хотя я думаю тут дело архитектуре, не все подходит для go. Но я бы все таки rust тут не упоминал, переписали на него по другой причине. Типа он был под рукой.

В то же время мы очень успешно внедряли Rust в других системах Discord, и в итоге приняли коллективное решение писать фреймворки и библиотеки для новых сервисов только на Rust. А эта служба казалась отличным кандидатом для переноса на Rust: она небольшая и автономная, а мы надеялись, что Rust исправит эти всплески с задержками

И вообще Discord (Движок: Electron) я бы не стал приводить в пример, лучше бы они клиента на rust переписали, хотя вряд ли осилят.

mx__ ★★★★★
()
Ответ на: комментарий от mx__

Но я бы на их месте покапал в сторону Cassandra, что это еще за БД которая написана на JAVA … вон тот же Prometeus молотит по черному на дохлом железе без проблем.

Они переехали с Cassandra на ScyllaDB: https://discord.com/blog/how-discord-stores-trillions-of-messages и да, ScyllaDB в конце 2024 сменила лицензию на несвободную: https://www.scylladb.com/2024/12/18/why-were-moving-to-a-source-available-lic...

X-Pilot ★★★★★
()
Ответ на: комментарий от mx__

Но я бы все таки rust тут не упоминал, переписали на него по другой причине. Типа он был под рукой.

Не совсем. У него нет GC, поэтому упомянутой проблемы в нем в принципе не может быть. Иначе бы вообще переписывать не стали.

И вообще Discord (Движок: Electron) я бы не стал приводить в пример

Независимо от фреймворка, это все же отлично работающий мессенджер с высоконагруженным беком.

liksys ★★★★
()
Ответ на: комментарий от mx__

лучше бы они клиента на rust переписали, хотя вряд ли осилят.

Там клиент - обертка на web-версией, поэтому и Electron. А у Rust сейчас все еще плохо как с GUI (вон, Mozilla когда начала переписывать свою утилиту отправки об отчетах о падении Firefox, то GUI компонентов не было и им пришлось их делать), так и с web-рендером (servo сейчас в непонятном состоянии)...

X-Pilot ★★★★★
()
Ответ на: комментарий от liksys

Не совсем. У него нет GC, поэтому упомянутой проблемы в нем в принципе не может быть. Иначе бы вообще переписывать не стали.

Могли бы и на с++ переписать, там тоже нет сборщика. Просто судя по тексту у них Растаманы были в штате …

Независимо от фреймворка, это все же отлично работающий мессенджер с высоконагруженным беком.

Ну да плевать сколько он ЦПУ,рама и т.д. жрет, это же не у нас а у юзеров, а у кого будет тормозить пусть просто купит новее железо. Да и нам электрон проще не нужно его для разных платформ особо поддерживать.

mx__ ★★★★★
()
Ответ на: комментарий от mx__

Могли бы и на с++ переписать, там тоже нет сборщика.

Не надо на плюсах ничего переписывать :)

Ну да плевать сколько он ЦПУ,рама и т.д. жрет, это же не у нас а у юзеров

Мы же бекенд обсуждаем, ну.

liksys ★★★★
()
Ответ на: комментарий от liksys

Я уже писал как то в другой теме, я использую много программ на серверах и на рабочих станциях на С,С++,Python,Go и не могу вспомнить ни одной программы на Rust, хз почему так.

mx__ ★★★★★
()
Ответ на: комментарий от mx__

Я, кажется, тоже на расте ничего не использую. Сейчас вот мне надо микросервис писать один, но скорее всего буду делать его вообще на сях, благо там всего на пару тыщ строк делов.

liksys ★★★★
()
Ответ на: комментарий от ivanich10

Тем что PHP немодный и писать на нём зазорно а Go весь такой стильно-модно-молодёжный и многопоточный поэтому на нём можно писать сайты, перекладывателей джэйсонов по базам данных и вообще всё что угодно а ещё, когда его продвигали, то говорили что он такой простой, такой простой что даже джэйэсники смогут писать на нём программы после двух дней обучения. А ещё очень быстрый и памяти совсем не кушает. Чем их не устроил тот же Pascal я не знаю.

wenxuan
()
Последнее исправление: wenxuan (всего исправлений: 1)
Ответ на: комментарий от liksys

TL;DR: у дискорда был микросервис очистки данных, и он тормозил из-за периодически запускающегося GC. Вылечить это не смогли, переписали на расте.

Это логично, что найдётся единичный случай, где производительности Golang недостаточно. Senor LOR experts возьмут этот пример в свой арсенал, теперь это их козырь в псевдотехнических спорах.

lbvf50txt
()
Ответ на: комментарий от lbvf50txt

Для того, чтобы не нагружать сборщик мусора, программы в Golang проектируются специальным образом.

Правда порой работа с памятью выглядит как колдовство.

Вот пример, в пакете fmt есть интерфейс Formatter. Он позволяет применить форматирование на месте. Для примера

package main

import (
	"fmt"
	"strconv"
	"unicode/utf8"
)

type Тысяча int

var _ fmt.Formatter = Тысяча(0)

func selectSuffix(n int) string {
	if n%10 == 1 && n%100 != 11 {
		return " тысяча"
	} else if n%10 >= 2 && n%10 <= 4 && (n%100 < 10 || n%100 >= 20) {
		return " тысячи"
	}
	return " тысяч"
}

func (t Тысяча) Format(s fmt.State, verb rune) {

	const availFlags = "-+# 0"

	var format = make([]byte, 1, 10+7) // format verbs + suffix
	format[0] = '%'

	var f byte
	for i := 0; i < len(availFlags); i++ {
		if f = availFlags[i]; s.Flag(int(f)) {
			format = append(format, f)
		}
	}
	var (
		width, prec int
		ok          bool
	)
	if width, ok = s.Width(); ok {
		format = strconv.AppendInt(format, int64(width), 10)
	}
	if prec, ok = s.Precision(); ok {
		format = append(format, '.')
		format = strconv.AppendInt(format, int64(prec), 10)
	}
	if verb > utf8.RuneSelf {
		format = append(format, string(verb)...)
	} else {
		format = append(format, byte(verb))
	}

	format = append(format, selectSuffix(int(t))...)

	fmt.Fprintf(s, string(format), int(t))
}

func main() {
	var list = []Тысяча{0, 1, 2, 5, 11, 21, 125}
	for _, t := range list {
		fmt.Printf("- %d\n", t)
	}
}

Если запустить

# go run main.go 
- 0 тысяч
- 1 тысяча
- 2 тысячи
- 5 тысяч
- 11 тысяч
- 21 тысяча
- 125 тысяч

А если провести нагрузочное испытание

package main

import (
	"fmt"
	"io"
	"testing"
)

func Benchmark(b *testing.B) {
	for n := range b.N {
		var t = Тысяча(n)
		fmt.Fprintf(io.Discard, "%d", t)
	}
	b.ReportAllocs()
}

type nopState struct{}

var _ fmt.State = nopState{}

func (nopState) Precision() (int, bool) {
	return 0, false
}

func (nopState) Width() (int, bool) {
	return 0, false
}

func (nopState) Flag(int) bool {
	return false
}

func (nopState) Write(p []byte) (int, error) {
	return len(p), nil
}

func BenchmarkТысяча_Format(b *testing.B) {
	var ns nopState
	for n := range b.N {
		Тысяча(n).Format(ns, 'd')
	}
	b.ReportAllocs()
}

func Benchmark_int(b *testing.B) {
	for n := range b.N {
		fmt.Fprintf(io.Discard, "%d", n)
	}
	b.ReportAllocs()
}

Получим

# go test -bench .
goos: linux
goarch: amd64
cpu: AMD Ryzen 9 5950X 16-Core Processor            
Benchmark-32                  7173708    169.4 ns/op     16 B/op    1 allocs/op
BenchmarkТычяча_Format-32    14566257     79.06 ns/op     8 B/op    0 allocs/op
Benchmark_int-32             26037321     46.17 ns/op     8 B/op    0 allocs/op
PASS
ok      3.873s

Обратите внимание. Просто для целого (int), который не имеет своего Format выделения в куче не происходит. Так же нет выделения в куче при вызове Тысяча.Format напрямую.

А перемещение небольшого числа (размера int) в кучу идёт как раз вот здесь

		var t = Тысяча(n)
		fmt.Fprintf(io.Discard, "%d", t) // <------

Когда Тысяча превращается в interface{}.

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

--func (t Тысяча) Format(s fmt.State, verb rune) {
++func (t *Тысяча) Format(s fmt.State, verb rune) {

Как перемещение в кучу прекращается

# go test -bench .
goos: linux
goarch: amd64
cpu: AMD Ryzen 9 5950X 16-Core Processor            
Benchmark-32                 19441869     62.46 ns/op     8 B/op    0 allocs/op
BenchmarkТысяча_Format-32    14680886     80.15 ns/op     8 B/op    0 allocs/op
Benchmark_int-32             25908232     45.56 ns/op     8 B/op    0 allocs/op
PASS
ok         3.769s
thegoldone ★★
()
Ответ на: комментарий от bdrbt

Казалось бы всё «щикарно», но вот этот самый Printer{} внутри вызывает форматер, который объявляет внутри себя переменные, которые ни разу не высвобождаются без gc.

Функция newPrinter берёт объект из пула. А потом он возвращается обратно в пул. Даже буфер сохраняется в пуле, если у него размер не больше 64*1024.

Это отличный пример снижения давления на сборщик в Го, всего лишь. Потому что для всех вызовов в цикле используется один и тот же буфер и pp.

И если и есть выделение памяти, то это мелочёвка.

И да, нет нужды отключать сборщик мусора, можно посчитать общее количество выделенных байтов всегда. Конечно если у вас 1024 kilobyte memory ok, то можно и через отключение (ха-ха).

package main

import (
	"fmt"
	"log"
	"math"
	"os"
	"runtime"
	"strconv"
	"sync"
)

const bound = 100 * 1024

type Source struct {
	pool   sync.Pool
	allocs int
}

func (s *Source) Acquire() (val []byte) {
	const maxLength = len("9223372036854775807, bottles of beer\n")
	var vi = s.pool.Get()
	if vi != nil {
		return vi.([]byte)
	}
	s.allocs++
	return make([]byte, 0, maxLength)
}

func (s *Source) Release(val []byte) {
	val = val[:0]
	s.pool.Put(val)
}

func main() {

	var (
		src Source

		memInitial runtime.MemStats
		memCurrent runtime.MemStats

		err error
	)
	runtime.ReadMemStats(&memInitial)

	for i := int64(0); i < math.MaxInt64; i++ {
		var p = src.Acquire()
		p = strconv.AppendInt(p, i, 10)
		p = append(p, ", bottles of beer\n"...)
		if _, err = os.Stdout.Write(p); err != nil {
			log.Fatal(err)
		}
		src.Release(p)

		runtime.ReadMemStats(&memCurrent)
		if memCurrent.TotalAlloc-memInitial.TotalAlloc > bound {
			fmt.Printf("At %d, allocs=%d, bytes=%d, pool_allocs=%d\n", i,
				memCurrent.Mallocs-memInitial.Mallocs,
				memCurrent.TotalAlloc-memInitial.TotalAlloc,
				src.allocs)
			return
		}
	}
}

За время цикла в куче было выделено 4065 объектов.

4059, bottles of beer
4060, bottles of beer
4061, bottles of beer
At 4061, allocs=4065, bytes=102408, pool_allocs=1

Циклов: 4061.
Выделений памяти в куче: 4065.
Всего выделено байтов: 102408.

А это значит примерно 25 байтов за цикл. Так можно очень долго ждать поглощения памяти.

А теперь финт ушами

package main

import (
	"fmt"
	"math"
	"runtime"
)

const bound = 100 * 1024

func main() {

	var (
		memInitial runtime.MemStats
		memCurrent runtime.MemStats
	)
	runtime.ReadMemStats(&memInitial)

	for i := int64(0); i < math.MaxInt64; i++ {
		fmt.Printf("%d, bottles of beer\n", i)

		runtime.ReadMemStats(&memCurrent)
		if memCurrent.TotalAlloc-memInitial.TotalAlloc > bound {
			fmt.Printf("At %d, allocs=%d, bytes=%d\n", i,
				memCurrent.Mallocs-memInitial.Mallocs,
				memCurrent.TotalAlloc-memInitial.TotalAlloc)
			return
		}
	}
}

И

6333, bottles of beer
6334, bottles of beer
6335, bottles of beer
6336, bottles of beer
At 6336, allocs=6086, bytes=102416

Как видите, он поглощает память намного медленнее. Примерно 17 байтов за цикл.

thegoldone ★★
()
Ответ на: комментарий от Obezyan

В последний раз я рынок анализировал в 2024 году и сейчас ситуацией не владею. Расскажу только, что в апреле 2024го на Go находил 800+ вакансий, а на жабе их было более двух тысяч. Это точно знаю, т.к. в чатике обсуждал со знакомыми и переписка осталась. Сейчас, если искать тем же способом, количество работы на жабе сократилось в половину, а на Go на четверть.

При этом, на 2024й, вакансий подходящих мне (с опытом в других ЯП, но без опыта в $lang_name) было больше на Go, а сами вакансии были интереснее, и поэтому я решил учить жабу. Вакансий для начинающих тоже было больше на Go. Насколько больше я не скажу, т.к. не записал нигде, но если запомнил значит было прям ололо насколько больше.

По моему направлению (с Perl’а) почти всегда уходят на Go.

Что касается соотношения вакансий за бугром и здесь - разница разительная. Как по зарплатам так и по популярности ЯП (пропорции языков на рынке забавно различались в РФ и у тех кто Пушкина не читал).

perl5_guy ★★★★★
()
Ответ на: комментарий от thegoldone

Спасибо, @thegoldone. Внимательно прочитал ваш комментарий. Именно ради таких комментариев я терплю агрессивное невежество на LOR.

  1. var _ fmt.Formatter = Тысяча(0) — красивая техника проверки типа на этапе компиляции.
  2. Механизм value pair при работе с интерфейсами уже начал забываться.
  3. Настоящий боевой код. Отличается от синтетических примеров. Это круто.

Для синтетического примера хватило бы:

UPD:

Не хватило бы. В format требуется добавить: ширину, точность и глагол. И в самом конце добавить суффикс.

// UPD:
// Не достаточно. Требуется больше кода.
func (t Тысяча) Format(s fmt.State, verb rune) {

	var format = make([]byte, 1, 10+7) 
	format = append(format, selectSuffix(int(t))...)
	fmt.Fprintf(s, string(format), int(t))
}

А тут полная реализация, 100% реализация. 🍰☕

  • При использовании func (t Тысяча) Format(...) размещается копия значения для value pair в HEAP.
  • В случае func (t *Тысяча) Format(s fmt.State, verb rune) пара представляет собой два указателя, размещение в HEAP не требуется.

Суппер комментарий, разбераясь с ним, я понял что: Возможно обратиться по указателю к переменной в Стеке метода родителя.

Вроде лежит на поверхности, а на самом деле нужно поразбираться.

lbvf50txt
()
Последнее исправление: lbvf50txt (всего исправлений: 2)
Ответ на: комментарий от lbvf50txt

Суппер комментарий, разбераясь с ним, я понял что: Возможно обратиться по указателю к переменной в Стеке метода родителя.

В том смысле, что переменную не надо выводить в HEAP, и если есть указатель на переменную, то переменная не «убегает» из метода.

Вроде до предела логично: постоянно передаем по ссылке параметры в метод, и он не копируется. Но именно сейчас связалась практика и теория.

Потому что теоретически казалось, что любая память, доступная нескольким методам, находится в HEAP.

lbvf50txt
()
Ответ на: комментарий от liksys

TL;DR: у дискорда был микросервис очистки данных, и он тормозил из-за периодически запускающегося GC. Вылечить это не смогли, переписали на расте.

Разработчики (или архитекторы) Дискорда – это далеко не эталон. То что языку со сборкой мусора будет плохеть при большом количестве выделяемых объектов – очевидно. Но это касается только выделяемых в куче.

Я провёл исследование на Го 1.24.0. И в результате не получил никаких задержек вообще. Давайте взглянем вместе.

Для хранения информации о состояниях мы используем структуру данных, которая так и называется: Read State. В Discord их миллиарды: по одному состоянию для каждого пользователя на каждый канал. У каждого состояния несколько счётчиков, которые необходимо атомарно обновлять и часто сбрасывать в ноль. Например, один из счётчиков — это количество @mention в канале.

Для быстрого обновления атомарного счетчика на каждом сервере Read States имеется кэш «последних состояний» (Least Recently Used, LRU). В каждом кэше миллионы пользователей и десятки миллионов состояний. Кэш обновляется сотни тысяч раз в секунду.

Проверим.

Код: https://go.dev/play/p/ap12nos-JB4

Результаты.

Первый случай: объекты не удаляются. Испытание 10 минут.

rps: 1257350, wps: 1257345, m at: 10.015465ms, n gc: 0, m pause: 162.088µs, cleaned: 0, added: 0, size: 500000000
rps: 1267523, wps: 1267525, m at: 10.015465ms, n gc: 0, m pause: 162.088µs, cleaned: 0, added: 0, size: 500000000
rps: 1254168, wps: 1254162, m at: 10.015465ms, n gc: 0, m pause: 162.088µs, cleaned: 0, added: 0, size: 500000000
rps: 1257532, wps: 1257535, m at: 10.015465ms, n gc: 0, m pause: 162.088µs, cleaned: 0, added: 0, size: 500000000
rps: 1257037, wps: 1257034, m at: 10.015465ms, n gc: 0, m pause: 162.088µs, cleaned: 0, added: 0, size: 500000000
rps: 1258976, wps: 1258976, m at: 10.015465ms, n gc: 0, m pause: 162.088µs, cleaned: 0, added: 0, size: 500000000
rps: 1259420, wps: 1259420, m at: 10.015465ms, n gc: 0, m pause: 162.088µs, cleaned: 0, added: 0, size: 500000000
finishing...
rps: 912267, wps: 361464, m at: 10.015465ms, n gc: 0, m pause: 162.088µs, cleaned: 0, added: 0, size: 500000000
done

Второй случай: раз в 5 секунд запускается очистка, которая удаляет записи, к которым не было обращения последние 5 секунд. Испытание 10 минут.

rps: 1265351, wps: 1265314, m at: 11.18331ms, n gc: 0, m pause: 169.392µs, cleaned: 8830, added: 23094, miss: 23179, size: 490834241
rps: 1257721, wps: 1257752, m at: 11.18331ms, n gc: 0, m pause: 169.392µs, cleaned: 6646, added: 22961, miss: 22975, size: 490850556
rps: 1257977, wps: 1257960, m at: 11.18331ms, n gc: 0, m pause: 169.392µs, cleaned: 8600, added: 22850, miss: 23230, size: 490864806
rps: 1262250, wps: 1262260, m at: 11.18331ms, n gc: 0, m pause: 169.392µs, cleaned: 9029, added: 23052, miss: 23073, size: 490878829
rps: 1247671, wps: 1247664, m at: 11.18331ms, n gc: 0, m pause: 169.392µs, cleaned: 10181, added: 22673, miss: 22631, size: 490891321
rps: 1257889, wps: 1257903, m at: 11.18331ms, n gc: 0, m pause: 169.392µs, cleaned: 8453, added: 23023, miss: 22716, size: 490905891
rps: 1261634, wps: 1261635, m at: 11.18331ms, n gc: 0, m pause: 169.392µs, cleaned: 9309, added: 22666, miss: 22984, size: 490919248
rps: 1248419, wps: 1248395, m at: 11.18331ms, n gc: 0, m pause: 169.392µs, cleaned: 7817, added: 22562, miss: 22651, size: 490933993
rps: 1261673, wps: 1261709, m at: 11.18331ms, n gc: 0, m pause: 169.392µs, cleaned: 9032, added: 22785, miss: 23110, size: 490947746
rps: 1255974, wps: 1255956, m at: 11.18331ms, n gc: 0, m pause: 169.392µs, cleaned: 10471, added: 23098, miss: 22764, size: 490960373
finishing...
rps: 46, wps: 30, m at: 11.18331ms, n gc: 0, m pause: 169.392µs, cleaned: 20567058, added: 3, miss: 10, size: 470393318
done

Раз в секунду сбор статистики. Давление оказывается в 32 потока. Чтение и запись поровну. Сборка мусора раз в 120 секунд запускается. И почти ничего не делает.

Как видите, максимальное время одной операции не превышает 11мс для 500 млн записей (предел для моей оперативной памяти).

Максимальная пауза при этом 169.392µs. Настолько маленькая, что не стоит внимания.

И да, самое главное, что эту простыню я написал чисто по приколу в течении часа вместе с испытаниями. Сколько они там рожали по времени реализацию на Расте остаётся только гадать. Полагаю, что более одного человекочаса.

Интересно было бы взглянуть на их реализацию на Го. Хи-хи.

thegoldone ★★
()
Ответ на: комментарий от lbvf50txt

Возможно обратиться по указателю к переменной в Стеке метода родителя.

Вроде лежит на поверхности, а на самом деле нужно поразбираться.

Да, с этим стеком ещё куча приколов. Например цепочка:

package main

import "fmt"

type A struct {
	N     int
	Shift int
}

func (v A) WithShift(shift int) A {
	v.Shift = shift // всё равно копия, можно менять
	return v        // по месту и возвращать
}

// нет нужды копировать на стек A, используем указатель
func (v *A) Shifted() int {
	return v.N + v.Shift
}

func main() {
	var a = A{N: 10}

	fmt.Println("A shifted:", a.WithShift(20).Shifted(), "origin", a)
}

Получаем [play].

./prog.go:24:44: cannot call pointer method Shifted on A

Потому что на стеке. И указатель не даёт взять.

И мы попадаем в ситуацию или выделять, или выделять.

package main

import (
	"fmt"
	"testing"
)

type B struct {
	N     int
	Shift int
}

func (b B) WithShift(shift int) B {
	b.Shift = shift
	return b
}

func (b B) WithOffset(offset int) B {
	b.Shift += offset
	return b
}

func (b B) Shifted() int {
	return b.N + b.Shift
}

func (b B) String() string {
	return fmt.Sprintf("B{%d %+d}", b.N, b.Shift)
}

type C struct {
	N     int
	Shift int
}

func (c *C) WithShift(shift int) *C {
	return &C{N: c.N, Shift: shift}
}

func (c C) WithOffset(offset int) *C {
	return &C{N: c.N, Shift: c.Shift + offset}
}

func (c *C) Shifted() int {
	return c.N + c.Shift
}

func (c *C) String() string {
	return fmt.Sprintf("C{%d %+d}", c.N, c.Shift)
}

var (
	_bx  B
	_bxp *B
	_cx  C
	_cxp *C
	_b   string
	_c   string
)

func BenchmarkBAssign(b *testing.B) {
	var bx = B{N: 10, Shift: 0}
	for i := range b.N {
		_bx = bx.WithShift(i).
			WithOffset(15).
			WithShift(i + 15).
			WithOffset(30)
	}
}

func BenchmarkBAssignPtr(b *testing.B) {
	var bx = B{N: 10, Shift: 0}
	for i := range b.N {
		var t = bx.WithShift(i).
			WithOffset(15).
			WithShift(i + 15).
			WithOffset(30)
		_bxp = &t
	}
}

func BenchmarkBSprintf(b *testing.B) {
	var bx = B{N: 10, Shift: 0}
	for i := range b.N {
		_b = fmt.Sprintf("%s",
			bx.WithShift(i).
				WithOffset(15).
				WithShift(i+15).
				WithOffset(30),
		)
	}
}

func BenchmarkCAssign(b *testing.B) {
	var cx = C{N: 10, Shift: 0}
	for i := range b.N {
		_cx = *cx.WithShift(i).
			WithOffset(15).
			WithShift(i + 15).
			WithOffset(30)
	}
}

func BenchmarkCAssignPtr(b *testing.B) {
	var cx = C{N: 10, Shift: 0}
	for i := range b.N {
		_cxp = cx.WithShift(i).
			WithOffset(15).
			WithShift(i + 15).
			WithOffset(30)
	}
}

func BenchmarkCSprintf(b *testing.B) {
	var cx = C{N: 10, Shift: 0}
	for i := range b.N {
		_c = fmt.Sprintf("%s",
			cx.WithShift(i).
				WithOffset(15).
				WithShift(i+15).
				WithOffset(30),
		)
	}
}

С результатом

# go test -benchmem -bench .
goos: linux
goarch: amd64
pkg: misc
cpu: AMD Ryzen 9 5950X 16-Core Processor            
BenchmarkBAssign-32       1000000000      0.4216 ns/op     0 B/op    0 allocs/op
BenchmarkBAssignPtr-32      88171006     13.26 ns/op      16 B/op    1 allocs/op
BenchmarkBSprintf-32         6228514    195.2 ns/op       56 B/op    4 allocs/op
BenchmarkCAssign-32       1000000000      0.4269 ns/op     0 B/op    0 allocs/op
BenchmarkCAssignPtr-32      79312046     13.60 ns/op      16 B/op    1 allocs/op
BenchmarkCSprintf-32         6245740    194.5 ns/op       56 B/op    4 allocs/op
PASS
ok      misc   6.045s

Во случае С мы сами выделяем новое значение. А в случае B оно выделяется во время fmt.Sprintf, и при сохранении указателя в глобальной переменной.

Причём Го оптимизирует цепочку выделений в одно (?) и итоговый результат одинаков для C и B.


P.S. ну и вишенка на торте

type Shifteder interface {
	Shifted() int
}

func shifted(sher Shifteder) int {
	return sher.Shifted()
}
var _bs, _cs int

func BenchmarkBShifted(b *testing.B) {
	var bx = B{N: 10, Shift: 0}
	for i := range b.N {
		_bs = shifted(
			bx.WithShift(i).
				WithOffset(15).
				WithShift(i + 15).
				WithOffset(30),
		)
	}
}

func BenchmarkCShifted(b *testing.B) {
	var cx = C{N: 10, Shift: 0}
	for i := range b.N {
		_cs = shifted(
			cx.WithShift(i).
				WithOffset(15).
				WithShift(i + 15).
				WithOffset(30),
		)
	}
}
BenchmarkBShifted-32    1000000000    0.2108 ns/op    0 B/op    0 allocs/op
BenchmarkCShifted-32    1000000000    0.6351 ns/op    0 B/op    0 allocs/op

Оно просто превращается в число.

thegoldone ★★
()
Последнее исправление: thegoldone (всего исправлений: 1)
Ответ на: комментарий от lbvf50txt

терплю […] невежество

Невежество – это море. Бороться с ним бесполезно, но можно ловить рыбу.

thegoldone ★★
()
Ответ на: комментарий от thegoldone

Спасибо.

Внимательно разбираю ваш код. Хорошо подобраны примеры: в цепочках методов компилятор Golang не производит автоматическое преобразование указателей в значения, а значений в указатели.

В то время как на объявленных переменных можно вызывать методы для указателей на значениях и методы для значений на указателях, т.е. срабатывает неявное преобразование типа: значение преобразуется в указатель, а указатель разыменовывается в значение под тип ресивера метода.

С переменными не важно, pointer или value receiver. А вот в цепочке методов важно, чтобы тип ресивера соответствовал возвращаемому значению. В цепочках никаких implicit conversions.

И это интересно, так как в примерах явно ставится на этом акцент.

lbvf50txt
()
Ответ на: комментарий от thegoldone

Go:

type Shifteder interface {
	Shifted() int
}

func shifted(sher Shifteder) int {
	return sher.Shifted()
}

Где-то я встречал что-то подобное. Вот где: в использовании функции как обработчика для HTTP запроса.

Go:

type Handler interface {
	ServeHTTP(http.ResponseWriter, *http.Request)
}

type HandlerFunc func(http.ResponseWriter, *http.Request)
func (f HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	f(w, r)
}
lbvf50txt
()
Ответ на: комментарий от bdrbt

Никто, вроде, не говорил о том, что го подходит для критически нагруженных систем. Просто он производительный, потому что компилируемый, вот и всё. Хотя я согласен, что эти понятия не всегда равнозначны.

kaldeon
() автор топика
Ответ на: комментарий от lbvf50txt

А именно: в начале программы выделяются буферы в памяти, которые во время работы программы используются.

Если @bdrbt хочет вообще не дёргать GC, то не поможет. Хотя сама цель неразумна, на мой взгляд.

А насколько вообще это полезная стратегия (я про буферы, не гц)?

Читайте Jon Bodner

Впервые слышу. Можно ссылку на его работы о памяти?

kaldeon
() автор топика
Последнее исправление: kaldeon (всего исправлений: 2)
Ответ на: комментарий от bread

Го не создавался для быдлокодинга для веб. Но его можно без особой боли использовать даже в вебе, особенно чтобы не плодить зоопарк технологий, если го уже где-то используется.

kaldeon
() автор топика
Ответ на: комментарий от kaldeon

Го не создавался для быдлокодинга для веб.

Эм, он буквально создан для веб апишек и поэтому же имеет JSON в качестве базовой структуры для работы.

Obezyan
()
Ответ на: комментарий от bread

безумной лапше без паттернов

Притянутый за уши паттерн — антипаттерн, который годится только для социальной инженерии, чтобы коллеги оценили.

kaldeon
() автор топика
Ответ на: комментарий от kaldeon

Если @bdrbt хочет вообще не дёргать GC, то не поможет. Хотя сама цель неразумна, на мой взгляд.

О хоссспади. Пример был приведён только с целью показать что от GC-избавится «волшебной архитектурой» не получится, даже простейшие стандартные функции гадят в кучу.

Управление GC так-то не зря вывели в runtime/debug.

bdrbt
()
Ответ на: комментарий от Obezyan

Ну всё-таки он создавался для проектов, которые были написаны на C++ и для которых требовалась отдельная машина для сборки, если верить этому докладу (7. Dependencies in C and C++). То есть интерпретируемость уже вне вопроса.

JSON в качестве базовой структуры для работы

Просто популярная часть стандартной библиотеки. И не самая гибкая в плане настройки сериализации, хотя это очень пригодилось бы в множестве разношёрстных вебов.

kaldeon
() автор топика
Последнее исправление: kaldeon (всего исправлений: 5)
Ответ на: комментарий от alysnix

Таки от провайдера зависит. Контору палить не буду (из шкурного интереса), но все работает :)

gns ★★★★★
()
Ответ на: комментарий от delidov_george

Лампочка дома, лампочка у соседа, скоро речь заходит о монополии на вкручивание лампочек.

kaldeon
() автор топика
Ответ на: комментарий от kaldeon

Ну всё-таки он создавался для проектов, которые были написаны на C++

Именно так, у С++ программистов расширился круг задач, стало больше веба, они потыкались поняли что на сях это долго и больно.

И не самая гибкая в плане настройки сериализации

Это мягко сказано, иногда приходится вспоминать матерей разработчиков, особенно когда в JSON вложенные массивы.

это очень пригодилось бы в множестве разношёрстных вебов.

На этом буквально стоит весь гошный веб.

Obezyan
()
Ответ на: комментарий от kaldeon

А насколько вообще это полезная стратегия (я про буферы, не гц)?

Сами подумайте. Есть системный вызов, который просит у операционной системы выделить ему память. Потом есть системный вызов, который говорит ОС: «Забирай память, мне она не нужна». Вот эти две операции лучше делать много раз или один раз?

Впервые слышу. Можно ссылку на его работы о памяти?

Судя по вашему вопросу о «полезности стратегии», вам не лишним будет прочитать про базовую архитектуру UNIX и устройство TCP/IP.

Уже потом начинать читать «Learning Go» Боднера про Golang. Кому надо, тот нагуглит.

lbvf50txt
()
Ответ на: комментарий от Obezyan

Того

А вот нихера! ASN1, во https://pkg.go.dev/encoding/asn1

// для тех кто в танке и шуток не понимает:

Пакет encoding содержит ещё ascii85, asn1, base32, base64, binary, csv, gob, hex, pem, xml парсеры. Это просто заранее заготовленные либы чтобы:

  • Не писать свои велосипеды, а побырому взять готовое
  • Если пришлось таки велосипедить - смотреть туда за бест-практис
bdrbt
()
Последнее исправление: bdrbt (всего исправлений: 1)
Ответ на: комментарий от lbvf50txt

Я понимаю как работает malloc. Мне интересно, действительно ли такие попытки экономить память работают на практике (дают ощутимую выгоду) в общем случае и как вообще это готовят по-умному. Это же не сишка, где «память» можно буквально передать откуда угодно куда угодно, хоть всю программу прозрачно держать в статике.

kaldeon
() автор топика
Последнее исправление: kaldeon (всего исправлений: 2)
Ответ на: комментарий от LongLiveUbuntu

Жаль, что этот проект не пользуется такой же популярностью на ЛОРе, как Столяров.

kaldeon
() автор топика
Ответ на: комментарий от kaldeon

Напишите или попросите https://chat.deepseek.com/ написать вам бенчмарк.

Вот я попросил.

buf_test.go

package main

import (
	"bytes"
	"io"
	"testing"
)

// BenchmarkReadWithSingleBuffer тестирует производительность чтения с использованием одного буфера
func BenchmarkReadWithSingleBuffer(b *testing.B) {
	data := bytes.Repeat([]byte("a"), 1024*1024) // 1MB данных
	reader := bytes.NewReader(data)
	buffer := make([]byte, 1024) // Буфер 1KB

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		reader.Seek(0, io.SeekStart) // Сброс позиции чтения
		for {
			_, err := reader.Read(buffer)
			if err == io.EOF {
				break
			}
		}
	}
}

// BenchmarkReadWithNewBuffer тестирует производительность чтения с выделением нового буфера при каждом чтении
func BenchmarkReadWithNewBuffer(b *testing.B) {
	data := bytes.Repeat([]byte("a"), 1024*1024) // 1MB данных
	reader := bytes.NewReader(data)

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		reader.Seek(0, io.SeekStart) // Сброс позиции чтения
		for {
			buffer := make([]byte, 1024) // Новый буфер 1KB при каждом чтении
			_, err := reader.Read(buffer)
			if err == io.EOF {
				break
			}
		}
	}
}

Запускать так:

go mod init your_module_name
go test -bench .

Разница в быстродействии на порядок. Т.е. в 10 раз.

lbvf50txt
()
Ответ на: комментарий от Obezyan

На 90% всех проектов сейчас на Go - гонять jSONы в апишке.

В современном вебе 90% на любом языке это json’огонялки. Go не только в вебе, в бэке который не наружу, json вообще самое отвратное решение.

bdrbt
()
Для того чтобы оставить комментарий войдите или зарегистрируйтесь.