函数规范化的模板参数和编译器优化

3

我发现这篇文章非常有用,我想澄清一下编译器优化的问题。假设我们有这个函数(与原帖中相同):

template<int action>
__global__ void kernel()
{
    switch(action) {
       case 1:
       // First code
       break;

       case 2:
       // Second code
       break;
    }
}

即使在我使用编译时的未知模板变量调用函数的情况下,编译器是否会进行优化,以消除不可达代码 - 比如创建两个单独的函数?例如:

kernel<argv[1][0]>();

1
那绝对行不通。模板参数值根据定义必须在编译时知道。 - talonmies
2个回答

4

简短的回答:不行。

模板在编译时纯粹通过实例化和生成来完成,因此您无法使用argv中的值,因为它们在编译时是未知的。

让我想知道为什么您没有试着将该代码抛给编译器-它会告诉您模板参数必须是编译时常量。

更新: 既然您在评论中告诉我们这与性能无关,而是与可读性有关,我建议使用switch/case:

template <char c> void kernel() {
  //...
  switch(c) { /* ... */ }
}

switch (argv[1][0]) {
  case 'a': 
    kernel<'a'>();
    break;
  case 'b': 
    kernel<'b'>();
    break;
  //...
}

由于你需要做决策的值(即argv [1] [0])只在运行时才知道,因此必须使用运行时决策机制。其中,switch/case是最快的之一,特别是如果有不太多但超过两个不同的情况,并且特别是如果情况之间没有间隙(例如'a','b','c',而不是1,55,2048)。编译器可以产生非常快的跳转表。


我在考虑重新组织我的代码,想知道这样做是否会导致性能下降。但是,感谢您回答了这个愚蠢的问题。 - stuhlo
在大多数情况下,只有一个人能够可靠地告诉你代码更改的实际性能影响:性能分析器。 - Arne Mertz

0

由于我对模板不熟悉,我不得不学习一些基本问题。最终,我找到了解决问题的方法。如果我想根据命令行参数调用带有模板参数的函数,应该这样做:

if(argv[1][0] == '1')
    kernel<1><<< ... >>>();

if(argv[1][0] == '2')
    kernel<2><<< ... >>>();

我还查了一下这个程序的ptx文件,发现编译器在这种情况下进行了优化,生成了两个不带switch语句的内核函数。


1
为了避免重复的if语句,您可以将函数指针放入各种模板实例化中的数组中,然后通过数组中相应的函数指针调用所需的内核,由适当的argv[]索引。 - njuffa
@njuffa:是的,看起来很聪明。那只是一个例子,switch 也是另一种方式,但我喜欢你的方法。 - stuhlo
使用switch/case比在数组中存储函数指针要更易读,并且据我所知不太可能产生更多的开销 - 但是,我会把决定留给分析器,即实现两个版本并查看哪个更快。当然,这仅适用于程序的某个角落真正需要性能的情况。如果不需要,节省时间和精力,只需按照您认为最易读和可维护的方式实现即可。 - Arne Mertz
@ArneMertz 使我的代码更易读和可维护正是我想要做的,我很好奇这种改进是否会导致性能下降。nvcc的PTX输出向我展示了“不会”。 - stuhlo
好的,从问题中我推断出性能是您的主要关注点 - 我已更新我的答案。 - Arne Mertz
@ArneMertz 我特别关注GPU代码(内核函数)的性能,因为它可能会被多次调用,所以我想知道即使在我使用取决于命令行参数的模板参数调用此函数的情况下,编译器是否会对此函数进行优化。考虑到我、你和njuffa上面提到的方法,现在我很自然地认为编译器会进行这些优化,但我之前并不知道。 - stuhlo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接