Можно ли делать вставки MSIL в код на c# ? По аналогии, как в C++ можно сделать ассемблерную вставку в код.
Возник такой вопрос, потому что, программа написанная на C# не использует полные арифметические возможности процессора.
У меня программа, будет работать с длинными целыми.
Будет тип,
struct UInt128
{
UInt64 high;
UInt64 low;
}
Чтобы определить операции умножения и деления двух экземпляров UInt128, очевидно, придется перемножить (или разделить) пары UInt64 внутри этого типа,
затем складывать промежуточные результаты, и в итоге, записывать верхнюю и нижнюю UInt64-части, внутри типа UInt128.
К примеру, для умножения, имеем,
UInt128 a;
UInt128 b;
Первый промежуточный результат — перемножим два UInt64-типа, a.low * b.low. В итоге, получим результат — 128-битное число, в виде двух 64-битных чисел.
Процессор позволяет это сделать одной ассемблерной инструкцией mul — результат, в регистрах RDX:RAX, причем в RDX — старшие 64 бита, а в RAX — младшие 64 бита.
И вот, в этот момент, нет возможности (или по крайней мере, мне неизвестно как) средствами c# прямо и получить эту старшую часть — которая уже содержится в регистре RDX !
(только часть RAX и будет в результате получена — в произведении, которое тоже имеет тип UInt64).
Чтобы получить старшую часть — нужно при перемножении двух UInt64 — их в свою очередь, разбивать
на два под-типа UInt32, и делать 4 умножения вместо одного, на уровне процессора.
От этого ниже производительность (а в некоторых задачах, в том числе моей, эти умножения-деления будут занимать весьма весомую часть), и не используются все возможности 64-битных процессоров.
(Аналогичная возможность и при делении, процессор позволяет разделить 128-битное содержимое RDX:RAX на 64-битное содержимое, к примеру RBX регистра, в то время,
как в C# можно делить только 64-битные числа, на 64-битные числа. Но пока — вопрос как хотя бы только умножение реализовать. Если это можно, то далее с MSIL вставками
можно и деление оптимизировать)
На c++ можно было бы просто сделать ассемблерную вставку, и в нужный момент, прочитать-таки, этот регистр RDX, после умножения, и не делать в программе
в 4 раза больше операций умножения, чем это необходимо на процессорном уровне.
Можно ли подобное сделать, с помощью вставки MSIL в код на C# ? Т.е. заставить после умножения сгенерировать такой машинный код, который прочитает регистр RDX
и запишет его в UInt64 переменную, и не надо было бы делать лишних умножений?
{в этот момент в регистре RDX уже содержится старшие 64 бита, часть умножения (a * b). Нужно чтобы MSIL сгенерировал для 64-битных процессоров, аналогичный
Ответ легко нагуглить, но думается, что такие вставки недопустимы.
Ну так пишите на с++ (хоть с соотв. библиотеками, хоть со вставками), и вызывайте этот код через interop.
Здравствуйте, SergeyY, Вы писали:
SY>Можно ли делать вставки MSIL в код на c# ? По аналогии, как в C++ можно сделать ассемблерную вставку в код.
Агрессивный инлайнинг метода — уже вставка MSIL. Так что надо пробовать (MethodImplAttribute/Options, .net 4.6).
Однако в MSIL никакого x86-like div разумеется нет. Так что надо смотреть ещё что там он (JIT) за код генерирует по факту в релизной сборке.
Оно точно очень-очень нужно быть супер оптимальным? Если это числодробилка — C# всё равно сольет плюсам, и возможно имеет смысл задуматься об отдельном модуле. Если операции выполняются эпизодически — то лучше уж код попроще без издевательств.
Погуглите SO — там вроде есть и готовые решения, может подойдут.
Здравствуйте, Sharov, Вы писали:
S>Здравствуйте, SergeyY, Вы писали:
SY>>Заранее, большое спасибо.
S>Ответ легко нагуглить, но думается, что такие вставки недопустимы. S>Ну так пишите на с++ (хоть с соотв. библиотеками, хоть со вставками), и вызывайте этот код через interop.
А если написать на с++, и вызвать этот код через interop — не будет ли каких нибудь дополнительных накладных расходов в программе?
(по сравнению со случаем, если бы заставили CIL сгенерировать код).
MSIL (или CIL) однозначно в определенном месте сгенерирует асм-инструкцию MUL , и далее, после выполнения её, нам по сути нужно прочитать
содержимое регистра RDX и записать по адресу, где находится наша переменная, и всё!
В самом .NET есть же возможность даже сам компилятор дописывать, удивительно, вот и хотелось бы, просто использовать полностью арифметические
возможности процессора..
Здравствуйте, fddima, Вы писали:
F>Здравствуйте, SergeyY, Вы писали:
SY>>Можно ли делать вставки MSIL в код на c# ? По аналогии, как в C++ можно сделать ассемблерную вставку в код.
F> Оно точно очень-очень нужно быть супер оптимальным?
Тут дело не супер-оптимальности. Дело в том, что если не использовать эти возможности процессора, то у нас количество умножений-делений, с длинной
арифметикой — увеличивается, причем сразу в 4 раза.
Если бы это не давало бы прироста производительности, то в прошлом и 64-битные регистры вообще не создавали бы, перемножали бы 32-битные числа
(тогда количество операций было бы в 16 раз больше), или и вовсе 16-битные (тогда количество операций было бы в 64 раза больше).
Я не гоняюсь за сверх-оптимальностью, но здесь выигрыш был бы существенный..
Здравствуйте, SergeyY, Вы писали:
SY>Я не гоняюсь за сверх-оптимальностью, но здесь выигрыш был бы существенный..
Здесь не нужно теории. Если у вас 1000 операций в секунду с этим типом — значит 4-х кратная "просадка" в микроскоп не видна. Если это именно что числодробилка и её можно отделить — то лучше постараться выделить в отдельный модуль. Да, накладные расходы будут на интероп, но значительно меньше чем выполнить миллиард делений особо извращенным (неэффективным) способом.
UPD: В интеропе не забываем SuppressUnmanagedCodeSecurityAttribute — толку от проверок обычно нет, а время жрут.
Здравствуйте, SergeyY, Вы писали:
SY>У меня программа, будет работать с длинными целыми.
Нормальной быстрой математики под дотнет нет, большинство реализаций ничего кроме фейспалма не вызывают. Как пример.
Есть BigInteger, если устраивают тормоза в ~50x по сравнению с int64 — отлично. Если нет — остаются разнообразные обёртки типа MPIR.net, для совсем отчаянных есть gpgpu-обёртки типа CUDAfy.
Здравствуйте, fddima, Вы писали:
F>Здравствуйте, SergeyY, Вы писали:
SY>>Я не гоняюсь за сверх-оптимальностью, но здесь выигрыш был бы существенный.. F> Здесь не нужно теории. Если у вас 1000 операций в секунду с этим типом — значит 4-х кратная "просадка" в микроскоп не видна. Если это именно что числодробилка и её можно отделить — то лучше постараться выделить в отдельный модуль. Да, накладные расходы будут на интероп, но значительно меньше чем выполнить миллиард делений особо извращенным (неэффективным) способом.
F> UPD: В интеропе не забываем SuppressUnmanagedCodeSecurityAttribute — толку от проверок обычно нет, а время жрут.
Здравствуйте, SergeyY, Вы писали:
SY>Здравствуйте, Sharov, Вы писали:
S>>Здравствуйте, SergeyY, Вы писали:
SY>>>Заранее, большое спасибо.
S>>Ответ легко нагуглить, но думается, что такие вставки недопустимы. S>>Ну так пишите на с++ (хоть с соотв. библиотеками, хоть со вставками), и вызывайте этот код через interop.
SY>А если написать на с++, и вызвать этот код через interop — не будет ли каких нибудь дополнительных накладных расходов в программе?
Использовать interop для 2-х ассемблерных команд как-то странновато, согласен. С др. стороны у Вас "длинная" арифметика и там соотв. свои библиотеки и т.п. В данном случае оправдано.
SY>(по сравнению со случаем, если бы заставили CIL сгенерировать код).
Это самый быстрый вариант, безусловно. Вообще у ms появилось несколько jit'ов типа RyuJit и т.д. Можно посмотреть на них, может они и подойдут.
Здравствуйте, Sinix, Вы писали:
S>Здравствуйте, SergeyY, Вы писали:
SY>>У меня программа, будет работать с длинными целыми.
S>Нормальной быстрой математики под дотнет нет, большинство реализаций ничего кроме фейспалма не вызывают. Как пример.
S>Есть BigInteger, если устраивают тормоза в ~50x по сравнению с int64 — отлично. Если нет — остаются разнообразные обёртки типа MPIR.net, для совсем отчаянных есть gpgpu-обёртки типа CUDAfy.
S>Я бы вытащил числобробилку в натив и не мучался.
Да, видимо придется вытаскивать весь код на C++, а ассемблерными вставками (кроме UI), производительность для задач алгоритмического характера — важнее всего.
Просто на C# много других полезных и приятных вещей, одна сборка мусора чего стоит.
Вот и было интересно — если бы можно было , добавить в генерируемый CIL код что то свое (в данном случае — хотя бы просто прочитать регистр RDX в нужный момент),
то я мог бы сделать библиотеку классов для работы с длинной арифметикой гораздо быстрее этих BigInteger-ов, и использовал бы все возможности 64-битных процессоров.
А если нельзя часть CIL добавить, то очень жаль. (с интероп — как сказали, все таки, накладные расходы).
.NET от этого только выиграл бы.
Здравствуйте, SergeyY, Вы писали:
SY>Про интероп понятно, спасибо.
Сам по себе интероп довольно быстр. Но ессно это оверкил для операций которые должны быть заинлайнены (навроде сложения, деления), но всё равно весьма и весьма прилично работает.
Есть ещё трюк с вызовом нативного кода не покидая managed context через делегаты/эмитинг msil — это ещё быстрее, но рекомендовать это не буду. Да я уже и не помню какой это выигрыш даст. Во времена дотнета 2.0 был ощутимый выигрыш, но и недостатков не меньше.
Здравствуйте, SergeyY, Вы писали:
SY>Я не гоняюсь за сверх-оптимальностью, но здесь выигрыш был бы существенный..
Doing it wrong, без обид.
Как уже fddima ответил, начинать надо не с "я хочу быстрее", а с замеров на реальном сценарии. Если у вас есть запас на пару порядков, то лучше не заморачиваться и заняться чем-то более полезным для пользователей. Если нет — надо начинать с выбора более эффективного алгоритма / библиотеки, а не с "сейчас asm влепим и всё ускорится".
Не, если заморочиться, то можно получить довольно недорогой интероп, но не выгодней ли будет переташить в натив весь блок с вычислениями?
Здравствуйте, SergeyY, Вы писали:
SY>А если нельзя часть CIL добавить, то очень жаль. (с интероп — как сказали, все таки, накладные расходы).
Интероп можно и "похакать". Но поскольку у нас нативный код всё равно всё ещё где-то сбоку — просадки останутся. Под похакать я имею ввиду calli на нужный адрес. В принципе для кастомной арифметики могло бы подойти, но проблема в том что арифметика построеная таким образом незаинлайнится — и множество method-level оптимизаций останутся недоступными.
SY>.NET от этого только выиграл бы.
Врядли. Накой асм в дотнете? Так можно всё на асме написпл "вставками". Миксировать код никто не мешает и сейчас. Особенно если взять рантайм какойнибудь и похакать его, добавив интрисинков.
Для .NET — C++/CLI можно ещё попробовать для этого дела, но сам не люблю его. Предпочитаю классические плюсы отдельно, шарп отдельно.
Здравствуйте, fddima, Вы писали:
F>Здравствуйте, SergeyY, Вы писали:
SY>>А если нельзя часть CIL добавить, то очень жаль. (с интероп — как сказали, все таки, накладные расходы). F> Интероп можно и "похакать". Но поскольку у нас нативный код всё равно всё ещё где-то сбоку — просадки останутся. Под похакать я имею ввиду calli на нужный адрес. В принципе для кастомной арифметики могло бы подойти, но проблема в том что арифметика построеная таким образом незаинлайнится — и множество method-level оптимизаций останутся недоступными.
SY>>.NET от этого только выиграл бы. F> Врядли. Накой асм в дотнете?
А чем было бы плохо, и сам .NET оптимизировать. Если была бы возможность что то дописывать в компилятор .NET, или сгенерировать какие то свои CIL инструкции.
Кому не нужно — тот в это не полезет. Кому интересно — открываются возможности.
Здравствуйте, SergeyY, Вы писали:
SY>то я мог бы сделать библиотеку классов для работы с длинной арифметикой гораздо быстрее этих BigInteger-ов, и использовал бы все возможности 64-битных процессоров.
Нет никакого смысла — пришлось бы собирать и распространять копии под x86/x64/arm — раз, плюсы всё равно быстрее будут — два. Проще сразу в нативе это дело оформить.
Здравствуйте, SergeyY, Вы писали:
SY>А чем было бы плохо, и сам .NET оптимизировать. Если была бы возможность что то дописывать в компилятор .NET, или сгенерировать какие то свои CIL инструкции.
Свои CIL инструкции в основном ненужны — интрисинки — обычные методы, но JIT — может быть осведомлен о них и генерировать соответствующий нативный код. Так было сделано в Mono.Simd, и так же в дотнете (вылетело из головы как оно у них там сейчас называется). Понятное дело, что легко это не расширяется, тем не менее при желании поднять свой форк моно или неткора — вполне реально. Но это очень дорого, в плане затрат — легче нарисовать, что нужно на C++.
SY>Кому не нужно — тот в это не полезет. Кому интересно — открываются возможности.
Вот оно приблизительно так сейчас и есть. В дотнете гораздо более актуальней просто более хороший JIT / и доступный для простых смертных .net native. Короче там и без этого вагон работы, а выигрыш от такой расширяемости неочевиден.
P/Invoke — же сам по себе использовать легко, модули вызывать свои можно без дополнительных приседаний. Там где нужно приседать — дотнет и так и так ложится плоховасто, по крайней мере пока.