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


24

假设我正在使用C ++编写科学代码。在最近与一位同事的讨论中,有人争辩说表达模板可能是一件很糟糕的事情,有可能使软件只能在某些版本的gcc上编译。据推测,这个问题影响了一些科学规范,正如《堕落这个模仿中的字幕所提到的那样。(这些是我所知道的唯一示例,因此是链接。)

但是,其他人则认为,表达模板很有用,因为它们可以避免将中间结果存储在临时变量中,从而可以提高性能(如SIAM Journal of Scientific Computing的论文)

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


啊,视频太有趣了。我不知道它的存在。是谁做的,你知道吗?
Wolfgang Bangerth 2012年

不知道; 几个PETSc员工曾一次向我发送了链接。我认为FEniCS开发人员做到了。
Geoff Oxberry 2012年

视频链接断开了,我快要好奇了。新链接?
Praxeolitic

噢,天哪,没关系,我看到YouTube 来了我们的希特勒视频。
Praxeolitic

Answers:


17

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


是的,当我不得不将代码从gcc 2.95移植到gcc 4.x时,我亲眼目睹了一些冗长的错误消息,并且编译器抛出了有关模板的各种错误。我的一个实验室伙伴正在开发一个用于C ++中间隔算术的模板库(添加Boost :: Interval中没有的新功能以完成更多研究),我不想看到代码成为噩梦编译。
Geoff Oxberry 2012年

12

其他人则评论了编写ET程序有多困难以及理解错误消息的复杂性的问题。让我对编译器问题进行评论:确实,前一段时间存在的一个大问题是找到一个与C ++标准足够兼容的编译器,以使所有工作正常运行并使其可移植地工作。结果,我们发现了许多错误-我以我的名义有2-300个错误报告,分布在gcc,Intel icc,IBM xlC和Portland的pgicc上。因此,deal.II配置脚本是大量编译器错误测试的存储库,主要是在模板,朋友声明,名称空间等方面。

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

这意味着什么:如果您坚持使用这两个大型编译器,则可以期望它们在今天就可以使用。由于当今大多数计算机和操作系统通常至少具有其中之一,因此就足够了。唯一困难的平台是BlueGene,该系统的编译器通常是xlC,其中包含所有错误。


出于好奇,您是否尝试过在/ Q上针对新的xlc编译器进行编译?
阿隆·艾玛迪亚

不。我承认我已经放弃了xlC。
Wolfgang Bangerth 2012年

5

很久以前,我就对ET进行了试验,就像您提到的那样,编译器仍在与之抗争。我在我的一些代码中将blitz库用于线性代数。然后的问题是获得好的编译器,并且由于我不是一个完美的C ++程序员,所以会解释编译器错误消息。后者根本无法管理。编译器平均会产生大约1000行错误消息。我无法快速找到编程错误。

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

但是我会远离他们。


编译器错误消息确实是我关注的问题之一。使用我编译的一些模板化C ++代码以为我的项目构建库时,编译器可能会生成数百行警告消息。但是,它不是我的代码,我听不懂,并且通常来说,它可以工作,所以我不理会它。冗长,隐秘的错误消息对于调试而言并不理想。
Geoff Oxberry 2012年

4

问题已经从术语“表达模板(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仍然活着并得到积极发展。

因此,在我看来,ET本身并不是邪恶的。但是它们如何在数值C ++库中普遍使用,在科学计算界赢得了非常不好的声誉。


迈克尔,我认为您缺少表达模板的要点之一。您的代码示例(1)实际上并未映射到任何优化的BLAS调用。实际上,即使当存在BLAS例程时,BLAS函数调用的开销也使得它对于小的向量和矩阵而言相当糟糕。诸如Blaze和Eigen之类的复杂表达模板库可以使用延迟表达评估来避免使用临时变量,但是我坚信,几乎没有任何特定领域的语言能够击败手工线性代数。
阿隆·艾玛迪亚

不,我认为您错过了重点。您必须区分(a)BLAS作为一些经常需要的线性代数运算的规范(b)BLAS的实现,例如ATLAS,GotoBLAS等。BTW在FLENS中的工作方式: 默认情况下,像(1)这样的表达式通过从BLAS调用axpy三次来进行评估。但无需修改(1),我也可以像(2)中那样对其进行评估。因此,逻辑上将发生以下情况:如果(1)中的操作很重要,则可以扩展指定的BLAS操作(a)的集合。
Michael Lehn 2012年

因此,关键点是:应该分开使用诸如'v = x + y + z'之类的符号以及它最终如何最终被计算出来。Eigen,MTL,BLITZ,blaze-lib在这方面完全失败。
Michael Lehn 2012年

1
是的,但是经常需要的线性代数运算的数量是组合的。如果您要使用C ++之类的语言,则可以选择使用表达式模板按需实现(这是Eigen / Blaze方法),方法是使用延迟评估智能地组合子块和算法,或者实现大量每个可能例程的库。我不主张使用这两种方法,因为Numba和Cython的最新工作表明,使用Python等高级脚本语言可以获得类似或更好的性能。
Aron Ahmadia 2012年

但是,我再次抱怨的事实是,像Eigen这样的复杂库(在复杂但不灵活的意义上)紧密地将符号和评估机制结合在一起,甚至认为这是一件好事。如果我使用Matlab之类的工具,我只是想编写代码,并依靠Matlab尽力而为。如果我使用C ++之类的语言,那么我希望得到控制。因此,请注意是否存在默认的评估机制,但必须可以更改它。否则,我将返回并直接在C ++中调用函数。
Michael Lehn 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.