Что делать – Telegram
Что делать
100 subscribers
209 photos
3 videos
4 files
130 links
Не смешно
Download Telegram
Простое объяснение CDN

Что это? Это - Content Delivery Network/Content Distribution Network, т.е. распределенная сеть серверов. Зачем она? Для того, чтобы клиент мог получать статичные данные* с географически ближайшего сервера для уменьшения задержек. Как устроена? Вот здесь немного по-подробней.

*статичные данные - могут быть как обычными файлами фронтэнда, так и музыкальными трэками (Spotify), видео-материалами (YouTube, Netflix), и любыми другими типами данных, не требующих динамического взаимодействия

Для начала, мы должны ввести два понятия:
1) Origin, он же ориджин - главный сервер, который является источником статических данных.
2) PoP (point of presence), она же точка присутствия - непосредственно сервер-репликант, доставляющий контент клиентам

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

Важно: данный трюк маловероятно сработает с utf8

Ну а если у нас есть массив байт в кодировке ASCII, то для перевода всех символов в нижний регистр можно просто применить к каждому символу операцию bitwise OR со значением 0x20. Пример:

text := []byte("Hello, World!")
for i, char := range text {
text[i] = char | 0x20
}

...после чего, переменная text будет уже в себе хранить значение hello, world!. Сравнение с bytes.ToLower() см. постом ниже
👍1
Что делать
Возвращаясь к приколам с []byte, или как быстро и без накладных приводить чары в ловеркейс Важно: данный трюк маловероятно сработает с utf8 Ну а если у нас есть массив байт в кодировке ASCII, то для перевода всех символов в нижний регистр можно просто применить…
Прикладываю результаты бенчмарков по множеству просьб подписчиков. Тестирование проводилось в таком виде. Результаты следующие:

Для bytes.ToLower():
BenchmarkSTDShortLowercase-12           23080384                51.11 ns/op
BenchmarkSTDLongLowercase-12 7538932 161.1 ns/op
BenchmarkSTDShortMixed-12 19974499 59.08 ns/op
BenchmarkSTDLongMixed-12 3973327 304.0 ns/op
BenchmarkSTDShortUppercase-12 27904444 42.65 ns/op
BenchmarkSTDLongUppercase-12 5482158 220.9 ns/op

Для собственной реализации, код которой лежит в файле с бенчмарками, в функции ToLowercase():
BenchmarkOwnShortLowercase-12           41382306                29.19 ns/op
BenchmarkOwnLongLowercase-12 10526306 108.4 ns/op
BenchmarkOwnShortMixed-12 35293806 29.89 ns/op
BenchmarkOwnLongMixed-12 10962948 111.6 ns/op
BenchmarkOwnShortUppercase-12 40000665 29.95 ns/op
BenchmarkOwnLongUppercase-12 10810712 111.2 ns/op

И здесь мы видим, что в среднем, наша функция быстрее в 2 раза, нежели реализация в std-либе

Однако, почему так? Вопрос кроется в том, что bytes.ToLower() корректно работает также с utf8
Ответ кроется в том числе в исходном коде самой функции ToLower(). Как мы видим, сначала идёт итерация по входному массиву для определения того, состоит ли массив из обычных ASCII-символов (и дополнительно проверка на корнер-кейс в случае со строкой и без того в ловеркейсе). В случае, если в строке не присутствуют unicode-символы, берётся беспонтовый алгоритм со смещением, равным разнице алфавитов в нижнем и верхнем регистрах. Сравнивать производительность с нашим вариантом я не буду, так как мне лень не имеет смысла и оба варианта достаточно шустры. В результате, мы итерируемся дважды по одному и тому же массиву. И не дай боже нам встретится unicode-символ! Ибо только бог знает, как работает та функция, поскольку там творятся страшные вещи. Очень страшные вещи. Даже не пытайтесь понять, что там происходит
Крч делюсь инфой про тайпкасты 🔥
Это когда вы переводите один тип переменной в другой тип.

Например, в переменную типа byte на 8 бит не получится запихнуть значение типа ushort на 16 бит или вообще int на 32.
Логично? Логично 🌚

