具有自动生成的 C 代码的大型 C++ dll 的性能损失
Posted
技术标签:
【中文标题】具有自动生成的 C 代码的大型 C++ dll 的性能损失【英文标题】:Performance penalty for large C++ dll's with autogenerated C code 【发布时间】:2012-09-07 07:31:04 【问题描述】:我正在开发一款需要调用一系列优化求解器的软件。每个求解器都是一段自动生成的 C 代码,包含数千行代码。我使用了 200 个这样的求解器,不同之处仅在于要解决的优化问题的大小。
总而言之,这些自动生成的求解器会产生大约 180MB 的 C 代码,我在 Visual Studio 2008 中使用 extern "C" /*200 solvers' headers*/
语法将其编译为 C++。编译所有这些非常慢(使用 "最大速度 /O2" 优化标志,大约需要 8 小时)。出于这个原因,我认为将求解器编译成单个 DLL 是一个好主意,然后我可以从一个单独的软件中调用它(这将有一个合理的编译时间,并允许我抽象出所有这些 extern “ C”的东西来自更高级别的代码)。编译后的 DLL 大约为 37MB。
问题是当使用 DLL 执行这些求解器之一时,执行需要大约 30 毫秒。如果我只将单个求解器编译成一个 DLL,并从同一个程序中调用它,则执行速度大约快 100 倍(
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*) ¶ms, (optim_001_output*) optimSoln, (optim_001_info*) &infoOut);
break;
case 2:
exitFlag = optim_002_solve((optim_002_params*) ¶ms, (optim_002_output*) optimSoln, (optim_002_info*) &infoOut);
break;
/*
...
etc.
...
*/
case 200:
exitFlag = optim_200_solve((optim_200_params*) ¶ms, (optim_200_output*) optimSoln, (optim_200_info*) &infoOut);
break;
return exitFlag;
;
;
【问题讨论】:
您在哪个平台上观察到这一点?在 32 位架构的 Linux 上,.so
文件需要占用一个寄存器的-fPIC
,因此代码运行速度可能会慢 5%(因为编译器溢出更多)。
帖子提到了 Visual Studio 和 DLL,其中提到了 Windows。
@Basile, themel:是的,都是在 Windows 上,用 VS2008 编译的。
但是对于 32 位的 Windows(可能还为编译 DLL 代码保留一个额外的寄存器)或 64 位的 Windows 可能很重要。 IT 可能与 Windows 系统上的 ABI 约定有关(32 位和 64 位系统不同)。
@Basile:都是 32 位的。编辑:抱歉,要明确一点:我正在编译 Win32,但我在 64 位 Windows 7 上运行。
【参考方案1】:
我不知道您的代码是否已内联到示例中的每个案例部分。如果您的函数是内联函数,并且您将它们全部放在一个函数中,那么它会慢得多,因为代码是在虚拟内存中布局的,在执行代码时这将需要 CPU 大量跳转。如果不是全部内联,那么这些建议可能会有所帮助。
您的解决方案可能会改进...
一) 1) 将项目分成 200 个独立的 dll。然后使用 .bat 文件或类似文件进行构建。 2)在每个dll中创建名为“MyEntryPoint”的导出功能,然后根据需要使用动态链接加载库。这将相当于一个繁忙的音乐程序,加载了许多小的 dll 插件。使用 GetProcAddress 获取指向 EntryPoint 的函数指针。
或者……
B) 将每个解决方案构建为单独的 .lib 文件。然后,这将根据解决方案非常快速地编译,然后您可以将它们全部链接在一起。构建一个指向所有函数的函数指针数组,然后通过查找来调用它。
结果 = SolveInterceptWhichStep;
将所有库合并到一个大库中不应花费八小时。如果需要那么长时间,那么您做的事情就非常错误。
和...
尝试将代码放入不同的实际 .cpp 文件中。如果它们都在不同的单元等中,也许那个特定的编译器会做得更好......然后一旦每个单元被编译,如果你不改变任何东西,它将保持编译状态。
【讨论】:
【参考方案2】:确保测量和平均多次调用优化器的时间,因为在第一次调用之前设置可能会有很大的开销。
然后还要检查 200 分支条件语句(您的开关)对您的性能有什么影响!尝试消除该开关进行测试,在您的测试项目中只调用一个求解器,但将它们全部链接到 DLL 中。您是否仍然看到性能缓慢?
【讨论】:
我平均拨打了 100 多个电话,但第一次通话似乎没有明显差异。我将尝试链接所有求解器,但删除开关 - 不幸的是,此测试将涉及整个项目的编译/代码生成,因此需要一天时间。【参考方案3】:我认为您生成代码的原因是为了更好的运行时性能,以及更好的正确性。 我做同样的事情。
我建议您尝试this technique 找出运行时性能问题是什么。
如果您看到 100:1 的性能差异,这意味着每次您中断它并查看程序的状态时,您有 99% 的机会会发现问题所在。
就构建时间而言,模块化是有意义的。 这些都不会对运行时间产生太大影响,除非这意味着您正在执行疯狂的 I/O。
【讨论】:
以上是关于具有自动生成的 C 代码的大型 C++ dll 的性能损失的主要内容,如果未能解决你的问题,请参考以下文章
是否可以在 DLL 中导出具有 C 链接的 C++ 成员方法?
C++ VS:多 DLL 解决方案中的“新建”和“删除”覆盖