C++ 模板参数作为函数说明符与编译器优化

C++ 模板参数作为函数说明符与编译器优化,c++,cuda,nvcc,C++,Cuda,Nvcc,我发现了一篇非常有用的帖子,我想澄清一些关于编译器优化的事情。假设我们有这个功能(和原来的帖子一样): 模板 __全局无效内核() { 开关(动作){ 案例1: //第一代码 打破 案例2: //第二代码 打破 } } 编译器是否会在消除无法访问的代码的意义上进行优化,即使在编译时我调用了模板变量未知的函数,比如创建两个separate函数?例如: kernel<argv[1][0]>(); kernel(); 简短回答:否 模板纯粹是在编译时实例化和生成的,因此不能使用argv

我发现了一篇非常有用的帖子,我想澄清一些关于编译器优化的事情。假设我们有这个功能(和原来的帖子一样):

模板
__全局无效内核()
{
开关(动作){
案例1:
//第一代码
打破
案例2:
//第二代码
打破
}
}
编译器是否会在消除无法访问的代码的意义上进行优化,即使在编译时我调用了模板变量未知的函数,比如创建两个separate函数?例如:

kernel<argv[1][0]>();
kernel();
简短回答:否

模板纯粹是在编译时实例化和生成的,因此不能使用argv中的值,因为它们在编译时是未知的

让我想知道,为什么您不只是尝试一下,然后将代码扔给编译器——它会告诉您模板参数必须是编译时常量

更新: 因为您在评论中告诉我们,它主要不是关于性能,而是关于可读性,所以我建议使用switch/case:

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

switch (argv[1][0]) {
  case 'a': 
    kernel<'a'>();
    break;
  case 'b': 
    kernel<'b'>();
    break;
  //...
}
template void kernel(){
//...
开关(c){/*…*/}
}
开关(argv[1][0]){
案例“a”:
内核();
打破
案例“b”:
内核();
打破
//...
}

由于必须进行描述的值(即
argv[1][0]
)仅在运行时已知,因此必须使用运行时描述机制。其中,switch/case是最快的,特别是如果不同的案例不太多(但多于两个),并且案例之间没有间隙(即“a”、“b”、“c”,而不是1、55、2048)。然后编译器可以生成非常快速的跳转表。

作为模板新手,我不得不研究一些基本问题。最后我想出了解决我问题的办法。如果我想根据命令行参数使用模板参数调用函数,我应该这样做:

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

if(argv[1][0] == '2')
    kernel<2><<< ... >>>();
if(argv[1][0]=“1”)
内核>();
如果(argv[1][0]=“2”)
内核>();

我还检查了此类程序的ptx文件,发现编译器在这种情况下进行优化,生成两个不同的内核函数,而不使用
switch
语句

那是行不通的。根据定义,模板参数值必须在编译时已知。我正在考虑重新组织代码,我想知道这种方法是否会导致性能下降。但感谢您回答了这个假问题。在大多数情况下,只有一个可以可靠地告诉您代码更改对性能的实际影响:探查器。为了避免重复的if语句,您可以将指向各种模板实例化的函数指针放入数组中,然后,只需通过数组中相应的函数指针调用所需的内核,该指针由相应的argv[]索引。@njuffa:Yes看起来很聪明。这只是一个例子,
switch
也是另一种方法,但我喜欢你的方法。使用switch/case的可读性要高得多,因为在数组和afaik中存储函数指针不太可能产生更多的开销-但是,我还是把决定权留给探查器,即实现这两个版本,看看哪一个更快。当然,只有在节目的那个角落里,表现真的很重要的时候,才能这样做。如果没有,请为您节省一些时间和精力,并以您认为最具可读性和可维护性的方式实现它。@ArneMertz使我的代码更具可读性和可维护性正是我想要做的,我很好奇这种改进是否会导致性能下降。nvcc的PTX输出显示我为“否”。好的,从问题中我假设性能是您最关心的-我更新了我的答案。
if(argv[1][0] == '1')
    kernel<1><<< ... >>>();

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