Так вот при тайпкасте производится сужение или же расширение конвертируемых значений.
В программировании это также называется narrowing и widening conversions.
И таким образом, в случае расширяющего преобразования, значение 4 типа byte будет представлено как 00000100 в двоичной системе.
Оно же после расширения в ushort будет выглядеть как 0000000000000100.
Значение расширилось до 16 бит (разрядов).

Однако эти операции также делятся на явные (explicit) и неявные (implicit).
Так вот в случае неявных расширяющих преобразований (implicit widening conversion), всё предельно ясно и просто.
Компилятор за нас всё перекидывал, лишь расширяя разряды.

Причем для беззнаковых типов данных меньшей разрядности к беззнаковому типу большей разрядности, он лишь добавлял биты со значением 0.
Логично?
Не думал, что буду форвардить посты Хауди хо. Но написано вроде как корректно и тема донесена простым языком
Вижу, что многие пользуются black, isort или как минимум форматируют код в PyCharm. Инструменты хорошие, но они ориентируются на устаревший PEP-8, поэтому в ближайшее время придётся подыскивать им замену.

Опубликован черновик нового стайл-гайда PEP-9001, который через какое-то время станет обязательным для соблюдения, поэтому рекомендую всем ознакомиться уже сейчас и присоединиться к обсуждению:

https://peps.pythondiscord.com/pep-9001/

#pep
🔥2
Как устроена цифровая подпись

К примеру - электронное письмо. Когда нужно удостовериться, что оно не было изменено в процессе передачи - используется хэш оригинального наполнения письма. И его, очевидно, можно легко изменить. Для того, чтобы это было практически невозможным - используют асимметричные алгоритмы шифрования. Например - RSA.

RSA имеет два ключа: одним мы можем только зашифровать, но не можем расшифровать, и то же самое со вторым, только наоборот. Это даёт нам возможность без зазрения совести делиться публичным ключом, ведь расшифровать зашифрованные им данные может только владелец приватного ключа. По этому принципу, к слову, работает https (да и практически вся криптография в интернете)

А непосредственно для подписи - хэш письма шифруется приватным ключом. В результате, любой, имея публичный ключ, может расшифровать хэш и сравнить с тем, что представлено в письме, однако не имея возможности этот самый хэш изменить
🤮2
Как найти источник излишнего количества аллокаций?

go build -gcflags=“-m”. Данная команда выведет результат эскейп-анализа. Другими словами, компилятор сообщит вам, какие переменные требуют аллокации на куче, а какие - спокойно себе живут на стэке. Для более подробного отчёта, можно использовать команду go build -gcflags="-m -m”
🤔1
Префиксное дерево

…также известное, как: нагруженное дерево, и trie

Это очень похоже на бинарное дерево, только имеющее иную цель, и позволяющее держать произвольное количество листьев. Суть сводится к тому, что это ассоциативная структура данных, визуализация которой видна на пикриле. Широко используется, например, в Т9

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


const tail = byte(0)

type Leaf struct {
char byte
leaves []Leaf
}

func (l Leaf) IsTail() bool {
return getLeaf(tail, l.leaves) != nil
}

func (l Leaf) Validate(data []byte) (isValid bool) {
if len(data) == 0 {
return l.IsTail()
}

leaf := getLeaf(data[0], l.leaves)

if leaf == nil {
return false
}

return leaf.Validate(data[1:])
}

func getLeaf(char byte, leaves []Leaf) *Leaf {
for _, leaf := range leaves {
if leaf.char == char {
return &leaf
}
}
return nil
}


Данным кодом мы реализовали рекурсивный проход по префиксному древу. Как мы можем видеть, в методе IsTail() мы возвращаем логическое значение, является ли текущий узел хвостовым посредством поиска “магической” константы в списке своих узлов. Метод Validate() же возвращает логическое значение, совпадает ли поданная строка посимвольно с деревом. В случае, если поданная строка закончилось, однако дерево подразумевает продолжение (т.е. последняя ветка не является хвостовой), то такая строка тоже отсекается как невалидная.

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

