使用模板函数生成的代码与普通函数之间的差异

时间:2009-08-19 11:19:58

标签: c++ compiler-construction templates code-generation

我有一个包含大量元素的向量。现在我想编写一个小函数来计算向量中偶数或奇数元素的数量。由于性能是一个主要问题,我不想在循环中放入if语句。所以我写了两个小函数,如:

long long countOdd(const std::vector<int>& v)
{
    long long count = 0;
    const int size = v.size();
    for(int i = 0; i < size; ++i)
    {
        if(v[i] & 1)
        {
            ++count;
        }
    }
    return count;
}

long long countEven(const std::vector<int>& v)
{
    long long count = 0;
    const int size = v.size();
    for(int i = 0; i < size; ++i)
    {
         if(0 == (v[i] & 1))
        {
            ++count;
        }
    }
    return count;
}

我的问题是,我可以通过编写这样的单个模板函数来获得相同的结果:

template <bool countEven>
long long countTemplate(const std::vector<int>& v1)
{
    long long count = 0;
    const int size = v1.size();
    for(int i = 0; i < size; ++i)
    {
        if(countEven)
        {
            if(v1[i] & 1)
            {
                ++count;
            }
        }
        else if(0 == (v1[i] & 1))
        {
            ++count;
        }
    }
    return count;
}

并像这样使用它:

int main()
{
  if(somecondition)
  {
     countTemplate<true>(vec); //Count even
  }      
  else
  {
     countTemplate<false>(vec); //Count odd
  } 
}

为模板和非模板版本生成的代码是否相同?或者会发出一些额外的指示吗?

请注意,数字的计算仅用于说明,因此请不要建议其他计数方法。

修改: 好。我同意从性能的角度来看它可能没什么意义。但至少从可维护性的角度来看,我希望只有一个函数来维护而不是两个。

10 个答案:

答案 0 :(得分:10)

模板化版本可能,很可能 将在编译器看到代码中的某个分支从未到达时进行优化。例如,countTemplate代码将countEven模板参数设置为true,因此奇数分支将被删除。

(对不起,我不禁建议另一种计算方法)

在这种特殊情况下,您可以在向量上使用count_if

struct odd { bool operator()( int i )const { return i&1; } };
size_t nbOdd = std::count_if( vec.begin(), vec.end(), odd() );

这也可以进行优化,并且编写方式更短:)标准库开发人员已经考虑了可能的优化,所以最好尽可能使用它,而不是编写自己的计数for循环。

答案 1 :(得分:6)

您的模板版本将生成如下代码:

template <>
long long countTemplate<true>(const std::vector<int>& v1)
{
    long long count = 0;
    const int size = v1.size();
    for(int i = 0; i < size; ++i)
    {
        if(true)
        {
                if(v1[i] & 1)
                {
                        ++count;
                }
        }
        else if(0 == (v1[i] & 1))
        {
                ++count;
        }
    }
    return count;
}


template <>
long long countTemplate<false>(const std::vector<int>& v1)
{
    long long count = 0;
    const int size = v1.size();
    for(int i = 0; i < size; ++i)
    {
        if(false)
        {
                if(v1[i] & 1)
                {
                        ++count;
                }
        }
        else if(0 == (v1[i] & 1))
        {
                ++count;
        }
    }
    return count;
}

因此,如果所有优化都被禁用,理论上if仍将存在。但即使是非常天真的编译器也会确定您正在测试常量,只需删除if

所以在实践中,不,生成的代码应该没有区别。所以你可以使用模板版本而不用担心这个。

答案 2 :(得分:2)

我想好的编译器会删除模板中的冗余代码,因为countEven是编译时常量,在模板实例化期间实现这样的优化非常简单。

无论如何,这似乎很奇怪。你写了一个模板,但在里面做了“动态切换”。 可以试试这样的事情:

struct CountEven {}
struct CountOdd {}

inline void CountNum(int & num, long long & count, const CountEven &)
{
   if(num & 1)
   {
      ++count;
   }
}

inline void CountNum(int & num, long long & count, const CountOdd &)
{
   if(0 == (num & 1))
   {
      ++count;
   }
}


