День открытий. Впервые за 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
Я тут заголовки переделал
В первую очередь, все значения заголовков теперь хранятся в одном большом слайсе байт. Это уменьшает нагрузку на гц за счёт того, что теперь меньше указателей и разрозненных участков в куче (не забываем, что слайс держит указатель на низлежащий массив).
Это также нам дало возможность подстроить не просто максимальную длину значения заголовка, а выделить конкретное количество пространства под значения. А значит, возможен более гибкий контроль над количеством используемой памяти
Ну и ещё изменений по мелочи, благодаря которым сервер стал в два раза меньше памяти на запрос тратить
В первую очередь, все значения заголовков теперь хранятся в одном большом слайсе байт. Это уменьшает нагрузку на гц за счёт того, что теперь меньше указателей и разрозненных участков в куче (не забываем, что слайс держит указатель на низлежащий массив).
Это также нам дало возможность подстроить не просто максимальную длину значения заголовка, а выделить конкретное количество пространства под значения. А значит, возможен более гибкий контроль над количеством используемой памяти
Ну и ещё изменений по мелочи, благодаря которым сервер стал в два раза меньше памяти на запрос тратить
Что делать
Я тут заголовки переделал В первую очередь, все значения заголовков теперь хранятся в одном большом слайсе байт. Это уменьшает нагрузку на гц за счёт того, что теперь меньше указателей и разрозненных участков в куче (не забываем, что слайс держит указатель…
Я ошибся. Моё решение не снижает количество указателей, ведь слайс всё равно указывает на память. Но теперь она хотя бы менее фрагментирована, тут большой слайс для всех всё же хорош
Вот значения заголовков у меня лежат в одном-большом пространстве, все вместе. Это решает проблему с уменьшением аллокаций, и убирает надобность в обжект пуле (реализация которого всё равно была бы сложной, либо сам обжект пул был бы тупым). Ну и можно указывать максимальное пространство, доступное для них, прикольно
Но вот с ключами я, почему-то, того же самого сделать не додумался. Я просто шёл по запросу до двоеточия, добавляя каждый символ индивидуально. Потом аллоцировал новую строку под ключ. Зачем? Не знаю
Ну сделал то же самое, что и со значениями - заводим отдельный большой байтовый массив, кладём туда из входного потока ключ, берём на эту область слайс и свапаем к строке через ансейф. И - вуаля! - количество аллокаций снижается в два раза! Да и памяти оно теперь меньше на запрос жрёт
А ещё, индюшка теперь в бете🎉
Но вот с ключами я, почему-то, того же самого сделать не додумался. Я просто шёл по запросу до двоеточия, добавляя каждый символ индивидуально. Потом аллоцировал новую строку под ключ. Зачем? Не знаю
Ну сделал то же самое, что и со значениями - заводим отдельный большой байтовый массив, кладём туда из входного потока ключ, берём на эту область слайс и свапаем к строке через ансейф. И - вуаля! - количество аллокаций снижается в два раза! Да и памяти оно теперь меньше на запрос жрёт
А ещё, индюшка теперь в бете🎉