Различия в производительности между P/Invoke и С++ Wrappers

В процессе обучения P/Invoke я задал этот предыдущий вопрос:

Как P/Вызывать, когда указатели задействованы

Однако я не совсем понимаю последствия использования P/Invoke в С# для создания обертки в Managed С++. Создание одной и той же DLL с использованием P/Invoke в С# определило более чистый интерфейс, так как я мог использовать DLLImport во встроенном ресурсе, но применил бы ли Managed С++-оболочку для родной DLL, где я сам занимаюсь маршалингом?

Ответ 1

С++-оболочка должна быть быстрее, посмотрите на страницу MSDN:

С++ Interop использует самый быстрый способ маршалинга данных, тогда как P/Invoke использует самый надежный метод. Это означает, что С++ Interop (в моде, типичном для С++) обеспечивает оптимальную производительность по умолчанию, а программист отвечает за рассмотрение случаев, когда это поведение небезопасно или целесообразно.

В основном основная причина заключается в том, что P/Invoke выполняет привязку, blitting, проверку ошибок, в то время как С++ interop просто подталкивает параметры в стеке и вызывает функцию.

Еще один момент, который следует помнить, заключается в том, что С++ может вызывать несколько API за один вызов, в то время как параметр P/Invoke EVERY, передаваемый по адресу, закрепляется и отжимается на КАЖДОМ звонке, копируется и скопированы назад и т.д.

Ответ 2

Вы получите лучшую производительность? Зависит от того, что вы делаете и как вы это делаете. Вообще говоря, ваш успех будет более вероятным при выполнении управляемых/неуправляемых переходов, и тем больше из них вы сможете вырезать лучше. В идеале, ваш интерфейс с неуправляемым кодом должен быть коротким и не болтливым.

Скажем, что у вас есть неуправляемый код, содержащий несколько тысяч объектов. Вы могли бы предоставить API, подобный этому, управляемому коду:

int GetFooCount();
IntPtr GetFoo(int n);
void ReleaseFoo(IntPtr p);

и что все хорошо и хорошо, пока вы не начнете использовать его в С#, как это:

int total = API.GetFooCount();
IntPtr[] objects = new IntPtr[total];
for (int i=0; i < total; i++) {
    objects[i] = GetFoo(i);
}
// and later:
foreach (IntPtr p in objects) { ReleaseFoo(p); }

который для total == 1000 будет 4002 управляемых/неуправляемых переходах. Если вместо этого у вас есть это:

int GetFooCount();
void GetFoos(IntPtr[] arr, int start, int count);
void ReleaseFoos(IntPtr arr, int start, int count);

то вы можете выполнить ту же работу с 6 переходами. Как вы думаете, что будет лучше?

Конечно, следующий важный вопрос, который нужно задать, - "стоит ли это повышение производительности?" поэтому не забудьте сначала измерить.

Одна вещь, о которой вы должны знать, также заключается в том, что смешные вещи могут случиться с STL, когда вы работаете с управляемым С++. У меня есть неуправляемый библиотечный код, который используется STL. Мой опыт состоял в том, что если бы я когда-либо касался любого типа STL в управляемом С++, то все из них стали управляемыми реализациями. Конечным результатом этого было то, что код низкого уровня выполнял управляемые/неуправляемые переходы при итерации списков. Хлоп. Я решил это, никогда не открывая типы STL для управляемого С++.

В нашем опыте гораздо лучше (если возможно) перейти на С# → управляемую С++ wrapper- > статическую библиотеку, если у вас есть возможность сделать это.