我什么时候应该在计算科学中使用 C++ 表达式模板,什么时候应该*不*使用它们?

计算科学 表现 C++ 自动分化 区间算术
2021-12-13 20:55:27

假设我正在使用 C++ 编写科学代码。在最近与一位同事的讨论中,有人认为表达式模板可能是一件非常糟糕的事情,可能使软件只能在某些版本的 gcc 上编译。据说,这个问题已经影响了一些科学代码,正如在这部《垮台》的戏仿的字幕中所暗示的那样(这些是我知道的唯一例子,因此链接。)

但是,其他人认为表达式模板很有用,因为它们可以通过避免将中间结果存储在临时变量中来产生性能提升,就像SIAM Journal of Scientific Computing 中的这篇论文一样。

我不太了解 C++ 中的模板元编程,但我知道它是一种用于自动微分和区间算术的方法,这就是我讨论表达式模板的方式。考虑到性能方面的潜在优势和维护方面的潜在劣势(如果这是正确的话),我应该什么时候在计算科学中使用 C++ 表达式模板,什么时候应该避免它们?

4个回答

我对表达式模板的问题是它们是一个非常容易泄漏的抽象。您花费大量工作编写非常复杂的代码来完成具有更好语法的简单任务。但是如果你想改变算法,你必须弄乱脏代码,如果你在类型或语法上犯了错误,你会得到完全无法理解的错误消息。如果您的应用程序完美地映射到基于表达式模板的库,那么可能值得考虑,但如果您不确定,我建议您只编写普通代码。当然,高级代码不太漂亮,但你可以做需要做的事情。作为一个好处,编译时间和二进制大小将大大减少,并且您不必处理由于编译器和编译标志选择而导致的巨大性能差异。

其他人评论了编写 ET 程序的难度以及理解错误消息的复杂性的问题。让我评论一下编译器的问题:确实,不久前的一个大问题是找到一个足够符合 C++ 标准的编译器,以使一切工作并使其可移植地工作。结果,我们发现了很多错误——我的名字中有 2-300 个错误报告,分布在 gcc、Intel icc、IBM xlC 和 Portland 的 pgicc 上。因此,deal.II 配置脚本是大量编译器错误测试的存储库,主要在模板、友元声明、命名空间等领域。

但是,事实证明编译器制造商真的齐心协力:今天,gcc 和 icc 今天通过了我们所有的测试,并且很容易编写在它们两者之间可移植的代码。我想说 PGI 也不甘落后,但它有一些多年来似乎并没有消失的怪癖。另一方面,xlC 则完全不同——他们每 6 个月修复一个错误,但尽管多年来一直向他们提交错误报告,但进展非常缓慢,xlC 从未能够成功编译 deal.II。

这一切的意思是:如果你坚持使用这两个大编译器,你可以期望它们今天也能正常工作。由于当今大多数计算机和操作系统通常至少具有其中之一,因此这就足够了。唯一比较困难的平台是 BlueGene,它的系统编译器通常是 xlC,有所有的错误。

很久以前,我对 ET 进行了一些试验,正如您所提到的,编译器仍在努力解决它们。我在我的一些代码中使用了blitz库进行线性代数。然后问题是获得好的编译器,因为我不是一个完美的 C++ 程序员,解释编译器错误消息。后者简直无法控制。编译器平均会生成大约 1000 行错误消息。我无法快速找到我的编程错误。

您可以在oonumerics网页上找到更多信息(有两个 ET 研讨会的会议记录)。

但我会远离他们......

问题已经从术语“表达式模板(ET)”开始。不知道有没有准确的定义。但在它的常见用法中,它以某种方式将“如何编码线性代数表达式”和“如何计算它”结合在一起。例如:

你编写向量操作

v = 2*x + 3*y + 4*z;                    // (1)

它由循环计算

for (int i=0; i<n; ++i)                 // (2)
    v(i) = 2*x(i) + 3*y(i) + 4*z(i);

在我看来,这是两个不同的东西,需要解耦:(1)是一个接口,(2)一个可能的实现。我的意思是这是编程中的常见做法。当然(2)可能是一个很好的默认实现,但总的来说,我希望能够利用一个专门的、专用的实现。例如,我想要一个像

myGreatVecSum(alpha, x, beta, y, gamma, z, result);    // (3)

当我编码时被调用(1)。也许(3)只是在内部使用了一个像(2)一样的循环。但根据向量大小,其他实现可能更有效。无论如何,一些高性能专家可以尽可能地实现和调整(3)。因此,如果 (1) 不能映射到 (3) 的调用,那么我宁愿避免使用 (1) 的语法糖并直接调用 (3)。

我所描述的并不是什么新鲜事。相反,这是 BLAS/LPACK 背后的理念:

  • LAPACK 中的所有性能关键操作都是通过调用 BLAS 函数来完成的。
  • BLAS 只是为那些常用的线性代数表达式定义了一个接口。
  • 对于 BLAS,存在不同的优化实现。

如果 BLAS 的范围不够用(例如,它没有提供类似 (3) 的功能),那么可以扩展 BLAS 的范围。所以这个 60 和 70 年代的恐龙用它的石器时代工具实现了界面和实现的干净和正交的分离。(大多数)数字 C++ 库没有达到这种软件质量水平,这有点有趣。尽管编程语言本身要复杂得多。因此,BLAS/LAPACK 仍然活跃并积极开发也就不足为奇了。

所以在我看来,外星人本身并不邪恶。但是它们在数字 C++ 库中的普遍使用方式在科学计算界赢得了非常糟糕的声誉。