【问题标题】:Performance loss if function is not defined in class declaration如果函数未在类声明中定义,则性能损失
【发布时间】:2015-01-13 02:31:31
【问题描述】:

我有一个高性能代码,其中包括在这篇文章底部找到的类。我的问题是,一旦我不在类声明中定义 advecu 函数,而是将声明和实现分开(如我所愿),我在英特尔 C++ 和 Clang 编译器中都会失去相当多的性能。但是,我不明白为什么。当我删除模板时,所有编译器的两种方式的性能都是相同的。

template<bool dim3>
struct Advec4Kernel
{
  static void advecu(double * restrict ut, double * restrict u, double * restrict v, double * restrict w, double * restrict dzi4, const Grid &grid)
  {
    int    ijk,kstart,kend;
    int    ii1,ii2,ii3,jj1,jj2,jj3,kk1,kk2,kk3;
    double dxi,dyi;

    ii1 = 1;
    ii2 = 2;
    ii3 = 3;
    jj1 = 1*grid.icells;
    jj2 = 2*grid.icells;
    jj3 = 3*grid.icells;
    kk1 = 1*grid.ijcells;
    kk2 = 2*grid.ijcells;
    kk3 = 3*grid.ijcells;

    kstart = grid.kstart;
    kend   = grid.kend;

    dxi = 1./grid.dx;
    dyi = 1./grid.dy;

    for(int k=grid.kstart; k<grid.kend; k++)
      for(int j=grid.jstart; j<grid.jend; j++)
        for(int i=grid.istart; i<grid.iend; i++)
        {
          ijk = i + j*jj1 + k*kk1;
          ut[ijk] -= ( cg0*((ci0*u[ijk-ii3] + ci1*u[ijk-ii2] + ci2*u[ijk-ii1] + ci3*u[ijk    ]) * (ci0*u[ijk-ii3] + ci1*u[ijk-ii2] + ci2*u[ijk-ii1] + ci3*u[ijk    ]))
                     + cg1*((ci0*u[ijk-ii2] + ci1*u[ijk-ii1] + ci2*u[ijk    ] + ci3*u[ijk+ii1]) * (ci0*u[ijk-ii2] + ci1*u[ijk-ii1] + ci2*u[ijk    ] + ci3*u[ijk+ii1]))
                     + cg2*((ci0*u[ijk-ii1] + ci1*u[ijk    ] + ci2*u[ijk+ii1] + ci3*u[ijk+ii2]) * (ci0*u[ijk-ii1] + ci1*u[ijk    ] + ci2*u[ijk+ii1] + ci3*u[ijk+ii2]))
                     + cg3*((ci0*u[ijk    ] + ci1*u[ijk+ii1] + ci2*u[ijk+ii2] + ci3*u[ijk+ii3]) * (ci0*u[ijk    ] + ci1*u[ijk+ii1] + ci2*u[ijk+ii2] + ci3*u[ijk+ii3])) ) * cgi*dxi;

          if(dim3)
          {
            ut[ijk] -= ( cg0*((ci0*v[ijk-ii2-jj1] + ci1*v[ijk-ii1-jj1] + ci2*v[ijk-jj1] + ci3*v[ijk+ii1-jj1]) * (ci0*u[ijk-jj3] + ci1*u[ijk-jj2] + ci2*u[ijk-jj1] + ci3*u[ijk    ]))
                       + cg1*((ci0*v[ijk-ii2    ] + ci1*v[ijk-ii1    ] + ci2*v[ijk    ] + ci3*v[ijk+ii1    ]) * (ci0*u[ijk-jj2] + ci1*u[ijk-jj1] + ci2*u[ijk    ] + ci3*u[ijk+jj1]))
                       + cg2*((ci0*v[ijk-ii2+jj1] + ci1*v[ijk-ii1+jj1] + ci2*v[ijk+jj1] + ci3*v[ijk+ii1+jj1]) * (ci0*u[ijk-jj1] + ci1*u[ijk    ] + ci2*u[ijk+jj1] + ci3*u[ijk+jj2]))
                       + cg3*((ci0*v[ijk-ii2+jj2] + ci1*v[ijk-ii1+jj2] + ci2*v[ijk+jj2] + ci3*v[ijk+ii1+jj2]) * (ci0*u[ijk    ] + ci1*u[ijk+jj1] + ci2*u[ijk+jj2] + ci3*u[ijk+jj3])) ) * cgi*dyi;
          }

          ut[ijk] -= ( cg0*((ci0*w[ijk-ii2-kk1] + ci1*w[ijk-ii1-kk1] + ci2*w[ijk-kk1] + ci3*w[ijk+ii1-kk1]) * (ci0*u[ijk-kk3] + ci1*u[ijk-kk2] + ci2*u[ijk-kk1] + ci3*u[ijk    ]))
                     + cg1*((ci0*w[ijk-ii2    ] + ci1*w[ijk-ii1    ] + ci2*w[ijk    ] + ci3*w[ijk+ii1    ]) * (ci0*u[ijk-kk2] + ci1*u[ijk-kk1] + ci2*u[ijk    ] + ci3*u[ijk+kk1]))
                     + cg2*((ci0*w[ijk-ii2+kk1] + ci1*w[ijk-ii1+kk1] + ci2*w[ijk+kk1] + ci3*w[ijk+ii1+kk1]) * (ci0*u[ijk-kk1] + ci1*u[ijk    ] + ci2*u[ijk+kk1] + ci3*u[ijk+kk2]))
                     + cg3*((ci0*w[ijk-ii2+kk2] + ci1*w[ijk-ii1+kk2] + ci2*w[ijk+kk2] + ci3*w[ijk+ii1+kk2]) * (ci0*u[ijk    ] + ci1*u[ijk+kk1] + ci2*u[ijk+kk2] + ci3*u[ijk+kk3])) )
                     * dzi4[k];
        }
  }
};