Важно упомянуть, что по скорости данное решение будет не то, чтобы медленнее, а даже местами быстрее хэшмапы и сбалансированного дерева на получение по ключу. По памяти - тоже выигрыш, однако при условии, что наше дерево будет “сжато” - то есть, промежуточные узлы, ведущие к единственному не-промежуточному узлу. Например, дерево с узлами a->b->c->d|e можно сжать до вида abc->d|e, другими словами, объеденив несколько промежуточных узлов в один
👍21
Forwarded from Subchannel
Интересно кстати. Знал что криптография на элиптических кривых быстрее чем на умножении натуральных чисел.
Но оказывается там не всё так просто. например подпись ed25519 в ~10 раз быстрее чем rsa2048 и в ~100 раз быстрее rsa4096. А вот проверка ВНЕЗАПНО у ed25519 в 2 раза медленнее, чем rsa4096 и в 7 раз медленее, чем rsa2048.
Так что если у вас есть софт, который проверяет много раз в секунду подписи, которые выдаются редко (например jwt токены), то rsa2048, возможно, будет вариантом лучше.
Век живи - век учись.
👏1🤯1
Модификация именованного возврата через defer

Встретил недавно прикольную штуку - при именованном возврате возвращаемое значение можно подменять в defer'e. Выглядит это примерно так:


func MyFunc() (result bool) {
defer func() {
result = true
}()

return
}


Логично догадаться, что defer выполняется при выходе из функции. А поскольку мы используем именной возврат, возвращается конкретно переменная result - которая по-умолчанию равняется false, однако благодаря defer'у становится true

Пользуясь случаем, передаю привет Косте
👍1
Я вспомнил пароль от канала

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

Начну, пожалуй, с того, как пофиксив проблему, я обнаружил баг в тестах

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

Починив проблему, тесты парсера вдруг улетели в дедлок. Потупив полчаса, я осознал, что все тесты проходят, кроме одного - странная особенность фреймворка. Посмотрев подробнее, что не так, я осознал, что я хочу видеть в запросе заголовок, как и во всех остальных тестах. А в самом запросе его нет. И работало это как раз таки потому, что другие тесты оставляли после себя этот самый заголовок!

Забавно фиксить проблему в коде, в следствии чего находить баг в тестах.
🔥2
Кстати, ещё интересных штук о заголовках.

У парсера есть свой буфер, куда он кладёт текущий заголовок (ключ и значение). Как только он кончается - его нужно куда-то копировать, иначе записанное значение будет перезаписано последующими

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

Ведь у нас есть слайсы, которые могут указывать только на свою часть!

Да, у этого способа есть свои проблемы. Например, при росте буфера, все его предыдущие версии будут оставаться висеть, и GC не сможет их очистить, потому что они всё ещё нам нужны. Однако случается это один раз (потом буфер будет больше, и вмещать в себе всё, что надо), и висеть мёртвым грузом старые версии будут лишь на время обработки запроса, после чего со спокойной душой смогут быть очищены GC.

Преимущество этого способа в том, что нам даже sync.Pool не нужен - вот он, один наш слайс, всегда здесь, при окончании обработки запроса просто buffer = buffer[:0], и готово!
It ain't much, but it's honest work
🥰1
Начал трогать CI в виде гитхаб-экшонов. Оказывается, не настолько и страшная штука!

При каждом пулл-реквесте в указанные ветки (у меня это мастер) прогоняет тесты, билдит примеры (проверяя их тем самым на работоспособность), и заодно прикрутил ещё CodeQL-анализ. Последнее, правда, пока что бесполезное, но выглядит круто, и в случае чего - перепроверяет, что в мастере будет работоспособная штука

Был у меня ещё, к слову, забавный момент. У меня также подключён deepsource, и при одном из PR он заблокировал мерж из-за потенциального бага. Оказывается, я передавал инстанс не по указателю, а по значению, из-за чего важная часть сервера потенциально не работала. Круто, скажете вы? А вот это должно было быть покрыто тестами, отвечу я
Forwarded from Котб в пакете (0𝔁𝓒𝓪𝓽𝓟𝓚𝓖)