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

help: @Vallder
Download Telegram
Endianness или конечность порядок байт

Компьютеры по-разному хранят данные в оперативной памяти.

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

А все потому что люди до сих пор не договорились как хранить данные.

Есть системы, которые хранят от старшего (MSB most significant byte) к младшему (LSB least significant byte), а есть наоборот от LSB к MSB.
MSB и LSB не стоит путать с MSb (most significant bit) и LSb (least significant bit) одно про биты, другое про байты.

В 1980 году для обозначения порядка байтов было введено два понятия: Little-Endians и Big-Endians, ссылаясь на документ под названием Gulliver's Travel (1726 г.).

Little-Endian — (LE) порядок байт от LSB к MSB (слева направо)
Big-Endians — (BE) порядок байт от MSB к LSB (справа налево)

Как выглядит на практике?
const u uint32 = 1
le := make([]byte, 4)
binary.LittleEndian.PutUint32(le, u)

be := make([]byte, 4)
binary.BigEndian.PutUint32(be, u)

fmt.Printf("%b\n", le) // output: [1 0 0 0]
fmt.Printf("%b\n", be) // output: [0 0 0 1]


Берем 1 в uint16, в байтовом представлении у нас должна быть только одна единица в позиции LSB.

В Little-Endian единица находится слева.
В Big-Endian единица находится справа.

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

И зачем же знать все это дело?
Чтоб в ногу не стрелялось

— При работе с UTF-16, UTF-32 следует помнить про BOM (byte order mark), константа которая поможет определить порядок байт.
В LE это [254 255] в BE это [255 254].

— При сериализации слайс байтов в конкретный тип.

К примеру нам приходит слайс байтов, мы знаем что этот слайс на самом деле uint64.
И если мы сконвертируем через unsafe, мы можем выстрелить в ногу, из-за разности понимания старшого и младшего бита (пример выше).

— Обратный пример использования unsafe:
t := &struct {
first, second uint16
}{
first: 1,
second: 0,
}

v := *(*uint32)(unsafe.Pointer(t))
fmt.Println(v) // output: 256 or 1


Результат может быть разным:
- 1 при Little-Endian
- 256 при Big-Endian

В комментарий скину код, который симулирует поведение при LE и при BE.

— В других случаях, которые я еще не придумал.

P.S.
PostgreSQL хранит файлы страниц как представление оперативной памяти, поэтому копирование файлов на другую машину с целью скопировать базу данных может быть ошибкой, из-за порядка байт.
4💅32
Небольшой факт:

Название операционной системы Plan 9, а если быть точнее Plan 9 from Bell Labs было вдохновлено очень спорным фильмом Plan 9 from Outer Space.

Как связана эта ОСь с тематикой канала?

Команду Plan 9 возглавляли: Rob Pike, Ken Thompson, они же в дальнейшем работали над Golang и многое привнесли из Plan 9 в сам язык.

На эту тему могу посоветовать доклад: Почему Golang такой странный.

Достойно упоминания:
Plan 9 строился на идее Name Space, спустя 10 лет подобный функционал появился в Linux под названием: namespaces это одна из основ контейнеризации.
2💅2👾1
Как же я люблю находить хитрые оптимизации внутри Go.

src/strconv/itoa.go
// small returns the string for an i with 0 <= i < nSmalls.
func small(i int) string {
if i < 10 {
return digits[i : i+1]
}
return smallsString[i*2 : i*2+2]
}

const nSmalls = 100

const smallsString = "00010203040506070809" +
"10111213141516171819" +
"20212223242526272829" +
"30313233343536373839" +
"40414243444546474849" +
"50515253545556575859" +
"60616263646566676869" +
"70717273747576777879" +
"80818283848586878889" +
"90919293949596979899"


И функции, которые возвращают строковое представление по целочисленному типу используют эту микро оптимизацию.

Не могу описать свой восторг, когда натыкаюсь на подобное.
🔥32💅2
Еще нечто подобное есть в пакете net/textproto, функционал которого использует net/http.Header{} для канонизации.

src/net/textproto/reader.go
func initCommonHeader() {
commonHeader = make(map[string]string)
for _, v := range []string{
"Accept",
"Accept-Charset",
"Accept-Encoding",
...
"X-Powered-By",
} {
commonHeader[v] = v
}
}


Для того чтоб избежать копирования слайса байт при string(a):

src/net/textproto/reader.go:782
// The compiler recognizes m[string(byteSlice)] as a special
// case, so a copy of a's bytes into a new string does not
// happen in this map lookup:
if v := commonHeader[string(a)]; v != "" {
return v, true
}
return string(a), true
🔥2💅21👾1
Давно не следил за SwissMap в Golang.

И кажется, есть вероятность, что в Go 1.24 поменяется алгоритм мап на Swiss Tables.

Три недели назад в master ветке появился коммит:
internal/runtime/maps: initial swiss table map implementation


Почитать про SwissMap можно тут:
Оригинальный issue: тык
Описание Swiss Tables: тык
Success story команды DoltDB: тык
🔥6💅4👍1🥰1
Небольшое напоминание о том, что каналы необязательно закрывать.

Об этом еще писал Rob Pike в 2011 году.

https://groups.google.com/g/golang-nuts/c/pZwdYRGxCIk/m/rGr1D-uTQMEJ
9👍3💅3🥰1🤝1
TL;DR: Для масштабирования gRPC в K8s, следует взять linkerd или нечто подобное.

Есть некий клиент (c) и некий сервер (s) в K8s, линия обозначает TCP соединение.
+--+   +--+
|c +---> s|
+--+ +--+


Используя K8s можно достаточно легко горизонтально масштабировать приложение:
- Руками изменить scale.
- Настроить HPA (Horizontal Pod Autoscaling)

И вроде все хорошо — у нас n-ое количество подов, и нагрузка должна автоматом распределятся по round-robin, ведь так?
+--+   +--+         
|c +---> s|
+--+ +--+
| +--+
+-----> s|
| +--+
| +--+
+-----> s|
+--+


Тут уже начинаются детали.

Распределение нагрузки будет работать, в зависимости от того, по какому именно транспорту будут общаться сервисы.

K8s использует iptables как балансировщик, а он работает на уровне TCP соединений, то есть может балансировать TCP соединения.

HTTP/1.1: TCP соединения переиспользуются только после завершения HTTP запроса, то есть: в одно и тоже время в одном TCP соединении может обрабатываться только один HTTP запрос.
HTTP/2: Позволяет использовать одно и тоже TCP соединения для нескольких одновременных HTTP запросов (multiplexing).

Следовательно, при HTTP/2 у нас используется одно TCP соединение, внутри которого проходят множество запросов.

Балансировка будет выглядеть так:
+--+   +--+         
|c +---> s|
+--+ +--+
+--+
| s|
+--+
+--+
| s|
+--+


Все запросы будут идти на первый сервер.

А gRPC использует HTTP/2 для транспорта.

——
То есть: балансировщик из коробки в K8s для gRPC трафика будет некорректно балансировать нагрузку, иначе говоря: балансировать TCP соединения, а не gRPC запросы.
——

Что делать?
Есть пару вариантов:
1. Взять Linkerd, он умеет балансировать gRPC с коробки.
*ИМХО самый корректный*

2. Так же, можно чуть чаще пересоздавать gRPC соединения, тут про это пишут.
*ИМХО самый простой*

2. Внутри самого сервиса: README.md.
*ИМХО самый странный*
🔥6💅211👍1
Недавно делал пост, в котором предполагал, что алгоритм для map поменяется на Swiss Tables.

А сейчас нахожу, что Swissmaps будут дефолтными, об этом написали в meeting notes.

До этого не было понятно, будут ли они доступны из коробки или через экспериментальный флаг, так как флаг уже реализован в master: flags.go.

Спасибо t.me/crossjoin за наводку)
🔥64💅3
Сегодня немного философии.

Откуда мы что-то узнаем?

Думаю многие знают что запуск сервиса на нулевом порту это запуск на случайном открытом порту, но откуда вы это узнали?

В RFC6335 описано как распределяются порты (системные, пользовательские и так далее),
а в IANA описано то, как уже конкретные порты распределяются по сервисам (DNS, HTTP, HTTPS...).

Для TCP и UDP в IANA нулевой порт помечен как зарезервированный, что вообще не описывает реальную ситуацию.

Может сказано в документации Linux?

При открытии сокета, порт впервые появляется в системном вызове bind.
В man упоминается нулевой порт только при ошибке, что опять же не описывает поведение.

Отчаявшись пошел уже читать исходники Linux. И нашел следующую функцию:
/* Obtain a reference to a local port for the given sock,
* if snum is zero it means select any available local port.
* We try to allocate an odd port (and leave even ports for connect())
*/
int inet_csk_get_port(struct sock *sk, unsigned short snum)


Если кратко, то:
Если передается нулевой порт, то аллоцируем нечетный порт


Про четный и нечетный порт в следующий раз напишу.

Я ожидал что этот путь будет чуть проще, я его не проходил ранее, но я знал эту информацию.
👍8💅3
В последнее время занимался тем, что немного привел в порядок свой линтер: canonicalheader, который входит в golangci-lint.

Добавил туда возможность конфигурирования, хотя мне эта идея не сильно нравится, но сообщество просило (#39, #12).

Из-за того что упростил еще текст issue, то обновление через dependabot не сработает и нужно будет руками обновить в golangci-lint.
👍4💅42🔥1👾1
В clickhouse есть функция left(s, offset)
Учитывая что clickhouse это колоночная база данных, кажется можно оптимизировать чтение строки, особенно если используется кодек без сжатия.

И читая сурсы, кажется, что никаких оптимизаций нет.

Функция left, по сути использует sliceFromLeftDynamicLength, которая принимает IColumn — интерфейс строки в памяти. Соответственно строка уже прочитана из файловой системы.

Несколько грустно, что нет такой оптимизации :(

Дисклеймер:
С++ не мой основной язык, если есть что добавить — добро пожаловать в комментарии.
🤪3🔥2💅2👾1
Благодаря 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