Lasiar работает 💡
161 subscribers
7 photos
47 links
Про Golang и около того

help: @Vallder
Download Telegram
Благодаря golangci-lint, я узнал о некой проблеме. В некоторых конфигурациях происходит деградация производительности при работе Go в контейнере с ограничением по CPU.

TLDR:

Просто используй https://github.com/uber-go/automaxprocs.

За что отвечает GOMAXPORCS:
За количество threads процесса, по дефолту GOMAXPROCS это количество ядер (потоков) процессора.

Почему это работает не совсем хорошо:
В 2015 году выходит Go 1.5, в котором изменено стандартное значение GOMAXPROCS с 1 на количество ядер (или потоков, если используется HTT). То есть до Go 1.5 создавался только один thread, даже когда приложение запускалось на многоядерных системах. Выставление GOMAXPROCS по количеству ядер позволило ускорить Go в разы.

Берется именно то количество ядер, которое доступно ОС, игнорируя cgroup (в Linux через sched_getaffinity). В этом и заключается проблема.

На 24 поточном сервере запускается контейнер с лимитом 1 CPU. Go выставляет GOMAXPROCS == 24, и создает 24 threads, из-за чего происходит деградация производительности. В самом репозитории есть сравнительная таблица.

Как это исправить?
Все просто — достаточно прочесть лимит по cgroup и если он есть, выставить его через runtime.GOMAXPROCS. Как раз uber-go/automaxprocs этим и занимается.
10🔥6👾3💅21
Наконец-то выбрался из леса, а это значит, что скоро будут новые посты
🥰6🔥4🏆4💅2👏1🕊1
Небольшой интересный факт о gRPC.

При реализации gRPC сервера, нельзя указать в приложении алгоритм сжатия отправляемого сообщения.

Алгоритм сжатия берется из заголовка запроса grpc-encoding (документация) (код).

То есть алгоритм сжатия определяет клиент.
😱54🤓3💅1
Мебибайты и кибибайты.

Меня достаточно давно смущало:

Приставки из СИ (Système international) это степени числа 10

То есть:

10^3 это кило
10^6 это мега

Но когда мы говорим про 1 килобайт, мы говорим про 1024 байт, а не про 1000 байт.

К примеру — код pprof, в исходном коде Go:
{"kB", []string{"kb", "kbyte", "kilobyte"}, float64(1 << 10)}


И вот, некоторая путаница cуществует с 60-х годов

— Понятия бит введено в 1948 году (A Mathematical Theory of Communication).
— В 1950 впервые
используется приставка кило для бит, в качестве 1000 бит.
— В 1964 использование приставки K в качестве степени 2 (
A Time-Shared Computer for Real-Time Information Processing).

Для решения этой путаницы в августе 1995 года добавляют новые приставки, которых нет в СИ:
В вики указан 1998, это когда МЭК ввел
kibi (Ki), mebi (Mi), gibi (Gi) and tebi (Ti)

Которые уже обозначают степени числа 2 (2^10, 2^20...).

То есть:
14 КB (килобайт) = 14*10^3 = 14_000 байт
14 KiB (кибибайт) = 14*2^10 = 14_336 байт


В итоге: спустя 30 лет, после появления приставок степени двойки, путаницы еще больше, уже в течении 60 лет (!!!).

— Проводник в Windows показывает размер в KB, когда на самом деле KiB.
— Внутри Go где-то правильное использование приставок, а где-то нет.
— Приложения GNU вроде (не все проверял) корректно дружат (parted, ls).
— Grafana знает про приставки степени двойки.
— Международное бюро мер и весов (создатели СИ) в брошюре 2019 года написали о том что: приставки СИ сугубо степени числа 10, а приставки степени числа 2 это не СИ не стоит их путать.
💅32🐳2
Кажется что в Go скоро появятся новые директивы:
//go:fix inline 

//go:fix forward


Они будут нужны для новой миграционной тулзы: go fix, которая переписывает на новый API после обновления Go (подробнее можно почитать тут).

На сколько я понял:
//go:fix forward — просто меняет обратно совместимую сущность.

А c //go:fix inline чуть муторнее, go fix будет просто инлайнить тело функции

К примеру:
package a

func f() {
One() // want `inline call of a.One`

new(T).Two() // want `inline call of \(a.T\).Two`
}

type T struct{}

//go:fix inline
func One() int { return one } // want One:`goFixInline a.One`

const one = 1

//go:fix inline
func (T) Two() int { return 2 } // want Two:`goFixInline \(a.T\).Two`


Меняется на:
package a

func f() {
_ = one // want `inline call of a.One`

_ = 2 // want `inline call of \(a.T\).Two`
}

type T struct{}

