Что делать
119 subscribers
209 photos
3 videos
4 files
133 links
Не смешно
Download Telegram
А в чем прикол бустить каналы премиум-подписками?
Что делать
А в чем прикол бустить каналы премиум-подписками?
Теперь и с каналов ливать. Ну что ж ты будешь делать
😁1🐳1
Переписываю gzip-декомпрессор из стд либы (пришлось, потому что иначе очень неудобно использовать в моем случае). Тут есть такая штука: если стоит флаг extra - следующие два байта после заголовков - длина данных, и непосредственно сами данные. Так вот: возможно, это не такой уж и hot-path, но на это все равно аллоцируют каждый раз новый слайс.

Собственно, а в чем вопрос? Так можно ведь буфер потом переиспользовать (в худшем случае - реаллоцировать под нужный размер, но это никак не будет отличаться от текущего решения). Просто сохранить его в структуре Reader. Ридер ведь переиспользуют! Я и уверен, что много кто и переиспользует, и хуже это явно не сделает. Так почему? Почему так?
Forwarded from Ivan Sokolov
в C++20 можно легально заставить работать такой синтаксис:
5_m + 2 // вызывает ваш произвольный operator+. суффикс может быть любым начинающимся с _
Как я оптимизировал flate на 5-10мб/с

Пришлось копаться с внутренностями стандартного gzip компрессора, и он под капотом flate использует (что логично, ведь gzip - это просто надстройка в виде пары заголовков для flate-потока). Там, в декомпрессоре, было одно интересное поле - step func(*decompressor) . Читай - указатель на функцию. Туда подставлялся метод декомпрессора, который должен быть использован для следующего шага (и делал он это довольно странным образом).

Собственно, почему это странно? Потому что вызов функции по указателю (а не по идентификатору) - это indirect call, и тут уже дело конкретно в железе. Потому что для совершения такого вызова, процессору сначала придется обратиться по адресу в памяти, на который указывает наш func ptr. В С, например, компилятор может соптимизировать это так, чтобы разницы в производительности не было (предварительно положив адрес в регистр).

Да вот только мы не в С :). Я даже попробовать сделать бенчмарк - и правда, обычный вызов справляется за 0.2нс. В то время, как indirect-вариант - за 1.1нс. Стоит сделать помарку: 0.2нс - это всегда подозрительно, и, скорее всего, так и есть, потому что в измеряемой мною функции сразу производился возврат, и результат никуда не присваивался - компилятор мог просто-напросто вырезать такой вызов. Поэтому я повторил с флагом -N (дабы отключить оптимизации), и результат стал 1.1нс и 1.9нс соответственно. То есть, разница все еще присутствует.

Так какой из этого можно сделать вывод? Вызов по func ptr дороже. В пределах пикосекунд, но есть. И именно это и сыграло роль (хоть и минимальную, особенно смотря на коэффициент).

Вот ссылка на мой PR, если интересно. Можете посмотреть там сравнения до и после патча. А еще, там есть интересный момент, связанный с занулением статического массива:)
🔥1
func sum(a, b int) (c int) {
с = a + b
defer func() {
с++
}()

return
}


Что вернет эта функция в случае sum(5, 5)? Для многих будет очевидно, что 11 - записали в c сумму, и в дефере инкрементировали. Окей, а если так?

func sum(a, b int) (c int) {
result := a + b
defer func() {
result++
}()

return result
}


казалось бы, ситуация практически 1 в 1. А нет: здесь дефер уже никак не влияет на возвращенный результат сложения. Почему так?

Давайте рассмотрим еще один, показательный случай:

func sum(a, b int) (c int) {
result := a + b
defer func() {
с++
}()

return result
}


Что мы имеем? Складываем а и б в переменную result, после чего возвращаем ее. А в дефере мы инкрементируем, казалось бы, ничего общего не имеющую с результатом возврата переменную с. Но что мы видим?

