Этот вопрос связан с другим моим вопросом под названием Вызов MASM PROC из C++/CLI в режиме x64 приводит к неожиданным проблемам с производительностью. Я не получил никаких комментариев и ответов, но в конце концов я сам обнаружил, что проблема вызвана переходниками функций, которые вставляются компилятором всякий раз, когда управляемая функция вызывает неуправляемую, и наоборот. Я не буду вдаваться в подробности еще раз, потому что сегодня я не хочу заострять внимание на еще одном последствии этого механизма настройки.
Чтобы дать некоторый контекст для вопроса, моя проблема заключалась в замене функции C++ для умножения 64-128-битных целых чисел без знака в неуправляемом классе C++/CLI функцией в файле MASM64 ради производительности. Замена ASM максимально проста:
AsmMul1 proc ; ?AsmMul1@@$$FYAX_K0AEA_K1@Z
; ecx : Factor1
; edx : Factor2
; [r8] : ProductL
; [r9] : ProductH
mov rax, rcx ; rax = Factor1
mul rdx ; rdx:rax = Factor1 * Factor2
mov qword ptr [r8], rax ; [r8] = ProductL
mov qword ptr [r9], rdx ; [r9] = ProductH
ret
AsmMul1 endp
Я ожидал большого прироста производительности, заменив скомпилированную функцию с четырьмя 32-битными умножениями на 64-битные простой инструкцией CPU MUL. Большим сюрпризом стало то, что версия ASM была примерно в четыре раза медленнее (!), чем версия C++. После долгих исследований и тестов я обнаружил, что некоторые вызовы функций в C++/CLI включают преобразование, которое, очевидно, является настолько сложной вещью, что занимает гораздо больше времени, чем сама функция.
Прочитав больше об этом thunking оказалось, что всякий раз, когда вы используете параметр компилятора /clr
, соглашение о вызовах всех функций незаметно изменяется на __clrcall, что означает, что они становятся управляемыми функциями. Исключениями являются функции, которые используют встроенные функции компилятора, встроенный ASM и вызовы других DLL через dllimport, и, как показали мои тесты, сюда входят функции, вызывающие внешние функции ASM.
До тех пор, пока все взаимодействующие функции используют соглашение __clrcall (т. е. являются управляемыми), thunking не используется, и все работает гладко. Как только граница управляемого/неуправляемого пересекается в любом направлении, срабатывает переключение, и производительность серьезно снижается.
Теперь, после этого длинного пролога, давайте перейдем к сути моего вопроса. Насколько я понимаю соглашение __clrcall
и переключатель компилятора /clr
, такая маркировка функции в неуправляемом классе C++ заставляет компилятор выдавать код MSIL. Я нашел это предложение в документации __clrcall:
Помечая функцию как __clrcall, вы указываете, что реализация функции должна быть MSIL и что собственная функция точки входа не будет создана.
Честно говоря, меня это пугает! В конце концов, я прохожу через трудности написания кода C++/CLI, чтобы получить настоящий нативный код, то есть сверхбыстрый машинный код x64. Однако это не похоже на значение по умолчанию для смешанные сборки. Пожалуйста, поправьте меня, если я ошибаюсь: если я использую значения проекта по умолчанию, заданные VC2017, моя сборка содержит MSIL, который будет скомпилирован JIT. Истинный?
Существует #pragma managed
, который, по-видимому, запрещает создание MSIL в пользу собственного кода для каждой функции. Я протестировал его, и он работает, но проблема в том, что thunking снова мешает, как только собственный код вызывает управляемую функцию, и наоборот. В моем проекте C++/CLI я не нашел способа настроить преобразование и генерацию кода без снижения производительности в каком-либо месте.
Итак, что я спрашиваю себя сейчас: в чем вообще смысл использования C++/CLI? Дает ли это мне преимущества в производительности, когда все по-прежнему скомпилировано в MSIL? Может быть, лучше написать все на чистом C++ и использовать Pinvoke для вызова этих функций? Я не знаю, я как бы застрял здесь.
Может быть, кто-то может пролить свет на эту ужасно плохо документированную тему...
MUL
. . На самом деле вы заменили функцию C++ на функцию ASM, а не на инструкцию ASM. Функции C++ могут быть встроены компиляторами C++, что устраняет все накладные расходы на вызовы функций. ВашAsmMul1
должен быть вызван с обычными накладными расходами - person MSalters   schedule 22.03.2019