我已经在网上看到过几次,有人提到使用模板可以让C++变得更快。
有没有人能解释一下,包括在低层次上,这到底是为什么?我一直认为,像大多数有用的概念一样,这样一个“好”的特性也会有开销。
从超低延迟的角度来看,我真的对此很感兴趣!
发布于 2012-01-19 19:55:01
排序就是一个常见的例子。
在C中,qsort接受一个指向比较函数的指针。一般来说,会有一个qsort代码的副本,它不是内联的。它将通过指针调用比较例程--当然这也不是内联的。
在C++中,std::sort是一个模板,它可以接受一个函数器对象作为比较器。对于用作比较器的每种不同类型,都有不同的std::sort副本。假设您使用一个带有重载operator()的函数器类,那么对比较器的调用可以很容易地内联到这个std::sort副本中。
因此,模板为您提供了更多的内联,因为有更多的sort代码副本,每个副本可以内联一个不同的比较器。内联是一种非常好的优化,排序例程会进行大量比较,因此您通常可以测量出std::sort的运行速度比等效的qsort更快。这样做的代价是有可能产生更大的代码--如果您的程序使用了许多不同的比较器,那么您会得到许多不同的排序例程副本,每个副本中都包含一个不同的比较器。
原则上,C实现没有理由不能将qsort内联到它被调用的地方。然后,如果它是用函数的名称调用的,优化器在理论上可以观察到,在使用它的时候,函数指针仍然必须指向同一个函数。然后,它可以内联对函数的调用,结果将类似于使用std::sort得到的结果。但在实践中,编译器往往不会迈出内联qsort的第一步。这是因为(a)它很大,(b)它在不同的翻译单元中,通常编译到你的程序所链接的某个库中,(c)要这样做,每次调用它都有一个qsort的内联副本,而不是每个不同的比较器都有一个副本。因此,它将比C++更臃肿,除非实现还可以找到一种方法,在使用相同的比较器在不同位置调用qsort的情况下统一代码。
因此,像C中的qsort这样的通用函数往往会有一些开销,因为通过函数指针调用或其他间接调用*。C++中的模板是一种常见的方法,用于保持源代码的泛型,但确保将其编译为特殊用途的函数(或几个这样的函数)。特殊用途的代码有望更快。
值得注意的是,模板并不仅仅与性能有关。在某些方面,std::sort本身比qsort更通用。例如,qsort只对数组进行排序,而std::sort可以对提供随机访问迭代器的任何内容进行排序。例如,它可以对一个deque进行排序,在幕后是几个分开分配的不相交的数组。因此,使用模板并不一定会带来任何性能上的好处,这可能是出于其他原因。恰好模板确实会影响性能。
*使用排序的另一个示例- qsort使用一个整数参数来说明数组的每个元素有多大,因此当它移动元素时,它必须调用memcpy或与此变量的值类似的值。std::sort在编译时知道元素的确切类型,因此知道确切的大小。它可以内联复制构造函数调用,该调用又可以转换为复制该字节数的指令。与内联比较器一样,通过调用复制可变字节数的例程,并向其传递值4(或8、16或其他值),通常可以更快地精确复制4(或8、16或其他)字节。与前面一样,如果使用字面值调用qsort,并且对qsort的调用是内联的,那么编译器可以用C语言执行完全相同的优化,但在实践中您看不到这一点。
发布于 2012-01-19 19:50:47
“更快”取决于你把它和什么做比较。
模板完全由编译器计算,因此它们在运行时的开销为零。调用Foo<int>()的效率与调用FooInt()的效率完全相同。
因此,与依赖于在运行时完成更多工作的方法相比,例如通过调用虚拟函数,模板确实可以更快。与专门为该场景编写的手写代码相比,没有任何区别。
因此,模板的好处不在于它们比其他方法“更快”,而在于它们和手写代码一样“快”,同时也是通用的和可重用的。
发布于 2012-01-19 19:57:52
使用模板提高运行时性能的另一个值得注意的例子是Blitz++数值库。它率先使用了所谓的expression templates,使用编译时逻辑将涉及大向量和矩阵的算术表达式转换为更易于编译为高效机器码的等价表达式。例如,给定以下伪代码:
vector<1000> a = foo(), b = bar(), c = baz(), result;
result = a + b + c;一种简单的方法是将a和b的每个元素相加,将结果存储在一个临时向量中,然后对c执行相同的操作,最后将结果复制到result中。使用表达式模板魔术,生成的代码将等同于以下代码:
for(int i = 0; i < 1000; ++i) {
result[i] = a[i] + b[i] + c[i];
}这要快得多,更好地利用了缓存局部性,并在此过程中避免了不必要的临时操作。它还避免了别名问题,即编译器无法证明两个指针指向不同的内存区域,从而迫使它生成非最佳代码。表达式模板现在通常用于高性能数字,以及其他不涉及性能的用途,如Boost.Spirit解析库。
https://stackoverflow.com/questions/8925177
复制相似问题