Ba-dum-tsss. Имеем снова sum(5, 5) == 11! Вот это уже интересно. А теперь давайте разберемся, что же все-таки это такое, именованный возврат?

Начать стоит с того, что у нас есть стэк. При вызове функции, мы аллоцируем новый стэкфрейм функции. При возврате, мы пишем значение в стэкфрейм caller'a и делаем jmp к инструкции, на которой тот остановился. Соответственно, когда мы возвращаем во втором примере переменную result, она копируется туда, где потом будет использоваться на месте вызова. И как раз туда, куда она копируется, и указывает с!

Поэтому, в последнем примере, когда мы инкрементируем с - он уже равен result, хоть мы явно этого и не делали. Получается, мы инкрементируем значение прямо на стэкфрейме caller'a. Это, к слову, также дает нам ответ на вопрос, почему при именованном возврате из функции можно сделать просто пустой return.

А теперь самое (не)интересное: это как-то влияет на перфоманс?

Я не знаю.
Я наконец дополировал индигу до состояния, когда готов показать.

Фич на данный момент маловато, равно как и миддлварей. Покрытие тестами не полное, но большая часть протестирована. Однако мне нужен фидбек, поэтому если имеете предложения по поводу функционала - буду рад. Если найдете багу - буду вдвойне рад. Если поможете с написанием функционала - буду рад в квадрате.

Линк - github.com/indigo-web/indigo
Внизу ридми лежит актуальная дока. Постарался в ней описать все основные моменты. Исправления и предложения по ней также принимаю
🔥1
Хотел написать пост, как оптимизировал индигу, и почему она вообще такая быстрая (но бенчмарков не будет). Но это скучно - все сводится к "думай, goto, simd".


Зато, как всем известно, что популярнее всего в интернетах? Бугурты!

Я хочу накинуть говна на го. А мне есть что сказать за те два года, что я пишу на этом языке. Каждому из пунктов я уделю отдельный пост, и я надеюсь, что вы будете не согласны (в противном случае, будет грустно)
0. Женерики

А нулевой потому, что это извечная проблема. Ну, была, их все-таки добавили. Спустя 11 лет после релиза, если что.

Ладно, временили 11 лет (можно сказать, я с 4 лет ждал их появления). Имеем по итогу ПОЛОВИНУ стд либы в пустых интерфейсах. Почему это хуево? Да потому, что мы таким образом избегаем от типосистемы. Она настолько примитивная, что даже стд либу с ней нормально сделать не могли, вы только вдумайтесь - а вам на этом еще круды пилить. Хорошо, ладно с той стд либой, которую мы все так любим (обмазанная any синк мапа, контексты, сортировки). На это посмотрели крудошлепы, и подумали: заебца! И знаете что? Мы теперь имеем тонну легаси с активным эксплуатированием any.

Ладно, я немного лукавлю. В стд либе одни кор мейнтейнеры берут any, потому что другим фИлОсОфИя Не ПоЗвОлЯеТ, видите ли, кривая вхождения станет выше (настолько низкого мнения они о круд-макаках). Но нахуя они крудошлепам? Я, конечно, занимаюсь другим профилем, дроча на перфоманс в ядре вебфреймворка, но практически всегда выбор обусловлен "быстрее нашлепать". Практически всегда есть способ реализовать задачу, не прибегая к стиранию типов. Но пРиДеТсЯ дУмАтЬ, бОлЬшЕ вРеМеНи ПоТрАтИтЬ. Извечная болезнь гофера. В итоге имеем сужение типа, посредством явного его приведения либо к более узкому интерфейсу, либо - что еще хуже - к конкретному не-интерфейсному типу. Что мы получаем? Чуть проебались - паника. Заслужено? Полностью. Можно было избежать? Абсолютно.

Скажите мне, пожалуйста, зачем? Зачем было столько ждать? Каким хуем команда инженеров из гугла, при том с одним из отцов С во главе, пришла к выводу, что полиморфизм нам нахуй не нужен? Да, есть видео, на котором Роб Пайк прямо заявляет, что язык был создан для гугломакак. Но неужто расчет был на настолько макак, что они даже программировать не умеют?
👎3
1. Менталитет