//go:fix inline
func One() int { return one } // want One:`goFixInline a.One`

const one = 1

//go:fix inline
func (T) Two() int { return 2 } // want Two:`goFixInline \(a.T\).Two`


Как по мне, выглядит несколько странно.

P.S.
Не совсем понятно как будет решаться проблема с пересечением скоупа.
Кажется тогда код с которым осуществляется работа может стать максимально некрасивым :с
👍411👨‍💻1👀1💅1👾1
Является ли число Pi корректным destination для ping?

PI=3.14159265 ping ${PI}
Anonymous Quiz
62%
Да
38%
Нет
🤨3🍌1💅1👾1
Постом выше спрашивал про ping.
А вот и ответ:

🥁🥁🥁

Число Pi для команды ping является корректным аргументом.

Почему?
Ping использует функцию inet_aton().

И man, говорит про эту функцию следующее:
a.b.c.d
Each of the four numeric parts specifies a byte of the address; the bytes are assigned in left-to-right order to produce the binary address.

a.b.c

Parts a and b specify the first two bytes of the binary address. Part c is interpreted as a 16-bit value that defines the rightmost two bytes of the binary address. This notation is suitable for specifying (outmoded) Class B network addresses.

a.b

Part a specifies the first byte of the binary address. Part b is interpreted as a 24-bit value that defines the rightmost three bytes of the binary address. This notation is suitable for specifying (outmoded) Class C network addresses.

a

The value a is interpreted as a 32-bit value that is stored directly into the binary address without any byte rearrangement.

В итоге, пингануть 127.0.0.1 можно просто через ping 127.1 или через 0x7F.1, или через 2130706433...
🤯🤯🤯

Откуда это все пошло:

- 1983, 4.2BSD. Первое появление подобной логики, в функции inet_addr().

- 1990, 4.3BSD-Reno. Появление функции inet_aton().

- 1999, Networking Services (XNS) Issue 5.2. Описывает inet_addr(), с подобным синтаксисом.

- 2004, POSIX. Взятое из XNS ассоциация IEEE добавляет в POSIX функции: inet_addr(), inet_ntoa(), сохраняя поведение (link, и через поиск этих функций).

- 2005, RFC 3986, Запрещает подобный синтаксис для URI, хотя в chromium based браузерах оно работает http://2915189091 (ссылка не работает на этом тексте, так что только через ^c ^v).

- 2020, Golang. Создано issue с предложением добавить описанную выше логику в пакет net, спустя почти пять лет (4 декабря 2024) закрывают issue как "not planned".
Please open Telegram to view this post
VIEW IN TELEGRAM
3👍2🔥2👾2❤‍🔥1😁1😱1💅1
Скорее всего уже на этой неделе будет релиз Go 1.24.
🎉🎉🎉

Об это в прошлый вторник объявила Cherry Mui, которая отвечает за выпуск релизов (и не только).
1.24.0 is planned to be released next week



И создан milestone для 1.25.
🔥4💅31🆒1👾1
Ура, Go 1.24 релизнулся!
🎉🎉🎉

Буквально пару часов делал пост про это https://t.me/LasiarAtWork/42
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥10👏311👌1💅1
Ранее писал про новую директиву //go:fix inline.

Alan Donovan из Go team недавно сделал PR, в котором добавил в пакеты ioutil и reflect эту директиву для некоторых функций.

Здесь выглядит симпатично, *НО* это сделал инженер из Go team, а какое качество будет у разработчиков сторонних библиотек?

В целом, я буду делать по старинке, через golangci-lint искать deprecated функции и руками буду править.

Как мне кажется, лучше было бы через go vet предупреждение сделать, а не править так исходный код.
5🔥1💅1💘1👾1
Недавно вспоминал о своем докладе: "Почему Docker написан на Go", который я готовил год назад для подлодки.

В этом докладе вас ждет:
- 🕵️ Теория заговора
- 📅 История контейнеризации с 1979 года
- 💔 Почему K8s отказался от Docker (спойлер: не совсем отказался)
- 📑 Не совсем публичные слайды команды dotCloud (ex Docker, inc)

📺 Смотреть

P.S. Все еще актуально, как и год назад! 👀
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥95🥰2
Ранее говорил о GOMAXPROCS и cgroup.

И вот в новой версии Go (1.25) уже не понадобится библиотека github.com/uber-go/automaxprocs, так как cgroup для GOMAXPROCS будет учитываться "из коробки".
Уже даже документация обновлена для master ветки.
🔥1032🍓1💅1
Кстати, про 1.25:
сегодня-завтра должен выйти Go 1.25 RC1
🔥72🍾2💅1
Я пропустил что в Go 1.22 добавили работу с относительно новой сущностью Linux pidfd.