template <class T>
long long countTemplate(const std::vector<int>& v1)
{
    long long count = 0;
    const int size = v1.size();
    for(int i = 0; i < size; ++i)
    {
        CountNum(v1[i], count, T());
    }
    return count;
}

它将在编译阶段选择必要的CountNum()函数版本:

int main()
{
  if(somecondition)
  {
     countTemplate<CountEven>(vec); //Count even
  }      
  else
  {
     countTemplate<CountOdd>(vec); //Count odd
  } 
}

代码很混乱,但我认为你有这个想法。

答案 3 :(得分:1)

这取决于编译器优化器的智能程度。编译器可能会看到if语句真的是冗余的,只执行了它的一个分支并优化了整个事件。

检查的最佳方法是尝试查看程序集 - 此代码不会产生太多的机器代码。

答案 4 :(得分:1)

我想到的第一件事就是两个优化“规则”:

  • 不要过早地选择。
  • 不要这样做。

关键是有时我们会担心在实践中永远不会发生的性能瓶颈。有研究表明,20%的代码负责80%的软件执行时间。当然这并不意味着你过早地悲观,但我认为这不是你的情况。

一般情况下,您应该只在之后执行此类优化操作您实际在程序中运行了一个分析器并确定了真正的瓶颈。

关于你的函数版本,正如其他人所说,这取决于你的编译器。请记住,使用模板方法,您将无法在运行时切换调用(模板是编译时工具)。

最后一点:long long不是标准的C ++(还)。

答案 5 :(得分:1)

如果您关心优化问题,请尝试按以下方式进行操作:

template <bool countEven>
long long countTemplate(const std::vector<int>& v1)
{
    long long count = 0;
    const int size = v1.size();
    for ( int i = 0; i < size; ++i ) {
      // According to C++ Standard 4.5/4: 
      // An rvalue of type bool can be converted to an rvalue of type int, 
      // with false becoming zero and true becoming one.
      if ( v1[i] & 1 == countEven ) ++count;
    }
    return count;
}

我相信上面的代码将使用与没有模板相同的代码编译。

答案 6 :(得分:1)

使用STL,Luke :-)它甚至在reference

中也是如此
bool isOdd(int i)
{
    return i%2==1;
}

bool isEven(int i)
{
    return i%2==0;
}

std::vector<int>::size_type count = 0;
if(somecondition)
{
    count = std::count_if(vec.begin(), vec.end(), isEven);
}
else 
{
    count = std::count_if(vec.begin(), vec.end(), isOdd);
}

答案 7 :(得分:0)

一般来说,结果会大致相同。您正在描述向量的线性内存上的O(n)迭代。

如果你有一个指针向量,突然性能会更差,因为引用的内存位置会丢失。

然而,更普遍的是,即使是上网本CPU也可以每秒进行一系列操作。循环遍历数组最不可能是性能关键代码。

您应该编写可读性,然后对代码进行分析,并在分析突出显示您遇到的任何性能问题的根本原因时,考虑做更多涉及手动调整的事情。

性能提升通常来自算法变化;如果您在向量中添加和删除元素时保留了赔率数,例如,将检索O(1)...

答案 8 :(得分:0)

我看到你正在使用 long long 作为计数器,这可能意味着你期望向量中有大量的元素。在那种情况下,我肯定会去模板实现(因为代码可读性),只要移动那个条件在for循环外。

如果我们假设编译器没有进行任何优化,那么你将有1个条件,并且可能通过向量进行超过20亿次迭代。此外,由于条件为 if(true) if(false),分支预测将完美地工作,并且执行将少于1个CPU指令。

我很确定市场上的所有编译器都有这种优化,但在性能方面我会引用我最喜欢的:“过早优化是所有邪恶的根源”“只有3条优化规则:衡量,衡量和衡量”

答案 9 :(得分:0)

如果你绝对荒谬地关心快速代码:

(一个聪明的编译器,或其他暗示使用指令或内在函数的人,可以使用SIMD并行执行此操作; CUDA和OpenCL当然会在早餐时吃这个!)

int count_odd(const int* array,size_t len) {
   int count = 0;
   const int* const sentinal = array+len;
   while(array<sentinal)
      count += (*array++ & 1);
   return count;
}

int count_even(const int* array,size_t len) {
   return len-count_odd(array,len);
}