Допустим, у меня есть подобный конструктор ответов (пикрил 1). В каждый метод (пикрил 2) я передаю структуру по значению - то есть, она копируется. А значит, исходную структуру я не мутирую - это чистые методы.
А вот нюанс (как и написано в комментарии) заключается в заголовках. Это хэшмапа, которая не копируется при передаче по значению, ведь внутри она указывает на конкретные участки памяти. И это может привести к неприятным последствиям в виде передачи заголовков от предыдущего запроса в следующем
Выход в данной ситуации - сделать структуру как nil и не давать пользователю к ней прикасаться. В таком случае, в методе
А вот нюанс (как и написано в комментарии) заключается в заголовках. Это хэшмапа, которая не копируется при передаче по значению, ведь внутри она указывает на конкретные участки памяти. И это может привести к неприятным последствиям в виде передачи заголовков от предыдущего запроса в следующем
Выход в данной ситуации - сделать структуру как nil и не давать пользователю к ней прикасаться. В таком случае, в методе
WithHeader нам приходится проверять на nil (и если хэшмапа оказывается действительно nil, создаём новую с нашем заголовком). Получилось неприятно, однако сохраняется чистота. К слову, со слайсами данный трюк был бы немного попроще🤔> Instructions, registers, and assembler directives are always in UPPER CASE to remind you that assembly programming is a fraught endeavor. (Exception: the g register renaming on ARM.)
Вырезка из доки по go asm. Спасибо
Вырезка из доки по go asm. Спасибо
https://github.com/golang/go/blob/master/doc/go_mem.html#L36-L41
If you must read the rest of this document to understand the behavior of your program, you are being too clever.
Don't be clever.
Ну ладно
If you must read the rest of this document to understand the behavior of your program, you are being too clever.
Don't be clever.
Ну ладно
GitHub
go/go_mem.html at master · golang/go
The Go programming language. Contribute to golang/go development by creating an account on GitHub.
🤔2
Оказывается, существует
Вызов финализатора не гарантирован, ведь программа может завершить свою работу ещё до того, как будет собран необходимый объём мусора
Если указатель на объект занимает 0 байт, то нет гарантий, что финализатор будет вызван. Также нет гарантий, что финализатор будет когда-либо вызван для объектов в package-level, так как они могут быть аллоцированы линкером, а не на хипе
Чтобы убрать финализатор объекта, достаточно установить финализатор в значение nil
runtime.SetFinalizer(x, f), где x - указатель на объект (в случае локальной переменной, при взятии указателя, она будет аллоцирована на хипе), а f - функция, способная принять аргументом x (это может быть как variadic, так и generic функция). Когда garbage collector собирается очистить x, но видит, что для него есть финализатор - сначала вызывается финализатор в отдельной горутине (одна для всех финализаторов), сам же объект будет очищен при следующем вызове gc, если только финализатор завершил свою работуВызов финализатора не гарантирован, ведь программа может завершить свою работу ещё до того, как будет собран необходимый объём мусора
Если указатель на объект занимает 0 байт, то нет гарантий, что финализатор будет вызван. Также нет гарантий, что финализатор будет когда-либо вызван для объектов в package-level, так как они могут быть аллоцированы линкером, а не на хипе
Чтобы убрать финализатор объекта, достаточно установить финализатор в значение nil
🤔1😱1
День открытий. Впервые за 2/3 года, как я изучаю go, узнал об операторе
fallthrough. Эта штука "проваливается" в кейс снизу, не обращая внимание на условие этого самого нижнего кейса. Кстати, в финальном кейсе (просто последний; может быть и не дефолтом вовсе) использование этого оператора запрещено👍1😁1
Simple request/response
Хоть и не про Go, однако все равно интересно (по крайней мере мне).
Из rfc1945 (посвящённому http/1.0), прочитал про такую штуку, как simple request/response. Суть в следующем:
– Simple request - это обязательно GET-запрос, состоящий из соответственно метода и пути запроса. После пути запроса, следует [CR]LF, без каких-либо заголовков
– Simple response - доступен только в случае simple request. Сервер отправляет суто тело ответа, без строки ответа и каких-либо заголовков. Терминатором тела является закрытие подключения, поскольку стандарт требует, чтобы сервер отвечал по http/0.9 протоколу (сам запрос автоматически должен считаться, как http/1.0)
Из нюансов - клиент должен сам детерминировать MIME-тип. Однако не беда, вон, мой сервер тоже не сильно мимами козыряет🌚
По поводу использования на практике - сказать, увы, не смогу. Однако звучит прикольно - поддерживать мы это, конечно же, не будем
Хоть и не про Go, однако все равно интересно (по крайней мере мне).
Из rfc1945 (посвящённому http/1.0), прочитал про такую штуку, как simple request/response. Суть в следующем:
– Simple request - это обязательно GET-запрос, состоящий из соответственно метода и пути запроса. После пути запроса, следует [CR]LF, без каких-либо заголовков
– Simple response - доступен только в случае simple request. Сервер отправляет суто тело ответа, без строки ответа и каких-либо заголовков. Терминатором тела является закрытие подключения, поскольку стандарт требует, чтобы сервер отвечал по http/0.9 протоколу (сам запрос автоматически должен считаться, как http/1.0)
Из нюансов - клиент должен сам детерминировать MIME-тип. Однако не беда, вон, мой сервер тоже не сильно мимами козыряет🌚
По поводу использования на практике - сказать, увы, не смогу. Однако звучит прикольно - поддерживать мы это, конечно же, не будем
Что делать
context.WithValue медленный длиннопоста не будет. Он под капотом рефлексию трогает. Придется в индиге ещё и свои контексты пилить
Кстати, почему key использует женерик
any, а не comparable, раз ему такие и нужны?Сделал для тела запроса структуру, имплементирующую
Правда, из-за некоторых особенностей моей потоковой работы с телом, пришлось чутка закостылить с тем, чтобы сообщать ядру о завершении обработки кусочка тела только при следующем вызове. Но работает стабильно
* Требование сообщать ядру о завершении обработки кусочка тела связана с тем, что этот кусочек тела в виде слайса байт тянется прямиком из tcp сервера, а точнее - буфера для чтения. А значит, если убрать эту синхронизацию, то получаем UB - слайс может быть перезаписан новыми данными
io.Reader. Теперь можно request.Reader(), и получить свой io.Reader. Красиво.Правда, из-за некоторых особенностей моей потоковой работы с телом, пришлось чутка закостылить с тем, чтобы сообщать ядру о завершении обработки кусочка тела только при следующем вызове. Но работает стабильно
* Требование сообщать ядру о завершении обработки кусочка тела связана с тем, что этот кусочек тела в виде слайса байт тянется прямиком из tcp сервера, а точнее - буфера для чтения. А значит, если убрать эту синхронизацию, то получаем UB - слайс может быть перезаписан новыми данными
Итак, до беты остаётся лишь имплементировать динамический роутинг. Есть идея ставить в пути маркер
Почему не регекспы? Ну, они медленные. У меня лично сейчас процесс роутинга укладывается в 80нс, при условии, что у меня получение по ключу из двух хэшмап. Регулярки же укладываются в 700-800нс
{name}, где name - либо пустота, либо имя значения в контексте, которое появится в случае, если путь запроса соответствует шаблону. Поскольку маркер может стоять только между двумя слэшами, нам остаётся лишь пройтись по статичным участкам, и при наступлении маркера - схоронить значение в контекст, где концом значения является слэш (или конец строки)Почему не регекспы? Ну, они медленные. У меня лично сейчас процесс роутинга укладывается в 80нс, при условии, что у меня получение по ключу из двух хэшмап. Регулярки же укладываются в 700-800нс
🔥2
От канала отписался один человек. И он не узнает, насколько быстро мой алгоритм матчит пути по шаблону…
🤡2
В общем, сравнение пути по шаблону я сделал, и вроде шустрое (само по себе - 80-90нс в худшем случае выдаёт). До 245нс жиреет из-за контекстов (без пропатченных - там до 700-800нс вырастает, прям как регулярки). Сейчас буду пилить префиксное дерево, потому что одним только сравнением сыт не будешь
Что делать
В общем, сравнение пути по шаблону я сделал, и вроде шустрое (само по себе - 80-90нс в худшем случае выдаёт). До 245нс жиреет из-за контекстов (без пропатченных - там до 700-800нс вырастает, прям как регулярки). Сейчас буду пилить префиксное дерево, потому…
Кстати, по поводу пропатченных контекстов - мне нужен конкретно
Переделал под дженерики. С 635нс производительность улучшилась до 245нс, количество аллокаций с 6-10 уменьшились до 3-5, т.е. в два раза. Пока что перемога, посмотрим, насколько префиксное дерево будет влиять на итоговый перфоманс
WithValue. О нём я уже писал. Он всё ещё был медленным из-за того, что использовал any (просто any, а не как дженерик), а это интерфейс. А интерфейс под капотом хранит указатель. А указатель утекает на кучу. А утекание на кучу приводит к аллокациямПеределал под дженерики. С 635нс производительность улучшилась до 245нс, количество аллокаций с 6-10 уменьшились до 3-5, т.е. в два раза. Пока что перемога, посмотрим, насколько префиксное дерево будет влиять на итоговый перфоманс
Я не я, если нет оптимизаций. Для роутинга я решил сделать три уровня поведения:
- Пути все статичные. В таком случае, динамический роутинг нам не нужен, мы его отключаем и фоллбекаемся к текущей реализации
- Пути динамические, но least unique string (самый короткий уникальный префикс) не включает в себя динамическую часть. Тогда мы берём из мапы по этому least unique string нужный нам шаблон, и сверяем
- Пути динамические, и least unique string вмещает в себе динамическую часть. В таком случае, берём это самое наше префиксное дерево, о реализации которого я отпишусь позже
- Пути все статичные. В таком случае, динамический роутинг нам не нужен, мы его отключаем и фоллбекаемся к текущей реализации
- Пути динамические, но least unique string (самый короткий уникальный префикс) не включает в себя динамическую часть. Тогда мы берём из мапы по этому least unique string нужный нам шаблон, и сверяем
- Пути динамические, и least unique string вмещает в себе динамическую часть. В таком случае, берём это самое наше префиксное дерево, о реализации которого я отпишусь позже
Что делать
Я не я, если нет оптимизаций. Для роутинга я решил сделать три уровня поведения: - Пути все статичные. В таком случае, динамический роутинг нам не нужен, мы его отключаем и фоллбекаемся к текущей реализации - Пути динамические, но least unique string (самый…
В общем, с least unique string я решил забить, ибо ну его нафиг. А вот по поводу префиксного дерева, я сделал следующую штуку:
Делим путь на сегменты. Сегмент - это кусочек между слэшами. Каждый сегмент может быть как статическим, так и динамическим. Если статический - мы берём из хэшмапы следующий узел, и идём дальше. А вот если динамический - кладём этот самый сегмент в контекст, и точно также идём дальше
получилось не слишком медленно, что-то около 390нс в самом длинном шаблоне, представленным у меня в тестах. Производительность всего роутера с фоллбеком к префиксному дереву я пока не мерял
Делим путь на сегменты. Сегмент - это кусочек между слэшами. Каждый сегмент может быть как статическим, так и динамическим. Если статический - мы берём из хэшмапы следующий узел, и идём дальше. А вот если динамический - кладём этот самый сегмент в контекст, и точно также идём дальше
получилось не слишком медленно, что-то около 390нс в самом длинном шаблоне, представленным у меня в тестах. Производительность всего роутера с фоллбеком к префиксному дереву я пока не мерял
А вот интереснее у меня устроен сам роутинг. Ведь если у нас только статические пути, то зачем, спрашивается, использовать префиксное дерево, если оно медленней обычной хешмапы?
Вот и я так подумал. И сделал такую штуку, что у нас динамически, при старте сервера, выбирается имплементация роутинга. Это - просто функция, которая лежит полем в структуре, и мы её вызываем, чтобы получить обработчик для запроса
Вот и я так подумал. И сделал такую штуку, что у нас динамически, при старте сервера, выбирается имплементация роутинга. Это - просто функция, которая лежит полем в структуре, и мы её вызываем, чтобы получить обработчик для запроса
https://github.com/golang/go/discussions/56010
Здесь, в
Это, кстати, как раз та штука, почему надо быть аккуратным с замыканиями в циклах
Кстати, чинится добавлением в теле цикла
А вот ишью, кстати, советую глянуть. Интересная штука
var all []*Item
for _, item := range items {
all = append(all, &item)
}
Здесь, в
all будет len(item) одинаковых указателей, равных указателю на последний элемент в item, потому что item - переменная цикла - является не per-iteration, а per-loop. То есть, переменная item у нас одна-единственная на все итерации, и при каждой итерации её значение затирается. Это, кстати, как раз та штука, почему надо быть аккуратным с замыканиями в циклах
Кстати, чинится добавлением в теле цикла
item := itemА вот ишью, кстати, советую глянуть. Интересная штука
GitHub
redefining for loop variable semantics · golang go · Discussion #56010
Update 2023-06-06: Go 1.21 is expected to support GOEXPERIMENT=loopvar as a way of trying out these new semantics. See #57969 and https://go.dev/wiki/LoopvarExperiment. We have been looking at what...
👍1