Зачем это в Linux?
Работа с PID по сути всегда гонка:
- Получаем PID процесса (например, PID=100);
- Процесс умирает,
- ОС создает новый процесс, переиспользуя PID=100;
- В итоге мы работаем с совершенно другим процессом.

Это причина уязвимостей: CVE-2019-6133, CVE-2019-15790... А так же причина одного из issue у Go .

Решение
В Linux 5.1 появился системный вызов pidfd_open(pid_t pid, unsigned int flags). Он даёт ссылку на конкретный процесс, а не на PID, который может быть переиспользован.
Дальше (вплоть до ядра 6.5) добавили и другие методы получения pidfd.

Что в Go?
И уже в Go 1.23 в методах и функциях, которые относятся к процессу используется по возможности pidfd.
🔥9👍4🍾2👨‍💻2😁1💅1
В последнее время довольно часто приходится работать напрямую с TCP/UDP, а иногда и по SSH.

В таких случаях особенно важно использовать connection pool и следить, чтобы соединение не "умерло" - - так мы избегаем накладных расходов на рукопожатие (особенно важно если в протоколе есть дополнительные "телодвижения").

Отлично в этом помогает библиотека от jackc -- puddle (автор, кстати, того же pgx).

Грубо говоря, это sync.Pool{}, только не для памяти, а для ресурсов, причём с возможностью ограничить количество создаваемых экземпляров. Если лимит достигнут, попытка получить новый ресурс блокируется до появления свободного -- что помогает контролировать нагрузку и избегать перерасхода соединений.

Примеры использования вне README можно посмотреть в исходниках pgx (файл pgxpool/pool.go) и ch-go (файл chpool/conn.go).

Ну и небольшой пример из README:

func main() {
constructor := func(context.Context) (net.Conn, error) {
return net.Dial("tcp", "127.0.0.1:8080")
}
destructor := func(value net.Conn) {
value.Close()
}
maxPoolSize := int32(10)

pool, err := puddle.NewPool(&puddle.Config[net.Conn]{Constructor: constructor, Destructor: destructor, MaxSize: maxPoolSize})
if err != nil {
log.Fatal(err)
}

// Acquire resource from the pool.
res, err := pool.Acquire(context.Background())
if err != nil {
log.Fatal(err)
}

// Use resource.
_, err = res.Value().Write([]byte{1})
if err != nil {
log.Fatal(err)
}

// Release when done.
res.Release()
}
👍9🔥7
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
Уххх!
В Go начали завозить intrinsics для SIMD.

То есть, возможно, можно будет вызывать SIMD инструкции напрямую, прямо из Go кода.

Для теста можно подтянуть ветку через gotip:
go install golang.org/dl/gotip@latest  # если ещё не установлено
gotip download dev.simd # собираем ветку dev.simd


Правда, пока что поддержка только для amd64, так что локально потыкать у меня не получится :(

Как только появится arm -- обязательно сделаю бенчмарки и поделюсь результатами.

Кратко о текущем состоянии -- в комментарии Cherry Mui: https://github.com/golang/go/issues/73787#issuecomment-3208178910
🔥31👏1🍓1💅1👾1
Всем привет!)

Второй год участвую в программном комитете конференции E-CODE, помогаю с формированием трека backend.

Буду лично на мероприятии, оно пройдёт 13-14 сентября.

Если вы тоже там будете -- можно пообщаться в перерывах между докладами)
6🥰4🐳1🍓1👾1
В Go завезли поддержку valgrind -- утилиты, которая может обнаруживать утечки памяти.

Это, конечно, круто, но зачем?

На этот вопрос ответил автор CL, а также security team lead в Golang -- Roland Bracewell Shoemaker.

Основная цель -- находить функции, которые выполняются не за константное время.

Тут у кого-то может возникнуть вопрос:
Зачем нам функции, которые работают за константное время? Разве мы не хотим, чтобы они выполнялись как можно быстрее?
А тут стоит посмотреть на роль того, кто добавил CL SECURITY team lead.

В криптографии очень важно, чтобы функции выполнялись за константное время.
Если передавать разные входные данные и замерять время выполнения, можно провести timing oracle attack.

Тривиальный и упрощённый пример:
Мы побайтово сравниваем два хеша и при первом несовпадении выходим из функции раньше.

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

Чтобы предотвратить это, нужно, чтобы доступ к памяти и ветвление кода были одинаковыми для всех входных данных.

Для проверки этого как раз и нужен valgrind -- он отслеживает, какие области памяти и регистры используются в каждой ветке исполнения.

Подробнее про использование valgrind в рамках поиска функции, которые выполняются не за константное время можно почитать тут репозиторию аж 15 лет.
🔥93👾2💅1