分离后的版本如下:

// header
template<bool dim3>
struct Advec4Kernel
{
  static void advecu(double *, double *, double *, double *, double *, const Grid &);
}

// source
template<bool dim3>
void Advec4Kernel<dim3>::advecu(double * restrict ut, double * restrict u, double * restrict v, double * restrict w, double * restrict dzi4, const Grid &grid)
{
  //...
}

【问题讨论】:

  • 听起来像是内联失败。你看过生成的代码吗?
  • 分离的时候是不是标记为inline?定义是否可用于使用该函数的代码,使用之前?
  • 请展示你如何将它们分开。 dim3 在这种情况下仍然是模板参数吗?
  • 我猜你的意思是定义而不是声明......如果一个成员函数没有在类定义中声明,它也不能在其他任何地方声明。
  • @Chiel 也许也可以在声明中添加restrict,就像在定义中一样?

标签: c++ templates optimization hpc


【解决方案1】:

显然,编译器使用restrict 关键字进行了一些优化。要从这些优化中受益,函数的 声明 必须包含 restrict 关键字。这是determined empirically;不知道是编译器缺陷还是法律问题。

代码:

// header
template<bool dim3>
struct Advec4Kernel
{
  static void advecu(double *restrict, double *restrict, double *restrict, double *restrict, double *restrict, const Grid &);
}

【讨论】:

  • 它不可能是真正的“法律”,因为 C++ 中的 restrict 是编译器扩展,而不是标准的一部分。
  • @Angew 我同意这一点,尽管如此,这解决了我在 Clang 和英特尔 C++ 编译器上的问题。我还是想知道解释
  • @Chiel 然后你必须查阅这些编译器的文档,特别是它们如何处理它们的restrict 扩展。
【解决方案2】:

在类声明中定义的函数在大多数情况下是内联的,这就是性能下降的原因

【讨论】:

  • 你确定是这个原因吗?这是一个巨大的功能。
  • @NeilKirk 我也是这么想的。这个问题困扰了我很长时间,我更愿意远离将实现放在类声明中。我可以删除模板,但这会引入大量代码重复。
  • @Chiel 当涉及到模板时,我只是将它放在类定义中,因为它更容易。在这种情况下,布尔标志不能只是一个类成员吗?
  • @Neil 不,我需要使用模板,因为内部循环中的 if 语句会完全破坏性能。
  • @Chiel 如果您想避免将函数模板主体放在标头中并且模板参数的域是有限的(例如,bool 只有 2 个值),您可以保留定义在源文件中并显式实例化它们。
猜你喜欢
  • 2023-02-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-07
  • 1970-01-01
  • 2019-03-10
相关资源
最近更新 更多