Внутри рантайма KPHP использовать что-то, что использует стандартный аллокатор ("системный"), может быть чревато утечками памяти.
Например, исполнение кода может быть прервано сигналом при таймауте. В этом случае управление перейдёт обработчику сигнала, который потом через getcontext переключится в сетевой контекст и... исполнение прерванного кода никогда не продолжится. Деструкторы всяких std::regex вызваны не будут.
Для того, чтобы использовать динамическую аллокацию памяти подобного рода нужно или критическую секцию выставить, чтобы сигнал в этот момент не прилетел, либо подменять аллокатор на скриптовый (примеры кода можно найти в самом рантайме). Память скриптового аллокатора автоматически чистится при завершении запроса.
Оптимизации на низком уровне часто позволяют создавать эффективные фундаментальные блоки. Например, какие-нибудь библиотеки, производительность которых важна для работы систем. Если сам язык не позволяет такую написать на нём же, то придётся в случае PHP писать расширения. Для таких библиотек эффективные низкоуровневые оптимизации могут давать прирост выше, чем 10-15%.
KPHP интересен тем, что он позволяет многие такие фундаментальные вещи писать на самом же PHP и они будут приемлемы по производительности. Задача компилятора тут дать нужные оптимизации и распознавать идиомы.
Гнев - довольно сильное чувство. Забавно, что техническая статья может так триггерить.
Ваши нападки на меня и мою команду гнева у меня не вызывают, но вот демотивируют и вызывает некоторое удивление - да.
P.P.S. А потом участники таких проектов приходят на собеседование и не могут банально рассказать про то, как что такое BSS, TEXT, elf и чем отличается uint32_t* от void*. Зато они могут положить на лопатки 64 ядра с 5 ТБ ОЗУ на сборку проекта "рабасной" игры из ВК.
Как-то не очень похоже на "всем добра", какие-то осуждения и сомнения в квалификации. Для начала, я не считаю незнание чего-либо пороком, не знать что-то - это нормально. Я далеко не эксперт, но свой вклад сделал в более чем один компилятор (и язык программирования), сделал много статей и докладов, чтобы как-то делиться знаниями, веду более одного популярного open source проекта.
Ваши домыслы о людях, которых вы не знаете, весьма сомнительны на этом фоне. А ещё это не очень приятно и не вполне вежливо. Технологии и код подвергать критике - это ОК, за этим мы здесь и собрались. Но вот о людях так высказываться в этом контексте мне кажется лишним.
У всех бывает синдром самозванца и в целом быть уверенным в своих достижениях - это не так просто. Когда вы так обесцениваете всё, то лишь увеличиваете количество негатива. Вы же не думаете, что помогаете ими кому-то?
Меня уже больше, чем должна, напрягает подобная позиция некоторых хабравчан ("всё говно, вы дурачки, а вот у меня софт хороший"). Ваше мнение услышано, но если вы практически лично людей оскорбляете, то будьте готовы получить ответ.
Есть ещё одна, менее очевидная причина. Я общался с автором FFI в PHP, мы обсуждали проблемы, дизайн, производительность. Желание такое: лучше понять применимость этой фичи, увеличить её распространение, а потом улучшить производительность за счёт JIT и некоторых других трюков.
Если мы не будем говорить о FFI и делать на нём библиотеки, он всегда будет таким, какой есть.
В компилируемом KPHP полезно иметь возможность использовать динамические плагины.
Скриптовый язык - термин не точный, а скорее жаргон. Язык редко бывает интерпретируемым или компилируемым, это свойство реализации. PHP чаще всего используется с интерпретатором, а KPHP имеет только компилируемую реализацию. На KPHP можно писать десктопные приложения, игры и прочие серьёзные штучки (на PHP тоже можно, но коробочный дистрибутив в виде одного почти полностью статически слинкованного бинарника распространять там гораздо сложнее).
FFI Lua работает и там, и там. Если вы работали с компилируемыми языками, то понимаете, что там не так просто обеспечить расширяемость. Это или dlopen на отдельно собранные либы, либо встраивание какого-то интерпретатора. Вот FFI в PHP/KPHP - это как раз dlopen. И через него мы встраиваем Lua.
Так можно собрать KPHP приложение в бинарь, отдать заказчику как чёрную коробку, а расширяемость обеспечить через Lua скрипты. Так заказчику не нужны исходники приложения. Всё, что нужно - положить в правильное место скрипты.
Вообще примеров, где нативные приложения используют встроенные интерпретаторы много. Это почти все игровые движки, сервера приложений типа Nginx, софт для работы с изображениями и полно чего ещё.
Зачем же тогда нам Lua в PHP? Чаще всего, приложения на KPHP приятно тестировать на PHP. А ещё это оставляет нам простор для того, чтобы, в случае чего, было проще переходить туда-сюда, ведь наш код всегда валиден и там, и там.
Но это всё если отбросить образовательный момент. По FFI мало материалов, особенно таких нетривиальных. Неужели плохо, что их становится больше? Здесь и примеры как самому сделать, и готовая библиотека для подглядывания. :)
Вообще статья и так вышла довольно крупной и сложной, поэтому мне не хотелось дополнительно добавлять много букв об этой детали. 1-го предложения достаточно для тех, кто когда-нибудь пробовал делать нативные приложения расширяемыми. А если этого опыта нет, то это уже почти тема для отдельной статьи, ведь там больше одного подхода.
Сначала заранее прошу прощения за не очень доброжелательный тон моего сообщения. Буду честен, ваш комментарий мне настроения не поднял. Надеюсь, вы понимаете, что вы просто ткнули в меня палкой.
Мне не очень хотелось отвечать, потому что статья действительно старая это раз. А два - вы так говорите, как будто бы я перед тем, как что-то заявлять, не пробовал это. Я же жил какое-то время на emacs и кодил используя этот проект. Мне было интересно и удобно. Кому-то тоже понравилась идея. Разве этого не достаточно, чтобы ответить на ваше "зачем?"
autocomplete для Go тоже не сможет работать
Почему? Он вполне себе работал. Там был способ описать что-то типа стабов, чтобы го видел эти функции как внешние, а компилятор goism для них создавал нужные обёртки с учётом типизации.
статическая типизация Go (кстати проект Elsa - хороший статический анализатор для Elisp) работать не будет
Опять не понимаю, почему такой вывод? Возможно, я просто чего-то не понял.
У нас код транслироваться не будет, если там есть ошибки. А то, что в рантайме там уже не те представления - это деталь реализации. У вас на уровне машинного кода тоже нет типизации, но высокоуровневые языки как-то работают. Статическая типизация на то и статическая, что она на этапе компиляции известна и там мы с ней работает. Как рантайм это потом исполняет - отдельный вопрос.
Статические анализаторы лиспа, конечно, работать не будут. Но зачем они нужны, если для Go у нас больше анализаторов и есть коммерческие в том числе?
Серьезно, документация Go, которая вам нравится, не будет иметь смысла для Emacs
Документация того, что написано на elisp доступна напрямую не будет, но я выше писал про стабы. Стабы причём я программно генерил, через сам elisp. Ведь документация и сигнатуры доступны через сам emacs. И вот уже у нас из Go есть go to definition с документацией.
На всю stdlib Go тоже документация работает. Вопрос только в правильной компиляции stdlib.
На все пакеты для emacs, написанные на Go, тоже документация работала бы.
Могу добавить, что если человек неплохо владеет Go, то писать на нём игры относительно приятно. Да, ощущается недостаток библиотек и фреймворков, но что-то для себя делать не так сложно. Если я как-то смогу, то постараюсь двигать геймдев на Go хотя бы на локальном уровне.
Кроме привычного языка для гофера, полезны в геймдеве эти особенности тулчейна (не утверждаю, что этого нет в других тулчейнах, но всё же):
Очень удобный go:embed. Легко все ресурсы записать в бинарник одной директивой.
Профилирование и бенчмарки на высоте. И это встроенно в стандартную поставку.
Тестирование тоже из коробки. TDD для геймдева ещё никогда не было настолько просто.
В целом язык приятен: есть контроль над памятью, но не настолько, чтобы это было утомительно. Мы относительно надёжно можем выделять на стеке, а то, что на куче, удалит GC
Производительность достаточная, чтобы написать движок на самом Go, и игровую логику тоже писать на Go. Есть ещё интерпретаторы Go, если нужно динамическое скриптование прикрутить. AAA игры может и не получится написать таким образом, но в инде жить можно.
Так как язык не имеет традиционного ООП с наследованием, меньше соблазна делать иерархии типа Label < Control < CanvasItem < Node < Object; Я не говорю, что это прямо ужасно, но мне кажется, что глубина в 6-7 - это не так красиво.
С генериками стало проще писать нужные абстракции типобезопасно. Сделать систему сигналов и слотов - это не особо сложно.
Хороший stdlib. Он композируем и в нём есть всякие JSON и http сервера. В C# относительно недавно завезли json в stdlib, а раньше нужно было ставить отдельный пакет. И этот пакет у меня время старта приложения замедлял на секунды 3-4, потому что, видимо, там какая-то нетривиальная инициализация (?)
Я уверен, есть и другие пункты. Go как минимум не так ужасен для геймдева, как может показаться. :)
By convention, one-method interfaces are named by the method name plus an -er suffix or similar modification to construct an agent noun: Reader, Writer, Formatter, CloseNotifier etc.
Иногда этой конвенции можно избежать, но для интерфейсов из 1-го метода её чаще всего придерживаются. Для интерфейсов из более чем одного метода можно не делать комбинацию типа Read+Write = ReadWriter, а придумать концептуальное название. В нашем случае, всё равно будет полезно иметь методы типа IsDisposed(), так что можно было назвать интерфейс GraphicsObject или как-то так. Но именование - сложная штука и хорошее название должно быть консистентно с остальными именами в движке или библиотеки. Чтобы не делать слишком много предположений, я просто взял общепринятую концепцию и не добавлял методы, которые не пригодились конкретно в этой статье.
Сейчас используется собственный форк gogrep, где теперь, например, поддерживается несколько новых форм частичного синтаксиса. Принципиально это не меняет того, что матчинг осуществляется по синтаксису, но немного развязывает руки для дальнейших расширений и интеграций. Ту проблему взаимодействия фильтров и вариадичных шаблонов я не решал потому что не нашёл эффективного и красивого решения.
В утилите perfguard используется гибридный подход. Почти все диагностики сделаны через правила ruleguard. Более глубокий анализ делается расширением, через обычный go/ast и прочие пакеты. go-critic, к слову, работает так же: простейшее через правила, остальное ручками. ruleguard не задумывался как единственный инструмент для статического анализа. Это легко встраиваемая библиотека для описания шаблонами того, что нудно писать руками.
Могу ещё добавить, что staticcheck внутри себя тоже имеет движок правил, похожий на ruleguard. Правда там вместо gogrep паттернов своеобразный S-expr синтаксис (как в лиспах). Этот движок, насколько мне известно, внутренний и никогда не продвигался как механизм расширения staticcheck. ruleguard - это более публичный продукт в этой же области. Ни в staticcheck, ни в go-critic, эти движки не могут предоставить 100% решения задач. Но они сильно упрощают добавление типовых паттернов и/или прототипирование диагностик.
Из совсем революционного: скоро в Where будет гораздо меньше ограничений благодаря смешиванию статически реализованных правил и того, что будет исполняться через интерпретатор quasigo. Правда там же будет релиз dsl v1, который внесёт обратно-несовместимые изменения, но даст некоторую гарантию стабильности в будущем (TODO по DSL уже долго накапливались).
Хочу ещё дополнить тем, что в пакете reflect некоторые операции относительно быстрые и совместимые с высокой производительностью, если понимать некоторые нюансы.
Например, если значение уже выделено в куче, то какой-нибудь reflect.ValueOf(x).IsNil() может быть эффективнее, чем крупный type switch по типам и сравнение с nil, потому что reflect сделает почти чёрную магию с распаковкой интерфейса и проверкой его data поля (напрямую через unsafe будет ещё быстрее, но unsafe не всегда оправдан).
А вот вещи типа MethodByName даже линковщик могут смутить и сделать его более консервативным при удалении мёртвого кода (ведь какой-то метод могут внезапно вызвать по имени через строку, чьё значение статически неизвестно).
Хотя если хочется контроля и стабильности, наверное лучше не использовать reflect вовсе (когнитивно так проще, чем пытаться быть экспертом и следить за изменениями в разных версиях Go). И те же fmt пакеты туда же. Справедливости ради, на практике мало кому нужно настолько контролировать производительность, но всякие zerolog и zap не просто так появились. :)
Рандомные советы, которые тоже можно было бы добавить в статью:
Кеш golangci-lint иногда приходится сбрасывать при использовании правил и их активном изменении. Есть даже issue об этом.
Как включать и отключать отдельные правила через golangci-lint конфиг, используя параметры enable/disable линтера gocritic/ruleguard.
Возможно, вы как-то решили проблему того, что в golangci-lint в yaml файла может быть неудобно прописывать путь к правилам? Есть, опять же, issue про это. Вроде как решилось тем, что теперь можно в конфиге для этого использовать интерполяцию. https://github.com/golangci/golangci-lint/pull/2308
Можно поделиться опытом тестирования диагностик. В go/analysis и ruleguard есть почти всё, чтобы это было удобным, но есть нюансы и продвинутые фичи. Например, можно ещё и quickfix'ы тестировать. :)
Описание всяких проблем в использований и путей для их обхода тоже было бы ценно. В том числе для меня, чтобы понять, какие вещи нужно фиксить в первую очередь внутри самого ruleguard.
В докладе по ссылке было побольше информации. Кажется, в статью можно было бы как минимум добавить недостающее и стало бы лучше.
Если планируется дорабатывать статью, то что-то из этого добавить всё ещё не поздно. Если же не планируется дорабатывать, то у читателей будут пара затравок на что посмотреть дальше.
Я, конечно, понимаю, что вы просто хотели пошутить, но может кому-то реально будет понятнее с пояснениями.
А что за гачи инструмент которым вы все мерили?
В статье упоминается pprof несколько раз. Он встроен в тулинг Go. Но им я не измерял, а просматривал CPU профиль.
cum cum%
cumulative - суммарное время (англ. - совокупное, накопительное), с учётом вызываемых внутри функции других функций. Flat при этом показывает "собственное" время функции.
Каждый семпл в profile.proto обычно имеет стектрейс. Первый элемент из него -- текущая функция, которая исполняется сейчас. Если мы считаем flat, то в статистику добавляется только это значение. Остальные элементы -- функции выше по стеку. Если мы считаем sum, то эти значения тоже учитываются. Подробнее можно посмотреть в самих исходниках pprof.
Возвращаясь к гачи-теме, как вам такой сниппет для pprof (100% рабочий):
Небольшое предостережение для читателей.
Внутри рантайма KPHP использовать что-то, что использует стандартный аллокатор ("системный"), может быть чревато утечками памяти.
Например, исполнение кода может быть прервано сигналом при таймауте. В этом случае управление перейдёт обработчику сигнала, который потом через getcontext переключится в сетевой контекст и... исполнение прерванного кода никогда не продолжится. Деструкторы всяких
std::regex
вызваны не будут.Для того, чтобы использовать динамическую аллокацию памяти подобного рода нужно или критическую секцию выставить, чтобы сигнал в этот момент не прилетел, либо подменять аллокатор на скриптовый (примеры кода можно найти в самом рантайме). Память скриптового аллокатора автоматически чистится при завершении запроса.
Оптимизации на низком уровне часто позволяют создавать эффективные фундаментальные блоки. Например, какие-нибудь библиотеки, производительность которых важна для работы систем. Если сам язык не позволяет такую написать на нём же, то придётся в случае PHP писать расширения. Для таких библиотек эффективные низкоуровневые оптимизации могут давать прирост выше, чем 10-15%.
KPHP интересен тем, что он позволяет многие такие фундаментальные вещи писать на самом же PHP и они будут приемлемы по производительности. Задача компилятора тут дать нужные оптимизации и распознавать идиомы.
Я человек простой: вижу статью про KPHP - скидываю ссылку на чатик сообщества.
https://t.me/kphp_chat
Автора статьи там уже вижу. ;)
Прочитал статью, но не нашёл ни ссылки на проект, ни названия. :)
Можете ссылкой поделиться?
P.S. - рад, что FFI вам оказался полезен.
Гнев - довольно сильное чувство. Забавно, что техническая статья может так триггерить.
Ваши нападки на меня и мою команду гнева у меня не вызывают, но вот демотивируют и вызывает некоторое удивление - да.
Как-то не очень похоже на "всем добра", какие-то осуждения и сомнения в квалификации. Для начала, я не считаю незнание чего-либо пороком, не знать что-то - это нормально. Я далеко не эксперт, но свой вклад сделал в более чем один компилятор (и язык программирования), сделал много статей и докладов, чтобы как-то делиться знаниями, веду более одного популярного open source проекта.
Ваши домыслы о людях, которых вы не знаете, весьма сомнительны на этом фоне. А ещё это не очень приятно и не вполне вежливо. Технологии и код подвергать критике - это ОК, за этим мы здесь и собрались. Но вот о людях так высказываться в этом контексте мне кажется лишним.
У всех бывает синдром самозванца и в целом быть уверенным в своих достижениях - это не так просто. Когда вы так обесцениваете всё, то лишь увеличиваете количество негатива. Вы же не думаете, что помогаете ими кому-то?
Меня уже больше, чем должна, напрягает подобная позиция некоторых хабравчан ("всё говно, вы дурачки, а вот у меня софт хороший"). Ваше мнение услышано, но если вы практически лично людей оскорбляете, то будьте готовы получить ответ.
Спасибо за внимание.
По традиции, продублирую ссылочку на сообщество KPHP:
https://t.me/kphp_chat
https://t.me/kphp_chat
Продублирую и тут ссылочку. В этом чатике собирается сообщество KPHP.
Разработчики компилятора там тоже есть.
Есть ещё одна, менее очевидная причина.
Я общался с автором FFI в PHP, мы обсуждали проблемы, дизайн, производительность.
Желание такое: лучше понять применимость этой фичи, увеличить её распространение, а потом улучшить производительность за счёт JIT и некоторых других трюков.
Если мы не будем говорить о FFI и делать на нём библиотеки, он всегда будет таким, какой есть.
В разделе "мотивация" старался описать. Вот:
Скриптовый язык - термин не точный, а скорее жаргон. Язык редко бывает интерпретируемым или компилируемым, это свойство реализации. PHP чаще всего используется с интерпретатором, а KPHP имеет только компилируемую реализацию. На KPHP можно писать десктопные приложения, игры и прочие серьёзные штучки (на PHP тоже можно, но коробочный дистрибутив в виде одного почти полностью статически слинкованного бинарника распространять там гораздо сложнее).
FFI Lua работает и там, и там. Если вы работали с компилируемыми языками, то понимаете, что там не так просто обеспечить расширяемость. Это или dlopen на отдельно собранные либы, либо встраивание какого-то интерпретатора. Вот FFI в PHP/KPHP - это как раз dlopen. И через него мы встраиваем Lua.
Так можно собрать KPHP приложение в бинарь, отдать заказчику как чёрную коробку, а расширяемость обеспечить через Lua скрипты. Так заказчику не нужны исходники приложения. Всё, что нужно - положить в правильное место скрипты.
Вообще примеров, где нативные приложения используют встроенные интерпретаторы много. Это почти все игровые движки, сервера приложений типа Nginx, софт для работы с изображениями и полно чего ещё.
Зачем же тогда нам Lua в PHP? Чаще всего, приложения на KPHP приятно тестировать на PHP. А ещё это оставляет нам простор для того, чтобы, в случае чего, было проще переходить туда-сюда, ведь наш код всегда валиден и там, и там.
Но это всё если отбросить образовательный момент. По FFI мало материалов, особенно таких нетривиальных. Неужели плохо, что их становится больше? Здесь и примеры как самому сделать, и готовая библиотека для подглядывания. :)
Вообще статья и так вышла довольно крупной и сложной, поэтому мне не хотелось дополнительно добавлять много букв об этой детали. 1-го предложения достаточно для тех, кто когда-нибудь пробовал делать нативные приложения расширяемыми. А если этого опыта нет, то это уже почти тема для отдельной статьи, ведь там больше одного подхода.
Сначала заранее прошу прощения за не очень доброжелательный тон моего сообщения. Буду честен, ваш комментарий мне настроения не поднял. Надеюсь, вы понимаете, что вы просто ткнули в меня палкой.
Мне не очень хотелось отвечать, потому что статья действительно старая это раз. А два - вы так говорите, как будто бы я перед тем, как что-то заявлять, не пробовал это. Я же жил какое-то время на emacs и кодил используя этот проект. Мне было интересно и удобно. Кому-то тоже понравилась идея. Разве этого не достаточно, чтобы ответить на ваше "зачем?"
Почему? Он вполне себе работал.
Там был способ описать что-то типа стабов, чтобы го видел эти функции как внешние, а компилятор goism для них создавал нужные обёртки с учётом типизации.
Опять не понимаю, почему такой вывод? Возможно, я просто чего-то не понял.
У нас код транслироваться не будет, если там есть ошибки. А то, что в рантайме там уже не те представления - это деталь реализации. У вас на уровне машинного кода тоже нет типизации, но высокоуровневые языки как-то работают. Статическая типизация на то и статическая, что она на этапе компиляции известна и там мы с ней работает. Как рантайм это потом исполняет - отдельный вопрос.
Статические анализаторы лиспа, конечно, работать не будут. Но зачем они нужны, если для Go у нас больше анализаторов и есть коммерческие в том числе?
Документация того, что написано на elisp доступна напрямую не будет, но я выше писал про стабы. Стабы причём я программно генерил, через сам elisp. Ведь документация и сигнатуры доступны через сам emacs. И вот уже у нас из Go есть go to definition с документацией.
На всю stdlib Go тоже документация работает. Вопрос только в правильной компиляции stdlib.
На все пакеты для emacs, написанные на Go, тоже документация работала бы.
Update: https://github.com/hajimehoshi/ebiten/issues/2143
Возможно, в ebiten появится какое-то API для более дружелюбного позиционирование текста.
Будем следить за обновлениями.
А потом раздался голос персонажа из Diablo:
Могу добавить, что если человек неплохо владеет Go, то писать на нём игры относительно приятно. Да, ощущается недостаток библиотек и фреймворков, но что-то для себя делать не так сложно. Если я как-то смогу, то постараюсь двигать геймдев на Go хотя бы на локальном уровне.
Кроме привычного языка для гофера, полезны в геймдеве эти особенности тулчейна (не утверждаю, что этого нет в других тулчейнах, но всё же):
Очень удобный
go:embed
. Легко все ресурсы записать в бинарник одной директивой.Профилирование и бенчмарки на высоте. И это встроенно в стандартную поставку.
Тестирование тоже из коробки. TDD для геймдева ещё никогда не было настолько просто.
В целом язык приятен: есть контроль над памятью, но не настолько, чтобы это было утомительно. Мы относительно надёжно можем выделять на стеке, а то, что на куче, удалит GC
Производительность достаточная, чтобы написать движок на самом Go, и игровую логику тоже писать на Go. Есть ещё интерпретаторы Go, если нужно динамическое скриптование прикрутить. AAA игры может и не получится написать таким образом, но в инде жить можно.
Так как язык не имеет традиционного ООП с наследованием, меньше соблазна делать иерархии типа
Label < Control < CanvasItem < Node < Object
; Я не говорю, что это прямо ужасно, но мне кажется, что глубина в 6-7 - это не так красиво.С генериками стало проще писать нужные абстракции типобезопасно. Сделать систему сигналов и слотов - это не особо сложно.
Хороший stdlib. Он композируем и в нём есть всякие JSON и http сервера. В
C#
относительно недавно завезли json в stdlib, а раньше нужно было ставить отдельный пакет. И этот пакет у меня время старта приложения замедлял на секунды 3-4, потому что, видимо, там какая-то нетривиальная инициализация (?)Я уверен, есть и другие пункты. Go как минимум не так ужасен для геймдева, как может показаться. :)
Редактировать прошлое сообщение уже поздно, поэтому накину ссылку с цитатой другим сообщением.
https://go.dev/doc/effective_go#interface-names
Иногда этой конвенции можно избежать, но для интерфейсов из 1-го метода её чаще всего придерживаются. Для интерфейсов из более чем одного метода можно не делать комбинацию типа Read+Write = ReadWriter, а придумать концептуальное название. В нашем случае, всё равно будет полезно иметь методы типа
IsDisposed()
, так что можно было назвать интерфейсGraphicsObject
или как-то так. Но именование - сложная штука и хорошее название должно быть консистентно с остальными именами в движке или библиотеки. Чтобы не делать слишком много предположений, я просто взял общепринятую концепцию и не добавлял методы, которые не пригодились конкретно в этой статье.Не знаю, честно. По-моему, не странно не знать чего-то, когда ты только начинаешь что-то делать.
Интерфейс назван по конвенциям Go. Глагол + er. Иногда получаются не совсем корректные с точки зрения английского слова.
Сейчас используется собственный форк gogrep, где теперь, например, поддерживается несколько новых форм частичного синтаксиса. Принципиально это не меняет того, что матчинг осуществляется по синтаксису, но немного развязывает руки для дальнейших расширений и интеграций. Ту проблему взаимодействия фильтров и вариадичных шаблонов я не решал потому что не нашёл эффективного и красивого решения.
В утилите perfguard используется гибридный подход. Почти все диагностики сделаны через правила ruleguard. Более глубокий анализ делается расширением, через обычный go/ast и прочие пакеты. go-critic, к слову, работает так же: простейшее через правила, остальное ручками. ruleguard не задумывался как единственный инструмент для статического анализа. Это легко встраиваемая библиотека для описания шаблонами того, что нудно писать руками.
Могу ещё добавить, что staticcheck внутри себя тоже имеет движок правил, похожий на ruleguard. Правда там вместо gogrep паттернов своеобразный S-expr синтаксис (как в лиспах). Этот движок, насколько мне известно, внутренний и никогда не продвигался как механизм расширения staticcheck. ruleguard - это более публичный продукт в этой же области. Ни в staticcheck, ни в go-critic, эти движки не могут предоставить 100% решения задач. Но они сильно упрощают добавление типовых паттернов и/или прототипирование диагностик.
Из совсем революционного: скоро в Where будет гораздо меньше ограничений благодаря смешиванию статически реализованных правил и того, что будет исполняться через интерпретатор quasigo. Правда там же будет релиз dsl v1, который внесёт обратно-несовместимые изменения, но даст некоторую гарантию стабильности в будущем (TODO по DSL уже долго накапливались).
Хочу ещё дополнить тем, что в пакете reflect некоторые операции относительно быстрые и совместимые с высокой производительностью, если понимать некоторые нюансы.
Например, если значение уже выделено в куче, то какой-нибудь
reflect.ValueOf(x).IsNil()
может быть эффективнее, чем крупный type switch по типам и сравнение с nil, потому что reflect сделает почти чёрную магию с распаковкой интерфейса и проверкой его data поля (напрямую через unsafe будет ещё быстрее, но unsafe не всегда оправдан).А вот вещи типа
MethodByName
даже линковщик могут смутить и сделать его более консервативным при удалении мёртвого кода (ведь какой-то метод могут внезапно вызвать по имени через строку, чьё значение статически неизвестно).Хотя если хочется контроля и стабильности, наверное лучше не использовать reflect вовсе (когнитивно так проще, чем пытаться быть экспертом и следить за изменениями в разных версиях Go). И те же fmt пакеты туда же. Справедливости ради, на практике мало кому нужно настолько контролировать производительность, но всякие zerolog и zap не просто так появились. :)
Рандомные советы, которые тоже можно было бы добавить в статью:
Кеш golangci-lint иногда приходится сбрасывать при использовании правил и их активном изменении. Есть даже issue об этом.
Как включать и отключать отдельные правила через golangci-lint конфиг, используя параметры enable/disable линтера gocritic/ruleguard.
Возможно, вы как-то решили проблему того, что в golangci-lint в yaml файла может быть неудобно прописывать путь к правилам? Есть, опять же, issue про это. Вроде как решилось тем, что теперь можно в конфиге для этого использовать интерполяцию. https://github.com/golangci/golangci-lint/pull/2308
Можно поделиться опытом тестирования диагностик. В go/analysis и ruleguard есть почти всё, чтобы это было удобным, но есть нюансы и продвинутые фичи. Например, можно ещё и quickfix'ы тестировать. :)
Описание всяких проблем в использований и путей для их обхода тоже было бы ценно. В том числе для меня, чтобы понять, какие вещи нужно фиксить в первую очередь внутри самого ruleguard.
В докладе по ссылке было побольше информации. Кажется, в статью можно было бы как минимум добавить недостающее и стало бы лучше.
Если планируется дорабатывать статью, то что-то из этого добавить всё ещё не поздно. Если же не планируется дорабатывать, то у читателей будут пара затравок на что посмотреть дальше.
Я, конечно, понимаю, что вы просто хотели пошутить, но может кому-то реально будет понятнее с пояснениями.
В статье упоминается pprof несколько раз. Он встроен в тулинг Go. Но им я не измерял, а просматривал CPU профиль.
cumulative - суммарное время (англ. - совокупное, накопительное), с учётом вызываемых внутри функции других функций. Flat при этом показывает "собственное" время функции.
Каждый семпл в
profile.proto
обычно имеет стектрейс. Первый элемент из него -- текущая функция, которая исполняется сейчас. Если мы считаем flat, то в статистику добавляется только это значение. Остальные элементы -- функции выше по стеку. Если мы считаем sum, то эти значения тоже учитываются. Подробнее можно посмотреть в самих исходниках pprof.Возвращаясь к гачи-теме, как вам такой сниппет для pprof (100% рабочий):
Теперь мне не даёт покоя некорректный код выше.
Вот исправленная версия: https://go.dev/play/p/S-2VASgk1kT
При этом не обязательно хранить индекс, наверное можно сразу методы, возвращаемые
Method(i)
, сохранять в мапе.Так или иначе, есть альтернативы
MethodByName()
которые будут работать гораздо лучше.