Да, нулевой был просто подводкой, потому что рыба гниет с головы. Вот с нее и начнем.

Некомпетентность, дилетантство, высокомерие. Так я бы описал го-го. Эфемерную читаемость кода ставят во главу, но делают это радикально. Радикализм всегда чересчур жертвует чем-то. А должен быть баланс. Я не буду разглагольствовать, но просто покажу архитипичный пример гофера:
https://t.iss.one/gogolang/908812 (заметка: к самому топикстартеру у меня личной неприязни нет, да и он нередко помогает в чате по делу; интересует непосредственно рассматриваемая ситуация)

Кратко - первый элемент слайса переместить в конец. В начале условие пограничного случая - если длина слайса равна нулю, то вернуть, как есть. Ниже замечание - мол, лучше сделать <2, дабы в условие пограничного случая включить и слайсы с len=1, ведь в их случае так же ничего не изменится (но мы выполним ненужную операцию). После этих слов, началась вонь вселенского масштаба - "арря нечитаемее", "ридабилити страдает", "на кодревью никогда бы условие <2 не пропустил". Спрашивается - кто такого человека вообще к кодревью тогда подпустит? А вот остальные гоферы. Потому что такая позиция встречается сообществом если не позитивно, то, как минимум, нейтрально.

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

Хорошо, давайте еще примеров приведу. Практически все интересные штуки, которые можно написать, и которые не будут слишком нишевыми - уже написаны, будем откровенны. Уже есть зоопарк жсон парсеров, два мейнстрим хттп ядра и пара десятков роутеров к ним на любой вкус и цвет. Никого это не напрягает. Но попробуй свое написать - сразу заклеймят. При том это будет не просто "чем твой велосипед лучше", а сразу "твой велосипед не нужен", "возьми готовое". Даже в исходном вопросе даже разбираться не станут - гоферу важен сам факт, что ты не берешь что-то готовое. Пусть даже в учебных целях - ибо, как известно, нет средства обучения лучше, чем велосипедостроение - это не сделает никакой разницы.

Из предыдущего вытекает следующее: аудитория го - в основном перекатыши с других языков. Перекатыш с другого языка успел накрудошлепить за деньги. Он считает себя пупом мира, ведь он невъебенный специалист по укладыванию жсонов в бд и обратно. Так, подождите, на чем строится его компетентность? А, точно, ни на чем. Действительно инженеров, которые разбираются в том, что они делают, но главное - хотят узнавать что-то новое, я готов сосчитать на пальцах. Одной, блять, руки. Остальные - не считают зазорным не знать даже то, как работает программа как таковая, как переменные на стэке хранятся, как очищаются, как вообще устроен вызов функции. Нет, не знать - не есть грех, грех есть - не хотеть знать. Отнюдь не мешает мнить себя трамвайным царем всея голанга, и гнобить тех, кто хочет разобраться глубже. Да, "не надо тебе настолько глубоко это знать" - тоже случалось. У меня это вызывает самый натуральный диссонанс.

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

* - github.com/mowshon/iterium, пример использования с брутфорсом хэша отрабатывает за 4.5 минуты. Без итериума - 30 секунд
👎5👍1
2. Скорость компиляции

Без преувеличений, один из столбов маркетинга. Его выдают за неебический плюс. Так, будто другие языки так не умеют. А правда не умеют?

Ну, да, может, и не умеют. Если включить оптимизации. Стоп - включить? Так их отключить можно было что ли?! Оказывается, можно было. И компиляция окажется не особо медленнее, чем на го! А то и быстрее (напоминаю: fmt компилируется относительно очень долго). Но есть одно мааааленькое отличие: для релиза мы можем включить оптимизации на полную. Да, некоторые для этого аж целые фермы используют. Но и получают код, который будет хорошо оптимизирован. А у нас есть что-то похожее на го? Нет? Мы каждый раз для тестов компилим так же, как и на релиз? Ааа, а я не знал

А нахуя? Учитывая, что у gc (go compiler) есть флаг для отключения оптимизаций (-gcflags="-N"), то делаем соответственно вывод, что вообще любой билд без дополнительных флагов - релизный. Окей, получается, мы и в дев-среде компилим так же, как и для релиза. Зачем. В чем смысл такого решения? В расте по-умолчанию мы компилим без агрессивных оптимизаций, но их можно включить, если указать билд, как релизный. Почему в го так не сделано? Им не выделили бюджет на оптимизаторы? Ах да, точно, они изначально были заложены кастратом.

Я не знаю, что еще на эту тему писать. Это вопиющий долбоебизм, которому я даже косвенного оправдания найти не могу. Пожалуйста, объясните в комментах, в чем сакральный смысл такого решения. Зачем было намеренно отказываться от оптимизаций кода?
👍1
3. Ах да, оптимизации

Ладно, у нас есть ограниченный набор оных. Спасибо, что вообще хоть что-то оптимизируют. ДА ХУЕВО ОПТИМИЗИРУЮТ, БЛЯТЬ!

Нет loop unrolling. Это одна из самых примитивных оптимизаций. Что мы видим? Люди ручками циклы раскручивают. И кому вы от этого, собственно, сделали лучше, скажите пожалуйста?

Хорошо, хотя бы инлайнят. А знаете, как инлайнят? У меня вот был наглядный пример из самого сердца хттп ядра индиги. Оно заинлайнило вообще все конструкторы, геттеры и сеттеры. А знаете, что оно НЕ заинлайнило? Единственную, сука, горячую функцию. Единственная функция, которая в цикле вызывается - именно она и не заинлайнилась по итогу. Смешно? Оборжаться можно.

А знаете, почему так? Потому что в инлайнер заложили очень интересную штуку. Они считают "вес" функции, исходя из нихуя не очевидных эвристик, которые еще и из минорной версии в версию меняются. Все настолько хуево, что только относительно свежие версии научились инлайнить свитч-кейс. Свитч кейс, блять. Нет, я понимаю, что некоторые функции с деферами довольно нелегко было бы заинлайнить, и там пришлось бы изъебаться ради этого. Но у меня меньшинство таких функций. Я хочу инлайнить то, что я скажу, потому что сам компилятор слишком тупорылый, у него "скорость компиляции". А про лимит глубины в анализаторе вы слышали, кстати? Если лимит достигнут, все не-проанализированные переменные просто на хип побегут. Смешно? Чего не смеетесь? Это голанг!

Ладно, может, благодаря таким вещам код более очевидным станет? Да вот...
4. Очевидность

...да вот нихуя подобного! Другим столбом маркетинга представляют мнимую простоту. Другой вопрос, что повальное нищеебство и полное отсутствие фич назвали простотой, но пусть так оно и будет. Но вот давайте подумаем, если код простой, то он должен быть и предсказуемым, верно? Ну, то есть, вот мы написали код простыми конструкциями, значит, понять его просто? А значит, нам должно быть очевидно, как он будет исполняться?

Нет. А с чего вы вообще решили, что очевидным образом будет исполняться простой код, бинарник с которым по итогу весит под 2 мегабайта? 2 мегабайта, блять. Это 2,000,000 байт. Весь этот пост занимает, если что, всего 6181 байт.

Хорошо, под 2 мегабайта весит среднестатистический хелловорлд (~1.8мб, если быть точным; стрип может убрать до 100 килобайт от силы). Может, это все из-за fmt? Да нет, с println мы имеем 1.1-1.2мб. Тобишь, больше мегабайта чистого рантайма. Что туда всунули? Я не знаю, и даже знать не хочу. Наверное, шедулер для единственной горутины и гц для отсутвующего мусора очень важны.

