Что делать
Загадка от Жака Фреско: угадайте, где здесь происходит аллокация на 24 байта. На ответ даётся 30 секунд
Ответ на загадку: аллокацию вызывает передача метода
Сейчас буду пытаться разузнать причину этого. Даже интересно стало, что же оно там такого аллоцирует на 24 байта🤨
client.WriteСейчас буду пытаться разузнать причину этого. Даже интересно стало, что же оно там такого аллоцирует на 24 байта🤨
В общем, я так и не понял, почему так, но штуку с горем пополам пофиксил. Главное - не забыл оставить гневное послание core-мейнтейнерам в комментарии
Поскольку заголовки всё ещё являются узким горлышком, я не прекращаю попыток придумать способы их соптимизировать. На повестке дня два способа:
- Статическая структура с well-known заголовками, чтобы лишний раз их не искать в общем хранилище
- Заранее объявлять списки нужных заголовков
С первым способом особо проблем нет, это имплементируется довольно банальным способом. Больше нас интересует второй пункт. Его идея состоит в том, чтобы если заранее известны необходимые обработчику заголовки, то их можно было сложить в одну структуру, и их запись/извлечение имели константную асимптотику. Иными словами - хэшмапа, но работающая на заранее известном множестве ключей. Более того, такая штука также может использоваться для хранения динамических сегментов путей (во встроенном роутере), что также повышает производительность.
Пока что вся проблема сводится к достаточно умной хэш-функции, которая могла бы выдавать желательно непрерывные (либо с небольшим интервалом) значения для строк. Надеюсь, я смогу что-то такое найти.
Эх, сколько же проблем решает сильный компайл-тайм
- Статическая структура с well-known заголовками, чтобы лишний раз их не искать в общем хранилище
- Заранее объявлять списки нужных заголовков
С первым способом особо проблем нет, это имплементируется довольно банальным способом. Больше нас интересует второй пункт. Его идея состоит в том, чтобы если заранее известны необходимые обработчику заголовки, то их можно было сложить в одну структуру, и их запись/извлечение имели константную асимптотику. Иными словами - хэшмапа, но работающая на заранее известном множестве ключей. Более того, такая штука также может использоваться для хранения динамических сегментов путей (во встроенном роутере), что также повышает производительность.
Пока что вся проблема сводится к достаточно умной хэш-функции, которая могла бы выдавать желательно непрерывные (либо с небольшим интервалом) значения для строк. Надеюсь, я смогу что-то такое найти.
Эх, сколько же проблем решает сильный компайл-тайм
Оказывается, на маке нельзя ничего ручками (кроме чтения) сделать с /usr/bin - даже с судо. Видите ли, политики безопасности (ну да, если у малвари судо, то он без /usr/bin все равно ничего сделать не сможет, правда ведь?). Собственно, задача: куда без переустановки компилятора, добавить исполняемый файл go, чтобы он был доступен по всей системе, а не только в интерактивном шелле zsh?
💩1
Еще про оптимизации.
Иногда, чем проверять символ на вхождение в множество других символов посредством свитча,
То есть, эксплуатируя особенность ascii-кодировки, в которой каждый символ закодирован строго одним байтом, мы можем просто завести
А, и да. Это вполне себе можно считать идеальной хэш-мапой:)
Иногда, чем проверять символ на вхождение в множество других символов посредством свитча,
switch char {
case 'a', 'b', 'c': ...
}
дешевле сделать лукап по массиву. То есть, эксплуатируя особенность ascii-кодировки, в которой каждый символ закодирован строго одним байтом, мы можем просто завести
[256]bool. Константа 256 - максимальное значение uint8 (алиасом на который byte, собственно, и является). В таком случае, заполняя массив следующим образом, мы можем получить значение, лежащее по индексу, равному значению символа. То есть, if table['a'] { ... }
Преимущество перед свитчом это, правда, начинает иметь только начиная с определенного момента. Авторы json-iter, например, решили, что свитч с числами на 16 кейсов уже будет дороже, чем лукап по массиву.А, и да. Это вполне себе можно считать идеальной хэш-мапой:)
GitHub
go/iter.go at master · json-iterator/go
A high-performance 100% compatible drop-in replacement of "encoding/json" - json-iterator/go
Раз уж зашла тема, то стоит еще обсудить мапу.
По О-нотации она довольно эффективна (в среднем сложность константная), отнюдь на практике достаточно дорогая в некоторых ситуациях. Например, у меня в парсере обработка заголовков - это довольно интенсивная как запись, так и чтение, потому что значений у заголовка может быть несколько - приходится держать их всех в слайсе.
И парсер был довольно медленным. Профайлер показывал, что бОльшая часть времени уделяется операциям с хэшмапой. Поэтому я решил попробовать заменить ее просто на
Но почему так? Потому что при интенсивной записи, не очень интенсивном чтении и небольшом количестве ключей (лимит - 50 заголовков, в среднем 10-20), обычный перебор будет быстрее. Но тенденция сохраняется до 20-30 элементов, далее скорость начинает линейно отставать от в среднем константной у хэшмапы (но и с 50 элементами перебор не намного медленнее хэшмапы). Также заметную часть расходов удалось убрать за счет отсутствия операции очищения мапы.
Но в целом, обычный одномерный слайс строк просто-напросто более удачная структура данных по части абсолютной производительности для конкретного кейса¯\_(ツ)_/¯
По О-нотации она довольно эффективна (в среднем сложность константная), отнюдь на практике достаточно дорогая в некоторых ситуациях. Например, у меня в парсере обработка заголовков - это довольно интенсивная как запись, так и чтение, потому что значений у заголовка может быть несколько - приходится держать их всех в слайсе.
И парсер был довольно медленным. Профайлер показывал, что бОльшая часть времени уделяется операциям с хэшмапой. Поэтому я решил попробовать заменить ее просто на
[]string, где четный индекс всегда ключ, а нечетный - всегда значение заголовка. Теперь асимптотика стабильно линейная, что является регрессией. Но в абсолютных числах - в бенчмарках с большим количеством заголовков, он стал в среднем в два раза быстрее (но чем заголовков больше, тем больше и разрыв). Но почему так? Потому что при интенсивной записи, не очень интенсивном чтении и небольшом количестве ключей (лимит - 50 заголовков, в среднем 10-20), обычный перебор будет быстрее. Но тенденция сохраняется до 20-30 элементов, далее скорость начинает линейно отставать от в среднем константной у хэшмапы (но и с 50 элементами перебор не намного медленнее хэшмапы). Также заметную часть расходов удалось убрать за счет отсутствия операции очищения мапы.
Но в целом, обычный одномерный слайс строк просто-напросто более удачная структура данных по части абсолютной производительности для конкретного кейса¯\_(ツ)_/¯
И снова сказ о том, как я парсер свой оптимизировал.
Решил я из интереса написать http-forwarder, для этого мне нужно из запроса достать заголовки Host, Content-Length и Transfer-Encoding (Host чтобы знать, кому перенаправлять; Content-Length и Transfer-Encoding - чтобы знать, что делать с телом запроса). Решил я для этого использовать
Неплохо. Посмотрел на свой парсер в индиге - 1.7гб/с. Уныленько. Переломал все к чертям, позаменял на
Так а в чем же прикол, собственно? Да суть вся в том, что
Решил я из интереса написать http-forwarder, для этого мне нужно из запроса достать заголовки Host, Content-Length и Transfer-Encoding (Host чтобы знать, кому перенаправлять; Content-Length и Transfer-Encoding - чтобы знать, что делать с телом запроса). Решил я для этого использовать
bytes.IndexByte(). Результат (после пары часов пост-оптимизирования, конечно же) - 11.7gb/s throughput. Неплохо. Посмотрел на свой парсер в индиге - 1.7гб/с. Уныленько. Переломал все к чертям, позаменял на
bytes.IndexByte(). Стало 4.5гб/с. Уже лучше, хоть и все еще уныленько - но эту проблему я пока решаю.Так а в чем же прикол, собственно? Да суть вся в том, что
bytes.IndexByte() для х86 архитектур использует SIMD, позволяя, тем самым, обходить строки гооораздо быстрее. Собственно, весь выигрыш и сводится к тому, насколько грамотно получится вкрутить эту штуку себе в код. В парсере для http-forwarder'а, например, большую часть данных просто пропускают мимо ушей, потому что основных (и самых важных заголовков) - очень маленькое подмножество, благодаря чему и получается достичь относительно высокой производительности. В индиге отставание в практически 3 раза, но уже потому, что приходится гораздо больше данных копировать - все же тут парсеру приходится быть немного более general-purpose, и извлекать больше деталей из запроса. А, ну и путь запроса много сжирает, да.Интерпретатор, совместимый с go1.19 и go1.20. Интересный проект, чтобы быстро проверить идею - отнюдь никаких оптимизаций не предусмотрено, и даже никакой виртуальной машины для исполнения нет
GitHub
GitHub - traefik/yaegi: Yaegi is Another Elegant Go Interpreter
Yaegi is Another Elegant Go Interpreter. Contribute to traefik/yaegi development by creating an account on GitHub.
👍1
https://github.com/golang/go/issues/61897
Говорят, стоит ожидать. Штош, было бы замечательно увидеть итераторы, наконец, и в стандартной библиотеке
Говорят, стоит ожидать. Штош, было бы замечательно увидеть итераторы, наконец, и в стандартной библиотеке
GitHub
iter: new package for iterators · Issue #61897 · golang/go
We propose to add a new package iter that defines helpful types for iterating over sequences. We expect these types will be used by other APIs to signal that they return iterable functions. This is...
Начал замечать, что с тех пор, как добавили женерики - начали добавлять и интересные пакеты. Вроде все того же slice, арены (отнюдь не уверен во взаимосвязи), итераторы планируют. Может, пустые интерфейсы - и правда не настолько хорошее решение by design?
👏1