加速C++代码

Ope*_*way 32 c++ optimization

我正在编写一个C++数字运算应用程序,其中瓶颈是一个必须计算为double的函数:

 template<class T> inline T sqr(const T& x){return x*x;}
Run Code Online (Sandbox Code Playgroud)

和另一个计算

Base   dist2(const Point& p) const
       { return sqr(x-p.x) + sqr(y-p.y) + sqr(z-p.z); }
Run Code Online (Sandbox Code Playgroud)

这些操作占用了80%的计算时间.我想知道你是否可以建议让它更快的方法,即使有某种准确性损失

谢谢

gue*_*ser 14

首先,确保dist2可以内联(从你的帖子中不清楚是否是这种情况),如果有必要在头文件中定义它(通常你需要这样做 - 但是如果你的编译器在链接时间,那不一定是这种情况).

假设x86架构,请确保允许编译器使用SSE2指令(SIMD指令集的示例)生成代码(如果它们在目标体系结构上可用).为了给编译器提供优化这些优化的最佳机会,您可以尝试将sqr操作一起批处理(SSE2指令一次最多可以执行4次浮点运算或2次双操作,具体取决于指令..但当然它可以只有在准备好的多个操作的输入时才执行此操作.我不会过于乐观地认为编译器能够确定它可以批量处理它们.但是你至少可以设置你的代码以便在理论上可行.

如果你仍然对速度不满意并且你不相信你的编译器做得最好,你应该考虑使用编译器内在函数,这将允许你明确地编写潜在的并行指令..或者你可以正确提前编写特定于体系结构的汇编代码,以利用SSE2或最适合您架构的指令.(警告:如果您手动编写程序集,要么特别注意它仍然被内联,或者进入大批量操作)

为了更进一步,(并且就像glowcoder已经提到的那样),您可以在GPU上执行这些操作.对于您的具体情况,请记住GPU通常不支持双精度浮点数.虽然如果它非常适合您正在做的事情,您将以这种方式获得更好的性能.谷歌GPGPU或诸如此类的东西,看看什么是最适合你的.


Ste*_*hen 10

什么是Base

它是一个非显式构造函数的类吗?您可能正在创建大量临时Base对象.这可能是一个很大的CPU.

template<class T> inline T sqr(const T& x){return x*x;}
Base   dist2(const Point& p) const {
  return sqr(x-p.x) + sqr(y-p.y) + sqr(z-p.z);
}
Run Code Online (Sandbox Code Playgroud)

如果p成员变量属于类型Base,则可以调用sqrBase对象,这将为减去的坐标创建临时对象,sqr然后为每个添加的组件创建临时对象.

(没有类定义,我们无法分辨)

您可以通过强制将sqr调用置于原始状态而不是使用Base直到达到返回类型来加速它dist2.

其他绩效改进机会是:

  • 如果精度较低,则使用非浮点运算.
  • 使用不需要调用dist2太多的算法,可能是缓存或使用传递属性.
  • (这可能是显而易见的,但是)确保在编译时打开优化.


Sal*_*gar 8

我认为优化这些函数可能很困难,您可能最好优化调用这些函数以减少调用它们的代码,或者以不同方式执行操作.


Gle*_*len 8

您没有说是否dist2可以并行调用.如果可以的话,那么你可以构建一个线程池,并将每个线程的工作分成更小的块.

你的探查者告诉你什么在里面发生dist2.您是否实际上一直使用100%的CPU,或者您是否缓存丢失并等待数据加载?

说实话,我们真的需要更多细节来给你一个明确的答案.


Fre*_*son 6

如果sqr()仅在原始类型上使用,则可以尝试按值而不是引用来获取参数.这样可以节省你的间接费用.

  • 它是内联的,所以我认为不重要. (3认同)

Pau*_*l R 6

如果您可以适当地组织数据,那么您可以在此处使用SIMD优化.为了有效实现,您可能希望填充Point结构,使其具有4个元素(即添加第四个虚拟元素进行填充).


cor*_*iKa 5

如果您有许多这样做,并且您正在进行图形或"图形化"任务(热建模,几乎任何3D建模),您可以考虑使用OpenGL并将任务卸载到GPU.这将允许计算并行运行,具有高度优化的运行能力.毕竟,你会期望像distance或distanceq这样的东西在GPU上拥有自己的操作码.

一位当地大学的研究人员将几乎所有用于AI工作的3d计算工作卸载到GPU上,并取得了更快的结果.


Pot*_*ter 4

有很多答案提到了 SSE 已经 \xe2\x80\xa6 但由于没有人提到如何使用它,我将在 \xe2\x80\xa6 中抛出另一个

\n\n

您的代码拥有矢量化器工作所需的几乎所有内容,除了两个限制:别名和对齐。

\n\n
    \n
  • 别名是指两个名称引用两个同一对象的问题。例如,my_point.dist2( my_point )将在 的两个副本上进行操作my_point。这会扰乱矢量化器。

    \n\n

    C99 定义了指针的关键字来指定所引用的对象是唯一引用的:当前作用域中restrict不会有其他指针指向该对象。restrict大多数不错的 C++ 编译器也实现 C99,并以某种方式导入此功能。

    \n\n
      \n
    • 海湾合作委员会称其为__restrict__. 它可以应用于参考文献或this
    • \n
    • MSVC 称之为__restrict. 如果支持与 GCC 有任何不同,我会感到惊讶。
    • \n
    \n\n

    (不过,它不在 C++0x 中。)

    \n\n
    #ifdef __GCC__\n#define restrict __restrict__\n#elif defined _MSC_VER\n#define restrict __restrict\n#endif\n\xc2\xa0\nBase   dist2(const Point& restrict p) const restrict\n
    Run Code Online (Sandbox Code Playgroud)
  • \n
  • 大多数 SIMD 单元需要与向量的大小对齐。C++ 和 C99 保留对齐实现定义,但 C++0x 通过引入[[align(16)]]. 由于这还需要一段时间,您可能需要编译器的半便携式支持,例如restrict

    \n\n
    #ifdef __GCC__\n#define align16 __attribute__((aligned (16)))\n#elif defined _MSC_VER\n#define align16 __declspec(align (16))\n#endif\n\xc2\xa0\nstruct Point {\n    double align16 xyz[ 3 ]; // separate x,y,z might work; dunno\n    \xe2\x80\xa6\n};\n
    Run Code Online (Sandbox Code Playgroud)
  • \n
\n\n

这并不能保证产生结果;GCC 和 MSVC 都实施了有用的反馈,告诉您哪些内容没有矢量化以及原因。谷歌你的矢量化器以了解更多信息。

\n