我在下面发布了一些代码,用于测试从 c ++ / cli中调用本机 c ++ 和 c#的方法的性能(以毫秒为单位) 使用 Visual Studio 2010 。我有一个单独的本机c ++项目,它被编译成dll。当我从c ++调用c ++时,我得到的预期结果比管理对应的快得多(大约4倍)。但是,当我从c ++ / cli调用c ++时,性能会慢10倍。
从c ++ / cli调用本机c ++时,这是一种预期的行为吗?我的印象是不应该有显着差异,但这个简单的测试显示不然。这可能是c ++和c ++ / cli编译器之间的优化差异吗?
更新
我对cpp进行了一些更新,因此我没有在紧密的循环中调用方法(正如Reed Copsey所指出的那样),并且结果表明无意义中的性能差异或者非常小。当然,取决于互操作的方式。
·H
#ifndef CPPOBJECT_H
#define CPPOBJECT_H
#ifdef CPLUSPLUSOBJECT_EXPORTING
#define CLASS_DECLSPEC __declspec(dllexport)
#else
#define CLASS_DECLSPEC __declspec(dllimport)
#endif
class CLASS_DECLSPEC CPlusPlusObject
{
public:
CPlusPlusObject(){}
~CPlusPlusObject(){}
void sayHello();
double getSqrt(double n);
// Update
double wasteSomeTimeWithSqrt(double n);
};
#endif
的.cpp
#include "CPlusPlusObject.h"
#include <iostream>
void CPlusPlusObject::sayHello(){std::cout << "Hello";}
double CPlusPlusObject::getSqrt(double n) {return std::sqrt(n);}
double CPlusPlusObject::wasteSomeTimeWithSqrt(double n)
{
double result = 0;
for (int x = 0; x < 10000000; x++)
{
result += std::sqrt(n);
}
return result;
}
C / CLI
const unsigned set = 100;
const unsigned repetitions = 1000000;
double cppcliTocpp()
{
double n = 0;
System::Diagnostics::Stopwatch^ stopWatch = gcnew System::Diagnostics::Stopwatch();
stopWatch->Start();
while (stopWatch->ElapsedMilliseconds < 1200){n+=0.001;}
stopWatch->Reset();
for (int x = 0; x < set; x++)
{
stopWatch->Start();
CPlusPlusObject cplusplusObject;
n += cplusplusObject.wasteSomeTimeWithSqrt(123.456);
/*for (int i = 0; i < repetitions; i++)
{
n += cplusplusObject.getSqrt(123.456);
}*/
stopWatch->Stop();
System::Console::WriteLine("c++/cli call to native c++ took " + stopWatch->ElapsedMilliseconds + "ms.");
stopWatch->Reset();
}
return n;
}
double cppcliTocSharp()
{
double n = 0;
System::Diagnostics::Stopwatch^ stopWatch = gcnew System::Diagnostics::Stopwatch();
stopWatch->Start();
while (stopWatch->ElapsedMilliseconds < 1200){n+=0.001;}
stopWatch->Reset();
for (int x = 0; x < set; x++)
{
stopWatch->Start();
CSharp::CSharpObject^ cSharpObject = gcnew CSharp::CSharpObject();
for (int i = 0; i < repetitions; i++)
{
n += cSharpObject->GetSqrt(123.456);
}
stopWatch->Stop();
System::Console::WriteLine("c++/cli call to c# took " + stopWatch->ElapsedMilliseconds + "ms.");
stopWatch->Reset();
}
return n;
}
double cppcli()
{
double n = 0;
System::Diagnostics::Stopwatch^ stopWatch = gcnew System::Diagnostics::Stopwatch();
stopWatch->Start();
while (stopWatch->ElapsedMilliseconds < 1200){n+=0.001;}
stopWatch->Reset();
for (int x = 0; x < set; x++)
{
stopWatch->Start();
CPlusPlusCliObject cPlusPlusCliObject;
for (int i = 0; i < repetitions; i++)
{
n += cPlusPlusCliObject.getSqrt(123.456);
}
stopWatch->Stop();
System::Console::WriteLine("c++/cli took " + stopWatch->ElapsedMilliseconds + "ms.");
stopWatch->Reset();
}
return n;
}
int main()
{
double n = 0;
n += cppcliTocpp();
n += cppcliTocSharp();
n += cppcli();
System::Console::WriteLine(n);
System::Console::ReadKey();
}
答案 0 :(得分:4)
但是,当我从c ++ / cli调用c ++时,性能会慢10倍。
桥接CLR和本机代码需要编组。从C ++ / CLI进入本机方法调用时,每次方法调用总会有一些开销。
开销(在这种情况下)看起来如此之大的唯一原因是你在一个紧凑的循环中调用一个非常快的方法。如果你要批处理类,或者调用一个在运行时方面明显更长的方法,你会发现开销非常小。
答案 1 :(得分:1)
这些微观基准非常危险。你努力避免典型的基准错误,但仍陷入经典陷阱。您的意图是测量方法调用开销,但这不是实际发生的事情。抖动优化器能够使用标准代码优化技术,例如代码提升和方法内联。当您查看生成的机器代码时,您才能真正看到它。 Debug + Windows + Disassembly窗口。
我使用启用了抖动优化器的VS2012,32位Release版本对此进行了测试。 C ++ / CLI代码是最快的,大约需要128毫秒:
000000bf fld qword ptr ds:[01212078h]
000000c5 fsqrt
000000c7 fstp qword ptr [ebp-20h]
//
// stopWatch->Start() call elided...
//
n += cPlusPlusCliObject.getSqrt(123.456);
000000f5 fld qword ptr [ebp-20h]
000000f8 fadd qword ptr [ebp-14h]
000000fb fstp qword ptr [ebp-14h]
for (int i = 0; i < repetitions; i++)
000000fe dec eax
000000ff jne 000000F5
换句话说,std :: sqrt()调用已从循环中提升,内循环只是从生成的值中执行添加。没有方法调用。还要注意它实际上没有测量sqrt()调用所需的时间:)
使用C#方法调用的循环有点慢,耗时约180毫秒:
000000ea fld qword ptr ds:[01211EC0h]
000000f0 fsqrt
000000f2 fadd qword ptr [ebp-14h]
000000f5 fstp qword ptr [ebp-14h]
for (int i = 0; i < repetitions; i++)
000000f8 dec eax
000000f9 jne 000000EA
只是内联方法调用Math :: Sqrt(),它没有被提升。实际上并不确定原因,抖动优化器执行的优化确实包含时间因素。
我不会发布互操作的代码。但是,是的,由于需要实际进行函数调用需要大约380毫秒,不能内联非托管代码,加上防止垃圾收集器进入非托管堆栈帧所需的thunk。 thunk非常快,需要几纳秒,但这无法与直接内联fadd或fsqrt的抖动优化器竞争。