使用自动生成的 C 代码创建大型 C++ 动态链接库会有性能惩罚。

4
我正在开发一款需要调用优化器的软件。每个优化器都是自动生成的C代码,有数千行代码。我使用了200个这样的优化器,只是解决问题的优化大小不同。
总的来说,这些自动生成的优化器约为180MB的C代码,我使用Visual Studio 2008将其编译成C++,采用extern "C"{ /*200 solvers' headers*/ }语法。编译这些代码非常慢(使用“最大速度/O2”优化标志需要约8小时)。因此,我认为将这些优化器编译成单个DLL是一个好主意,然后我可以从另一个软件中调用它(这个软件具有合理的编译时间,并允许我从更高级别的代码中抽象出所有这些extern "C"的内容)。编译后的DLL大小约为37MB。
问题在于,使用该DLL执行其中一个优化器需要约30ms的时间。如果我仅将一个单独的优化器编译为DLL并从同一程序中调用它,则执行速度快了100倍(<1ms)。这是为什么?我能解决这个问题吗?
下面是DLL的代码。每个优化器使用相同的结构(即它们具有相同的成员变量),但它们具有不同的名称,因此需要进行类型转换。
extern "C"{
#include "../Generated/include/optim_001.h"
#include "../Generated/include/optim_002.h"
/*etc.*/
#include "../Generated/include/optim_200.h"
}

namespace InterceptionTrajectorySolver
{

__declspec(dllexport) InterceptionTrajectoryExitFlag SolveIntercept(unsigned numSteps, InputParams params, double* optimSoln, OutputInfo* infoOut)
{
  int exitFlag;

  switch(numSteps)
  {
  case   1:
    exitFlag = optim_001_solve((optim_001_params*) &params, (optim_001_output*) optimSoln, (optim_001_info*) &infoOut);
    break;
  case   2:
    exitFlag = optim_002_solve((optim_002_params*) &params, (optim_002_output*) optimSoln, (optim_002_info*) &infoOut);
    break;
  /*
    ...
    etc.
    ...
  */
  case   200:
    exitFlag = optim_200_solve((optim_200_params*) &params, (optim_200_output*) optimSoln, (optim_200_info*) &infoOut);
    break;
  }

  return exitFlag;
};

};

1
帖子提到了Visual Studio和DLL,这表明它是关于Windows的。 - themel
@Basile,themel:是的,所有的都在Windows上,使用VS2008编译。 - mwmwm
但如果它是32位的Windows(这可能还为编译DLL代码保留了一个额外的寄存器),或者是64位的Windows,那么它可能很重要。这可能与您的Windows系统上的ABI约定有关(对于32位和64位系统来说是不同的)。 - Basile Starynkevitch
@Basile:都是32位的。 编辑:抱歉,明确一下:我正在编译Win32,但我在64位的Windows 7上运行。 - mwmwm
@BasileStarynkevitch:32位Windows没有这样的功能。 - jalf
显示剩余3条评论
3个回答

1

我不知道你的代码是否在示例中的每个case部分中都是内联的。如果你的函数是内联函数,并且你把它们全部放在一个函数中,那么它会变得更慢,因为代码在虚拟内存中布局,这将需要CPU在执行代码时进行大量跳转。如果没有全部内联,那么也许以下建议可以帮助你。

你的解决方案可能会通过以下方式得到改进...

A) 1) 将项目分成200个单独的dll。然后使用.bat文件或类似工具进行构建。 2) 在每个dll中创建导出函数名为"MyEntryPoint",然后使用动态链接在需要时加载库。这将相当于一个忙碌的音乐程序,其中加载了许多小的dll插件。使用GetProcAddress获取EntryPoint的函数指针。

或者...

B) 将每个解决方案构建为单独的.lib文件。这样每个解决方案的编译速度就会非常快,然后你可以将它们全部链接在一起。构建一个包含所有函数指针的数组,并通过查找调用它。

result = SolveInterceptWhichStep;

将所有的lib合并成一个大的lib不应该需要八个小时。如果需要那么长时间,那么你肯定做错了什么。

而且...

尝试将代码放入不同的实际.cpp文件中。也许如果它们都在不同的单元中,特定的编译器会做得更好...然后,一旦每个单元被编译,如果您不更改任何内容,它将保持编译状态。


0
请确保您测量和平均多次调用优化器的时间,因为在第一次调用之前可能会有大量设置开销。
然后还要检查那个 200 分支条件语句(您的 switch)对性能的影响! 尝试在测试中消除该 switch,只调用一个求解器,但将所有求解器链接到 DLL 中。 您是否仍然看到性能较慢?

我正在对100个调用进行平均,但第一次调用似乎没有明显的差异。我将尝试链接所有求解器,但删除开关--不幸的是,这个测试将涉及到整个项目的编译/代码生成,所以需要一天时间来完成。 - mwmwm

0

我猜你生成代码的原因是为了更好的运行时性能和更好的正确性。我也这么做。

我建议你尝试这个技术来找出运行时性能问题所在。

如果你看到100:1的性能差异,那就意味着每次中断并查看程序状态时,有99%的可能性你会看到问题所在。

至于构建时间,当然将其模块化是有意义的。除非这意味着你正在进行疯狂的I/O操作,否则这些都不应该对运行时间产生太大影响。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接