Для моего проекта С++/CLI я просто попытался измерить стоимость указателей на объекты С++/CLI и .NET.
Я ожидал, что указатели на функции С++/CLI быстрее, чем .NET делегаты. Таким образом, мой тест отдельно подсчитывает количество вызовов делегата .NET и указателя встроенной функции в течение 5 секунд.
Результаты
Теперь результаты были (и до сих пор) шокирующими меня:
- делегат .NET: выполнение 910M с результатом 152080413333030 в 5003 мс
- Указатель функций: 347M исполнение с результатом 57893422166551 в 5013ms
Это означает, что использование указателя функции С++/CLI почти в 3 раза медленнее, чем использование управляемого делегата из кода С++/CLI. Как это может быть? Я должен использовать управляемые конструкции, когда дело доходит до использования интерфейсов, делегатов или абстрактных классов в критичных для производительности разделах?
Код проверки
Функция, вызываемая непрерывно:
__int64 DoIt(int n, __int64 sum)
{
if ((n % 3) == 0)
return sum + n;
else
return sum + 1;
}
Код, вызывающий метод, пытается использовать все параметры, а также возвращаемое значение, поэтому ничто не оптимизируется (надеюсь). Здесь код (для делегатов .NET):
__int64 executions;
__int64 result;
System::Diagnostics::Stopwatch^ w = gcnew System::Diagnostics::Stopwatch();
System::Func<int, __int64, __int64>^ managedPtr = gcnew System::Func<int, __int64, __int64>(&DoIt);
w->Restart();
executions = 0;
result = 0;
while (w->ElapsedMilliseconds < 5000)
{
for (int i=0; i < 1000000; i++)
result += managedPtr(i, executions);
executions++;
}
System::Console::WriteLine(".NET delegate: {0}M executions with result {2} in {1}ms", executions, w->ElapsedMilliseconds, result);
Подобно вызову делегата .NET, используется указатель на функцию С++:
typedef __int64 (* DoItMethod)(int n, __int64 sum);
DoItMethod nativePtr = DoIt;
w->Restart();
executions = 0;
result = 0;
while (w->ElapsedMilliseconds < 5000)
{
for (int i=0; i < 1000000; i++)
result += nativePtr(i, executions);
executions++;
}
System::Console::WriteLine("Function pointer: {0}M executions with result {2} in {1}ms", executions, w->ElapsedMilliseconds, result);
Дополнительная информация
- Скомпилирован с Visual Studio 2012
- .NET Framework 4.5 была нацелена
- Сборка релиза (количество показов остается пропорциональным для отладочных построек)
- Соглашение о вызове __stdcall (__fastcall не разрешено, когда проект компилируется с поддержкой CLR)
Все выполненные тесты:
- Виртуальный метод .NET: выполнение 1025M с результатом 171358304166325 в 5004 м.
- делегат .NET: выполнение 910M с результатом 152080413333030 в 5003 мс
- Виртуальный метод: 336M исполнение с результатом 56056335999888 в 5006 м.
- Указатель функций: 347M исполнение с результатом 57893422166551 в 5013ms
- Функциональный вызов: 1459M исполнения с результатом 244230520832847 в 5001ms
- Встроенная функция: исполнение 1385M с результатом 231791984166205 в 5000 мс
Прямой вызов "DoIt" представлен здесь "Функциональный вызов", который, как представляется, встраивается компилятором, поскольку нет (значимой) разницы в количествах выполнения по сравнению с вызовом встроенной функции.
Вызовы для виртуальных методов С++ являются "медленными" в качестве указателя функции. Виртуальный метод управляемого класса (класс ref) выполняется так же быстро, как делегат .NET.
Update: Я немного углубился, и кажется, что для тестов с неуправляемыми функциями переход на собственный код происходит каждый раз, когда вызывается функция DoIt. Поэтому я завернул внутренний цикл в другую функцию, которую я заставил скомпилировать неуправляемым:
#pragma managed(push, off)
__int64 TestCall(__int64* executions)
{
__int64 result = 0;
for (int i=0; i < 1000000; i++)
result += DoItNative(i, *executions);
(*executions)++;
return result;
}
#pragma managed(pop)
Кроме того, я протестировал std:: function следующим образом:
#pragma managed(push, off)
__int64 TestStdFunc(__int64* executions)
{
__int64 result = 0;
std::function<__int64(int, __int64)> func(DoItNative);
for (int i=0; i < 1000000; i++)
result += func(i, *executions);
(*executions)++;
return result;
}
#pragma managed(pop)
Теперь новые результаты:
- Функциональный вызов: 2946M исполнение с результатом 495340439997054 в 5000 мс
- std:: function: 160M исполнение с результатом 26679519999840 в 5018ms
std:: function немного разочаровывает.