Pull to refresh

Comments 11

быстро как и обычный, без накладных расходов на использование метаданных, как это обычно бывает при использовании reflection.

поясните плиз подробнее! каков примерно выигрыш в скорости в % ?

Я не назову вам конкретных цифр, т.к. они очень сильно зависят от того, что вы пытаетесь оптимизировать, насколько часто этот код вызывается и какую долю занимает в остальном коде. Обычно разница такая же, как если сравнивать универсальный код для решения нескольких задач и код, который написан под конкретную задачу.

Например, если это код какого-то маппера, который копирует данные из одного объекта в другой, то реализация через reflection может быть на порядок или на два медленнее.

Тут приводятся некоторые цифры по поводу использования reflection: https://stackoverflow.com/questions/25458/how-costly-is-net-reflection

Естественно, нет никакого смысла писать динамический метод, чтобы вызвать его один раз на старте приложения.

Правильно ли я понимаю, что после JIT компиляции структура исполнения алгоритма остаётся в виде стековой машины? Это какая-то эмуляция или идёт прямая трансляция на команды конкретной машины для прямой работы со стеком (ну если они есть, конечно)? То есть регистры процессора так и остаются незадействованные (или исп. только для служебных целей) или JIT-компилятор может что-то оптимизировать и переносить в регистры и применять соответствующие команды? Особенно как обрабатываются intrinsic команды (поддержка короых не так давно появилась в .NET)?

Где размещаются аргументы функции и локальные переменные до того как они переносятся на стек или обратно? А как идёт доступ к переменным/аргументам более высокого уровня (из локальных функций или доступ к this ссылки владельца функции)? Сам этот перенос - это копирование значения из одной ячейки памяти в другую (т.е. в процессе обработки переменных/аргументов они постоянно копируются)? А как обрабатываются переменные структурных типов (структуры, кортежи.... отдельно про классы)?

Правильно ли я понимаю, что после JIT компиляции структура исполнения алгоритма остаётся в виде стековой машины?

Нет, всё компилируется в машинный код, как с обычным .NET кодом. Фактически динамические методы хранятся в динамической сборке, которая практически ничем не отличается от обычных dll сборок к которым все привыкли.

Разница только в том, что обычный код вы компилируете из C# в IL, а IL потом через JIT компилируется в машинный код. А с динамическими методами (а также с компилируемыми Expresson Trees и динамическими сборками) вы в runtime генерируете сразу IL, который для выполнения через JIT компилируется в машинный код.

За исключением, может быть, того, что динамические методы, если на них не остаётся ссылок в коде потом собираются GC.

Особенно как обрабатываются intrinsic команды (поддержка короых не так давно появилась в .NET)?

Всё что умеет оптимизировать JIT компилятор, всё будет оптимизироваться и в динамических методах.

Где размещаются аргументы функции и локальные переменные до того как они переносятся на стек или обратно?

Я не уверен, что понял вопрос. Они размещаются там же где и аргументы и локальные переменные в обычном коде.

А как идёт доступ к переменным/аргументам более высокого уровня (из локальных функций или доступ к this ссылки владельца функции)?

Локальные функции, которые обращаются к переменным из окружающего контекста - это замыкания. Для замыканий C# создаёт специальный объект, где хранит все эти переменные.

Т.е. нужно научиться в runtime генерировать точно такой же код, который сгенерировал бы компилятор C#. Это не самая тривиальная задача, но это можно сделать.

По поводу this. Я постараюсь описать это в следующей статье. При создании делегата через CreateDelegate можно указать параметр target, который хранит объект, экземпляром которого будет являться метод. В этом случае все параметры метода "сдвигаются" на единицу, а this становится самым первым параметром и его значение можно получить через ldarg.0.

Сам этот перенос - это копирование значения из одной ячейки памяти в другую (т.е. в процессе обработки переменных/аргументов они постоянно копируются)?

Нет, не копируются. Стековая машина - это абстракция, которая упрощает понимание. Любой код .NET работает через стековую машину. Потом всё это оптимизируется при компиляции в машинный код.

А как обрабатываются переменные структурных типов (структуры, кортежи.... отдельно про классы)?

Это хороший вопрос. Я постараюсь ответить на него в следующей статье =)