Но это все относится к выхлопу. Какая разница, что функция может неожиданным образом прерваться там, когда мы это не ожидаем, а тогда, когда ожидаем, может не прерваться (отсылка к го 1.13, до которого включительно один пустой бесконечный цикл мог целый поток повесить, потому что горутина никогда не переключала контекст). Зато сам язык-то больше никаких приколов нам не принесет, верно? И опять хуй нам всем в рот. Большой, жирнючий хуй.
1) Именованный возврат - раз.
2) Указатели - ну, они всегда такими были. Например, если структура лежит по указателю, то в полях можно указатели не держать, даже держа их по значению - они будут мутироваться.
3) io.Reader с неочевидной семантикой, которая гласит: данные могут быть возвращены вместе с ошибкой. Спойлер - много кода этого не учитывает:)
4) Мапа - неявный указательный тип.
5) Слайс - не указательный тип, а обычная структура, но которую можно сравнивать с nil.
6) Мапу нельзя нормально очистить. Ну, то есть, чтобы стриггерить вызов mapclear(), ты должен написать специальный код, который словит оптимизатор и заменит на нужный вызов. Оптимизатор, кстати, буквально берет и в лоб проверяет, что мы все правильно делаем.
7) Неочевидные манипуляции с json.Unmarshal(), в который мы суем сам-догадайся-что через пустой интерфейс.
8) Стандартный json.Decoder не умеет в потоковый парсинг, хоть и принимает io.Reader.
9) Числовые константы 666 и 0666 нихуя не одинаковые. По логике, лидирующий 0 должен быть опущен, но го воспринимает это, как обозначение восьмиричного числа. Это прямое наследие С, кстати. Даже в питоне такое запрещено, даже в питоне используют нормальный префикс 0о.
10) Переменные цикла. Признавайтесь, кто запускал горутины/деферы в цикле, а потом получал n деферов с аргументом, равным последнему значению в итерируемой коллекции?

К чему это я? Го - такой же язык, как и все остальные, со своими странностями, коих не намного меньше, чем в среднем по палате. То есть, он не настолько уж и очевидный. Но простой-то? Да, это качество у него остается заслуженно, я за 3 дня прошел го тур и написал свой маленький стэковый интерпретатор. Но вот только есть нюанс: сама по себе простота вот воообще нихуя не дает. Был бы простой код еще и очевидным? Другое дело. Но он простой и неочевидный. В нем просто нихуя нет. Это нищеебский язык. Аскетизм возведен в максимум. Три с половиной фичи выставляют за преимущество, а хомячки радостно шуршат защекоинами во рту.
Короче мой хейт никто не прочитал и никто не порвался, поэтому я обиделся и буду писать стенд для бенчмарков
Как я сравнение строк переизобретал

Мне в парсере нужно понимать, когда мы спарсили какой-нибудь служебный заголовок: например, чтобы сразу указать, что запрос будет иметь chunked transfer encoding. Но из-за того, что в http ключи заголовков регистро-независимые, мне пришлось изобрести свой EqualFold (стандартный был сильно медленнее, потому что поддерживает utf8). Долгое время это было ОК, пока я не дошел до экстремальных оптимизаций. И тут начинается самое интересное.

Я сначала посмотрел, как это сделано в nginx (спойлер: у них далеко не самый эффективный парсер ). До заголовков, правда, дойти не успел: меня в целом и методы запросов устроили. Как это устроено: мы берем 1-2-4 символов, и приводим к uint8-16-32, после чего сравниваем уже полученные числа.

У меня это нужно, потому что по каждому символу мне нужно применить побитовое ИЛИ по 0x20 (старый добрый трюк для ловеркейса аски). Таким образом, я не просто заинлайнил EqualFold, но еще и развернул цикл - сплошные победы. Соответственно, теперь имеем код следующего вида:
(у меня не хватило символов, чтобы пикрилом к предыдущему посту прикрепить)
1