Как стать автором
Обновить

Комментарии 37

Делал подобное на vb6 лет 15 назад. Правда там использовал известную для ВэБэшников api функцию CallWindowProc. Впрочем, метод (как тот, так и описанный здесь в статье) прибит гвоздями к x86.

CallWindowProc использовали при царе Горохе (в 90-ые) а последние лет 15 — DispCallFunc
Чтобы Вы не тратили время на разработку и отладку генератора нативного кода, вот Вам ссылка на проект, в котором реализованы все инструкции x86/x64, даже векторизация.
Спасибо, может пригодиться.

Для работы с указателями и ref — структурами можно посмотреть в сторону
System.Runtime.CompilerServices.Unsafe. Класс доступен как в netcore3.0 так и в netstandard2.0 как самостоятельный nuget-пакет.
Позволяет кастовать (в определенных случаях) ссылки к указателям и обратно, а так же "безопасно" выполнять арифметику над указателями.

Хм, да, он, возможно, заметно облегчил бы мне задачу. Спасибо, заинтересовался! Надо будет глянуть их реализацию.
Сударь знает толк…
Как насчет написать процедуру в байт-кодах и передать управление в нее?
В IL-овских? Мысль. Попробую на досуге что-нибудь придумать. Глядишь, ещё в один пост выльется…
Кстати. Если Вам достаточно иметь метод с передаваемыми аргументами, то вполне достаточно аллоцировать память через Marshal.AllocHGlobal например, потом запротектить на выполнение (единственный хак вне платформы), затем в этот участок памяти перелить опкоды (тут iced либа в помощь), затем через тот же Marshal получить вполне законный управляемый делегат — Marshal.GetDelegateForFunctionPointer, который можно где-то сохранить и переиспользовать. Все законно, в рамках платформы.

Генерация IL-кода на лету доступна "из коробки".
Google "ilgenerator", например: https://habr.com/ru/company/skbkontur/blog/262711/


А вот возиться с нативным ассемблером из C# не вижу смысла.

Если интересно, можете здесь посмотреть.

А почему не использовали Mono?
Там что то упоминалось про вставки на ассемблере если вы компилируете в наитивный код (не знаю -не слежу, убрали ли эту возможность в связи с объединением кодовой базы с Net ).То есть получается исполняемый файл под конкретную архитектуру, если на размер плевать то можно сделать автономным, не завищищий есть ли у тебя mono -среда,.А если что вызывать из ехешника библиотеки среды исполнения, тогда размер поменьше.

Потому что с Mono знаком исключительно понаслышке и про такую возможность услышал сейчас впервые. К тому же всё это делалось не ради практической пользы, а, скорее, из интереса. Интереса же в использовании готового функционала немного…

Если уж извращаться, то почему бы не через expressions?
Типа
Action a = Asm.Compile((ptr)=>{
var label1 = Asm.Mov(Asm.Ecx, ptr);
Asm.Cmp(Asm.Ecx, Asm.Edx);
Asm.Je(label1);
})

У C# (да и прочих IL-языков) никаких шансов пробиться в топ по производительности.

Негативных факторов слишком много.

Насколько я помню, первые попытки отмазаться встроенным ассемблером начались в Дельфях. Хотя сильно не уверен в первенстве.

Но главный вопрос — а нафига?

Вообще возможно собрать что-то на синтаксисе C# что будет компилироваться в идеально прилизанный нативный код.
У разработчиков Unity в блоге есть занимательный текст (на английском) как они перевели performance critical вещи с C++ на C# и вполне счастливы, суть претензий — в борьбе с компиляторами С++ где векторизация имеет склонность тихо отваливаться.
Но, конечно, уже это не совсем C#

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

А потом я переписал эту программу на C#/Mono, и она с ходу заработала ещё быстрее.

А вчера я начал баловаться с Rust, и переписанная на нём программа заработала ещё в полтора раза быстрее.
У C# (да и прочих IL-языков) никаких шансов пробиться в топ по производительности.