Разница только в том, что обычный код вы компилируете из C# в IL, а IL потом через JIT компилируется в машинный код. А с динамическими методами (а также с компилируемыми Expresson Trees и динамическими сборками) вы в runtime генерируете сразу IL, который для выполнения через JIT компилируется в машинный код.

Стековая машина - это абстракция, которая упрощает понимание. Любой код .NET работает через стековую машину. Потом всё это оптимизируется при компиляции в машинный код.

Я просто не знаю какой машинной код компилирует JIT - это может быть и машинный код реализующий сегменты стековой машины. Или машинной код, оптимизирующий работы посредством регистров процессора? То есть JIT компилирует алгоритм для стековой машины в алгоритм для регистровой машины (конкретной платформы в лице исполнителя CPU)?

То есть JIT компилирует алгоритм для стековой машины в алгоритм для регистровой машины (конкретной платформы в лице исполнителя CPU)?

Да, всё должно быть именно так.

UFO just landed and posted this here

Насколько я понимаю, асинхронный код генерировать таким образом очень неудобно, expressions тоже не не очень для асинхронщины, стоит смотреть в сторону source generators?

Да, асинхронные методы сложно будет сгенерировать динамически, т.к. нужно сгенерировать код стейт-машины. Я бы в этом случае смотрел в сторону того, чтобы код с async-await написать на C# с минимальным количеством логики, а из него вызывать синхронные динамические методы.

По поводу source generators. Я бы сказал, что у них по отношению к динамическим методам разные области применения.

Source generators подходят для генерации кода на основе исходников. Например вы разметили код какими-то атрибутами и по ним сгенерировали код на этапе компиляции.

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

Есть же ещё .NET Compilation Platform (а именно часть Roslyn влице .NET Scripting)- там код можно писать прям на C# - затем компилировать его в сборку (она правда, будет с изолированным контекстом, применяющим тонкую proxy-послойку - но всё в одном домене приложения так что все ссылки на объекты в куче будут валидные, только стеки разные) - и при желании - через рефлексию выдернуть даже IL байткод (и уже создать динамический метод в текущем контексте на основе этого байткода - вот только встроить его в существующую стейтмашину навряд ли просто удастся); вот нет стандартных путей превращения байткода в поток IL op-кодов - для упрощения модификации. Но тут можно поискать библиотеку для такой конвертации (но я не нашёл, хотя все программы-рефлеторы это умеют делать - и это не так уж сложно)

Другой подход - а кто мешает обернуть динамический метод асинхронную оболочку, передавая метод в качестве делегата (хотя этот и несколько увеличит затраты на вызов такого метода, зато оболочка сама будет встраиваться в стейтмашину)

По поводу генераторов исходного кода C# (опять же Roslyn) - всё тоже не просто - к сожалению механизм позволяет генерировать только новый код (хотя есть техника замены старого кода на новый - но безвозвратно - т.е. прямо в самих исходных исходниках - прошу прощения за тавтологию) - лично я не знаю техники, позволяющий исключить исходный исходный код из итоговой компиляции, заменив его на новый, так чтобы он так и остался в исходниках (вероятно его можно сразу настраивать не компилируемым со всеми вытекающими - автоматически анализируемым он тоже не будет). Тем самым сильно затрудняется процесс встраивания сгенерированного кода существующий!

Но и не надо забывать, что генераторы исходного кода статические. Всё-таки потребность в динамических методах больше, скорее, в необходимости генерировать их на основе рантайм состояния.

И тут может быть интересная и гибридная техника: когда основной код генерируется на .NET Scripting (на ЯП C# - потому что это проще, или в виде синтаксического дерева - немного сложнее, чем текстом - зато красивее, но в данном случае бессмысленно, т.к. для .NET Scripting нужен именно текст алгоритма, хотя выражения синтакс.дерева легко превращаются в текст), затем его вызов оборачивается динамическим методом (кстати библиотеки такие уже есть для .NET Scripting), а уже динамический метод (или делегат) далее используется в основном коде, например в качестве подключаемого обработчика события, или вот далее встраивается в основную стейтмашину, или используется в качестве какого-либо ремоут взаимодействия!

UFO just landed and posted this here
Sign up to leave a comment.

Articles