模板元编程是否比同等的C代码更快?

时间:2009-07-25 18:22:00

标签: c++ c language-agnostic templates metaprogramming

模板元编程是否比同等的C代码更快? (我说的是运行时性能):)

8 个答案:

答案 0 :(得分:27)

首先,免责声明: 我所想的认为不仅仅是模板元编程,还包括通用编程。这两个概念密切相关,并没有确切定义每个概念。但简而言之,模板元编程实质上是使用模板编写程序,这些模板在编译时进行评估。 (这使得它在运行时完全免费。没有任何反应。值(或更常见的类型)已经由编译器计算,并且可用作常量(const变量或枚举)或typedef嵌套在一个类中(如果你用它来“计算”一个类型)。

通用编程使用模板,并在必要时使用模板元编程,创建通用代码,它们具有相同的功能(并且没有性能损失),具有全部和任何类型。我将在下面使用两者的例子。

模板元编程的一个常见用途是使类型能够用于泛型编程,即使它们不是为它设计的。

由于模板元编程技术上完全是在编译时进行的,所以你的问题与泛型编程有点相关,它仍然在运行时发生,但是很有效,因为它可以专门用于在编译时使用的精确类型-time。

总之...


取决于您如何定义“等效C代码”。

关于模板元编程(或一般的通用编程)的技巧是它允许将大量计算移动到编译时,并且它使得灵活的参数化代码能够像硬编码值一样高效。

显示的代码here例如在编译时计算斐波纳契序列中的数字。

C ++代码“unsigned long fib11 = fibonacci<11uL>::value”依赖于该链接中定义的模板元程序,并且与C代码“unsigned long fib11 = 89uL”一样高效。模板在编译时进行评估,产生可分配给变量的常量。所以在运行时,代码实际上与简单的赋值相同。

因此,如果这是“等效C代码”,则性能是相同的。 如果等效的C代码是“一个可以计算任意斐波纳契数的程序,用于查找序列中的第11个数字”,则C版本会慢得多,因为它必须作为函数实现,它计算值在运行时。但这是“等效C代码”,因为它是一个C程序,具有相同的灵活性(它不仅仅是一个硬编码常量,而是一个可以返回任何数字的实际函数。斐波纳契序列)。

当然,这通常不常用。但它几乎是模板元编程的典型例子。

通用编程的一个更现实的例子是排序。

在C中,你有qsort标准库函数,它带有一个数组和一个比较器函数指针。无法内联对此函数指针的调用(除非在普通情况下),因为在编译时,不知道将调用哪个函数。

当然,替代方案是为您的特定数据类型设计的手写排序功能。

在C ++中,等效的是函数模板std::sort。它也需要一个比较器,但它不是一个函数指针,而是一个函数对象,如下所示:

struct MyComp {
  bool operator()(const MyType& lhs, const MyType& rhs) {
     // return true if lhs < rhs, however this operation is defined for MyType objects
  }
};

这可以内联。 std::sort函数传递一个模板参数,因此它知道比较器的确切类型,因此它知道比较器函数不仅仅是一个未知的函数指针,而是MyComp::operator()

最终结果是C ++函数std::sort 完全与在同一排序算法的C中的手动编码实现一样高效。

再次,如果那是“等效的C代码”,那么性能是相同的。 但是,如果“等效C代码”是“可以应用于任何类型的通用排序函数,并允许用户定义的比较器”,那么C ++中的通用编程版本效率要高得多。

这才是真正的伎俩。通用编程和模板元编程不是“比C更快”。它们是实现通用,可重用代码的方法,其速度与手动编码一样快,并且硬编码C

这是一种充分利用这两个世界的方法。硬编码算法的性能,以及一般参数化算法的灵活性和可重用性。

答案 1 :(得分:11)

模板元编程(TMP)在编译时是“运行”的,所以在将它与普通的C / C ++代码进行比较时,并不是真的比较苹果和苹果。

但是,如果你有TMP评估的东西,那么根本没有运行时成本。

答案 2 :(得分:4)

如果您的意思是可重复使用的代码,那么毫无疑问是的。元编程是生成库的一种优越方式,而不是客户端代码。客户端代码不是通用的,它是为了做特定的事情而编写的。

例如,查看C标准库中的 qsort 和C ++标准排序。这就是 qsort 的工作原理:

int compare(const void* a, const void* b)
{
    return (*(int*)a > *(int*)b);
}

int main()
{
    int data[5] = {5, 4, 3, 2, 1};

    qsort(data, 5, sizeof(int), compare);
}

现在查看排序

struct compare
{
    bool operator()(int a, int b)
    { return a < b; }
};

int main()
{
    int data[5] = {5, 4, 3, 2, 1};
    std::sort(data, data+5, compare());
}

排序更干净,更安全,更强高效,因为比较功能在排序中内联。在我看来,这是元编程的好处, 你编写通用代码 ,但 编译器生成的代码就像手工编写的那样


我发现元编程非常漂亮的另一个地方是当你编写像boost :: spirit或boost :: xpressive这样的库时,使用 spirit 你可以在C ++中编写EBNF并让编译检查EBNF您和 xpressive 的语法,您可以编写正则表达式,让编译器也为您检查正则表达式语法!


我不确定提问者是否通过TMP在编译时计算值。这是我用boost写的一个例子:)

unsigned long greatestCommonDivisor = boost::math::static_gcd<25657, 54887524>::value;

你用C做什么你不能模仿上面的代码,基本上你必须手工计算它然后将结果分配给 greatCommonDivisor

答案 3 :(得分:1)

答案取决于它。

模板元编程可以用来轻松编写递归下降语言解析器,与精心设计的C程序或基于表的实现(例如flex / bison / yacc)相比,这些解析器效率低下。

另一方面,您可以编写生成展开循环的元程序,这可能比使用循环的传统C实现更有效。

主要的好处是元程序允许程序员用更少的代码完成更多工作。

缺点是它还会给你一把枪,用脚射击自己。

答案 4 :(得分:1)

模板元编程可以被认为是编译时执行。

编译时编译代码需要更长的时间,因为它必须编译然后执行模板,生成代码,然后重新编译。

运行时开销我不确定,它应该不会比你自己用C代码自己编写的那样多。

答案 5 :(得分:1)

我参与了另一个程序员尝试过元编程的项目。太可怕了。这是一个彻头彻尾的头痛。我是一名具有大量C ++经验的普通程序员,并试图设计他们想要做的事情所花费的时间比他们直接写出来的时间要多。

由于这种经历,我对C ++ MetaProgramming感到厌倦。

我坚信最好的代码最容易被普通开发人员阅读。这是第一优先级的软件的可读性。我可以使用任何语言使任何工作......但技能是使其可读并且对于项目中的下一个人而言可以轻松地工作。 C ++ MetaProgramming无法通过审核。

答案 6 :(得分:0)

我认为没有任何宣传,但C ++ FAQ给出了关于模板的清晰简单的答案:https://isocpp.org/wiki/faq/templates#overview-templates

关于原始问题:无法回答,因为这些事情无法比较。

答案 7 :(得分:0)

模板元编程在性能方面没有给你任何神奇的力量。它基本上是一个非常复杂的预处理器;你总是可以用C或C ++编写等价物,它可能需要你很长时间。