Но это связано с компиляцией в процессе работы программы, а не с самим фактом существования IL-кода. Тот же LLVM, но со статической компиляцией, вполне себе быстр.


Но главный вопрос — а нафига?

Вот именно. Поначалу я пытался использовать ассемблер для SSE/AVX оптимизаций. Потом понял бесперспективность данного направления из-за необходимости писать ветки кода и для x86, и для x64 архитектуры и открыл для себя интринсики.

У нас с другом была такая шутка. Что есть человек, который настолько любит ассемблер, что куда бы он ни устроился и на каком бы языке не писал, пишет код с ассемблерными вставочками :) И этим бесит коллег, потому что никто эти вставки не понимает)

Вставки по большому счету всем понятны, а вот то, как потом поддерживать код с такими вставками — коллегам не понятно

Я просто положу это здесь

Приведенная реализация InvokeAsm может в любой момент поломаться из-за GC. Ну хорошо, VirtualProtect работает надёжно потому что в блоке fixed — а дальше-то что помешает массиву байт быть перемещенным в другое место?

Да, действительно. По-хорошему надо бы InvokeAsm обернуть в метод, запрещающий сборку мусора так же, как это сделано в SafeInvokeAsm.

Честно говоря, такое решение мне кажется не совсем элегантным, так что если кто-либо сможет предложить иной вариант борьбы с GC — с удовольствием почитаю.

Так ведь правильное решение уже подсказали выше: явно выделить кусок памяти вне кучи. Лучше даже через VirtualAlloc.


А потом можно получить управляемый делегат через Marshal.GetDelegateForFunctionPointer. Только надо придумать как связать время жизни делегата и время жизни участка памяти, похоже придётся ещё и управляемый делегат налету компилировать, с вызовами DangerousAddRef и DangerousRelease...

А как на счёт GCHandle.Alloc с типом Pinned и после возврата вызвать Free?
Пока хэндл не будет освобождён, этот кусок памяти будет прибит гвоздями и gc не будет его трогать.
В Visual Studio была подобная возможность для С++, но, вроде, ее убрали. Все равно оптимизацию современного компилятора не переплюнуть. Он и SSE и остальное сам все делает. Так что, не стоит вскрывать эту тему.

Лет 7-8 назад качество компиляции VS оставляло желать лучшего. Код на интринсиках работал в 2-3 раза медленее вручную написанного на ассемблере даже с максимальными оптимизациями. А потом я открыл для себя интеловский компилятор.

Да, примерно 7-8 лет назад можно было невооруженным взглядом увидеть разницу если во встроенном ассемблере написать, скажем, код для векторизации. Года четыре назад можно уже было только в лучшем случае сравниться с компилятором.
1. Берем высокоуровневый кроссплатформенный язык с рантаймом, GC и прочим
2. Выкидываем все вышеперечисленное и пишем на асме
3. ???
4. PROFIT!
Немного запоздало, но все же оставлю ссылку на свой пост о генерации IL-кода: https://habr.com/ru/post/251765/.
Пример там не абстрактный, а вполне рабочий, применяемый в реальных проектах.

Ну а что касается ассемблера, использовать его в C# смысла не вижу, язык создан не для этого. Кроме того, теряется кроссплатформенность, которая является главной (на мой взгляд) фишкой .NET Core, а за ним будущее всего .NET.

Что-то там у вас тоже всё слишком сложно вышло, можно же использовать System.Linq.Expressions для той же цели.

Да, согласен, ассемблер в C# — вещь абсолютно бессмысленная. А всё, что делалось мной в рамках этого проекта, — делалось просто потому, что это было интересно. Ни малейшей пользы или смысла не планировалось изначально.
Может стоило сразу добавить в заголовок?

А то мне за вопрос о смысле впаяли -15 =)
Мда, как-то слегка неловко получилось… :)
Впрочем, я не ожидал, что кто-то может всерьёз воспринять это как попытку улучшить язык.
Интересная идея, пожалуй я тоже покапаюс.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории