Официальный новостной канал криптобиржи OKX | www.okx.com на русском языке.
💬 Комьюнити: t.me/okx_russian
👨💻 Поддержка: [email protected]
АДМИН: @DaniiOKX
Маркетинг: @CoffeeTrends
Last updated 2 weeks, 3 days ago
Here in simple language about TON and crypto
Founder: @metasalience
contact : @deftalk_bot
Last updated 3 months, 2 weeks ago
Канал о TON и все что с ним связано:
1. Аналитика
2. Инсайды
3. Авторское мнение
Ведро для спама: @ton_telegrambot
Бот с курсами криптовалют: @TonometerBot
Чат: @chaTON_ru
Админ: @filimono
Last updated 2 weeks, 5 days ago
Заглушить логи
С появлением пакета log/slog в Go 1.21 наконец-то стало возможно нормально вести журналы без использования внешних библиотек.
Если еще не пробовали, то рекомендую простенький туториал и доку, она понятная и с примерами.
log := slog.New(
slog.NewTextHandler(os.Stdout, nil),
)
log.Info("operation", "count", 3, "took", 50*time.Millisecond)
time=2024\-12\-09T10:20:47.660+00:00 level=INFO msg=operation count=3 took=50ms
Но вообще сказать я хотел не о самом slog
, а о том, как в нем заглушить логи (например, для тестов или бенчмарков).
Сделать это несложно — просто используйте io.Discard
в качестве приемника для slog.TextHandler
:
log := slog.New(
slog.NewTextHandler(io.Discard, nil),
)
log.Info("Prints nothing")
Удобно!
Скорость алгоритмов: O(1)
O(1), так же известно как «константное время». Самый лучший вариант, скорость алгоритма не зависит от количества ~~котиков~~ входных данных.
*🐾 Пример*
Вы — счастливый обладатель N котиков. Каждый котик знает, как его зовут. Если позвать «Феликс!», то прибежит только он, а остальным N-1 жопкам пофиг.
***🐈*** ***🐈*** ***🐈*** ***🐈*** ***🐈***
***🐈*** ***🐈⬛*** ***🐈*** ***🐈*** ***🐈***
↓
мяу!
В жизни время выполнения O(1) обычно встречается при работе со срезами и картами.
Срезы
У среза известен адрес в памяти первого элемента и размер каждого элемента. Следовательно, адрес i-го элемента тоже известен (first + i*size)
— а значит, доступ к i-му элементу выполняется за константное время. Изменение i-го элемента аналогично — тоже O(1).
s := make([]int, 1e6)
i := rand.IntN(1e6)
s[i] = 42 // O(1)
fmt.Println(s[i]) // O(1)
С добавлением и удалением элементов сложнее — они рано или поздно приводят к созданию нового массива под срезом — а это уже операция O(n). Но если аккуратно все посчитать, то получится, что в среднем добавление/удаление тоже константные.
Карты
Не вдаваясь в подробности скажу, что за картой тоже скрывается массив. Грубо говоря, когда вы пишете m["answer"] = 42
, то Go превращает "answer" в целое число (хеш строки), после чего считает по нему нужный индекс в массиве. По этому индексу и находится значение 42.
d := map[string]int{}
d["answer"] = 42 // O(1)
fmt.Println(d["answer"]) // O(1)
Поэтому доступ к элементу карты по ключу — тоже O(1).
🐈
Скорость алгоритмов: O-нотация
Скорость работы алгоритмов принято оценивать в О-нотации: у медленного она «о», у более быстрого «ооо», у самого быстрого «ооооо ваще огонь».
Вру, конечно.
На самом деле, скорость оценивают в количестве действий, которое выполняет алгоритм. Например, если у вас дома 10 котов, и нужно определить самого увесистого, то придется взвесить каждого кота, то есть выполнить 10 операций.
Алгоритм, понятное дело, должен работать одинаковым образом что для 10 котиков, что для 100, что для 10000. Оценка же скорости нужна одна. Поэтому оценивают не конкретное количество операций, а количество операций относительно количества входных данных (котов в нашем случае).
Если у вас n
котов, то чтобы определить самого жирненького, придется выполнить n
взвешиваний. В таком случае говорят, что скорость работы алгоритма — O(n)
.
В реальности действий всегда будет не n
, а несколько больше. Например, сначала нужно откалибровать весы, а в конце убрать их обратно в шкаф. Получается уже n+2
действия. Но поскольку дополнительных действий константное количество, их в оценке не учитывают: O(n+2)
= O(n)
А что делать, если котики отказываются взвешиваться без рыбов? Тогда на каждого кота приходится 2 действия: выдать рыбку и взвесить, поэтому общее количество действий 2n
. Но фактор 2× тоже константный, поэтому и его в оценке не учитывают: O(2n)
= O(n)
Ровно так это работает и в коде:
var fattest Cat
for \_, cat := range cats {
if cat.weight > fattest.weight {
fattest = cat
}
}
Здесь на каждого кота выполняется до трех действий:
То есть действий может быть до 3n+1
(+1 — инициализация переменной fattest
). Но скорость работы алгоритма — O(n)
Таким образом, в O-нотации любые константные факторы игнорируются. Есть и другие нюансы, но с ними разберемся дальше по ходу дела.
P.S. Я пока не уверен, что реально нужна серия про скорость алгоритмов — может, для всех это совсем очевидная тема. Посмотрим, как пойдет.
🐈
Go 1.23
Тут вышел Go 1.23, ну а мы с вами успели разобрать основные изменения заранее:
— Итераторы
— Таймеры
— Уникальные значения
— Скопировать каталог
— Куки
Все вместе с интерактивными примерами:
https://antonz.org/go-1-23
Большая крыса Go
Прежде чем вы решите, что я сошел с ума — речь на самом деле о типе big.Rat
.
В отличие от float64
, он позволяет работать с обыкновенными дробями (a/b) без потери точности.
Например, из школьного курса математики мы знаем, что 1/10 + 2/10 = 3/10. Однако, float64 другого мнения:
x := 0.1
y := 0.2
fmt.Println(x + y)
// 0.30000000000000004
А вот big.Rat справляется с такими вычислениями без проблем:
x := big.NewRat(1, 10)
y := big.NewRat(2, 10)
z := new(big.Rat)
z.Add(x, y)
fmt.Println(z)
// 3/10
Если вдруг придется работать с обыкновенными дробями — имейте «крысу» в виду.
Статический HTTP-сервер
Вы, наверно, слышали про встроенный в Python статический сервер:
python \-m http.server 8080
На Go его можно реализовать в десять строчек кода (плюс импорты):
```
func main() {
port := "8000"
if len(os.Args) > 1 {
port = os.Args[1]
}
fs := http.FileServer(http.Dir("."))
http.Handle("/", fs)
log.Printf("Serving HTTP on port %s...\n", port)
log.Fatal(http.ListenAndServe(":"+port, nil))
}
```
И запускать вот так:
go run http.go 8080
Git в примерах
Зашел я на Степик в новые курсы, а там «Основы Git». В связи этим вспомнил, что у меня тоже есть интерактивная книга / сборник рецептов по гиту, называется Git by example.
Удобный краткий формат с конкретными примерами, я сам туда постоянно подглядываю (особенно в раздел undo).
Загляните и вы, если не обзавелись еще черным поясом по гиту.
https://antonz.org/git-by-example
P.S. А вы же знаете, что по Go тоже такая есть?
Безопасные ворота
Вот три способа безопасно закрыть ворота, один другого краше.
➊ sync.Mutex
Дубовый, но надежный способ. Защищаем изменение логического поля closed
мьютексом:
```
type Gates struct {
// признак закрытия ворот
closed bool
// мьютекс для защиты closed
mu sync.Mutex
}
func (g *Gates) Close() {
g.mu.Lock()
defer g.mu.Unlock()
if g.closed {
// игнорируем повторное закрытие
return
}
// закрыть ворота
g.closed = true
// освободить ресурсы
}
```
➋ atomic.Bool
Compare-and-set на атомарном bool гарантирует, что только одна горутина сможет поменять значение с false на true:
```
type Gates struct {
// признак закрытия ворот
closed atomic.Bool
}
func (g *Gates) Close() {
if !g.closed.CompareAndSwap(false, true) {
// игнорируем повторное закрытие
return
}
// закрыли ворота,
// можно освободить ресурсы
}
```
➌ sync.Once
Once.Do
гарантирует однократное выполнение в конкурентной среде, поэтому не приходится даже явно хранить состояние:
```
type Gates struct {
// гарантирует однократное выполнение
once sync.Once
}
func (g *Gates) Close() {
g.once.Do(func() {
// освободить ресурсы
})
}
```
Правда, такие ворота уже не получится открыть обратно, в отличие от предыдущих вариантов.
Кто ваш любимчик? Поделитесь в комментариях.
Опасные ворота
Вот наши ворота:
```
type Gates struct {
// признак закрытия ворот
closed chan struct{}
}
func (g *Gates) Close() {
select {
case <-g.closed:
// игнорируем повторное закрытие
return
default:
// закрыть ворота
close(g.closed)
// освободить ресурсы
}
}
```
Метод Close
— небезопасный. Если две горутины одновременно вызовут Close
, обе могут провалиться в default-ветку селекта, обе попытаются закрыть канал, и второе закрытие приведет к панике.
Другими словами, здесь гонки на закрытии канала. Селект сам по себе не защищает от гонок. Sad but true.
Что с этим делать — традиционно в следующей заметке.
Баян [::]
Гошный баян (вообще он «полносрезное выражение» или full slice expression, но «баян» мне ближе) имеет такой синтаксис:
s[low : high : max]
Баян создает срез длиной high\-low
и емкостью max\-low
. Используется крайне редко.
Чтобы понять разницу между обычным срезом и баяном, рассмотрим пример.
Как вы знаете, под каждым срезом лежит массив с данными (сам срез данных не содержит). Обычно этот массив создается неявно, но мы для наглядности сделаем так:
```
arr := [5]int{1, 2, 3}
// [1 2 3 0 0]
s := arr[0:3]
// [1 2 3]
len(s) // 3
cap(s) // 5
```
Срез s
указывает на массив arr
. Его длина (length) равна 3, а емкость (capacity, размер массива под срезом) равна 5.
Добавление элемента в срез добавляет его в массив, поскольку емкость это позволяет:
```
s = append(s, 4)
fmt.Println(arr)
// [1 2 3 4 0]
fmt.Println(s)
// [1 2 3 4]
```
А вот что будет, если создать срез с помощью баяна:
```
arr := [5]int{1, 2, 3}
// [1 2 3 0 0]
s := arr[0:3:3]
// [1 2 3]
len(s) // 3
cap(s) // 3
```
Все как раньше, только емкость среза равна 3. Поэтому добавление элемента в срез приведет к созданию нового массива под срезом. Исходный массив arr
не изменится:
```
s = append(s, 4)
fmt.Println(arr)
// [1 2 3 0 0]
fmt.Println(s)
// [1 2 3 4]
```
Такие дела. За разновидностями баянов приглашаю в комментарии.
?
Официальный новостной канал криптобиржи OKX | www.okx.com на русском языке.
💬 Комьюнити: t.me/okx_russian
👨💻 Поддержка: [email protected]
АДМИН: @DaniiOKX
Маркетинг: @CoffeeTrends
Last updated 2 weeks, 3 days ago
Here in simple language about TON and crypto
Founder: @metasalience
contact : @deftalk_bot
Last updated 3 months, 2 weeks ago
Канал о TON и все что с ним связано:
1. Аналитика
2. Инсайды
3. Авторское мнение
Ведро для спама: @ton_telegrambot
Бот с курсами криптовалют: @TonometerBot
Чат: @chaTON_ru
Админ: @filimono
Last updated 2 weeks, 5 days ago