代码之家  ›  专栏  ›  技术社区  ›  Naveen

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

  •  3
  • Naveen  · 技术社区  · 15 年前

    我有一个包含大量元素的向量。现在我想写一个小函数,它计算向量中偶数或奇数元素的数目。因为性能是一个主要问题,所以我不想在循环中放入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 回复  |  直到 15 年前
        1
  •  6
  •   Stack Overflow is garbage    15 年前

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

    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 理论上还是会有的。但即使是非常幼稚的编译器也会确定您正在测试一个常量,并简单地删除 如果 .

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

        2
  •  10
  •   xtofl Adam Rosenfield    15 年前

    模板化版本 可以 很可能, 当编译器在代码中看到某个分支时,它会对其进行优化。这个 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() );
    

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

        3
  •  2
  •   bocco    15 年前

    我想好的编译器会把模板中多余的代码作为 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
      } 
    }
    

    代码是凌乱的,但我想你明白了。

        4
  •  1
  •   sharptooth    15 年前

    这将取决于编译器优化器的智能程度。编译器可能会看到实际上if语句是多余的,并且只执行其中的一个分支并优化整个语句。

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

        5
  •  1
  •   Leandro T. C. Melo    15 年前

    我首先想到的是两个优化“规则”:

    • 不要过早选择。
    • 现在不要这样做。

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

    一般来说,你只应该做这种选择 之后 实际上,您已经在程序上运行了一个分析器,并识别出了真正的瓶颈。

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

    最后一点:长隆不是标准C++(还)。

        6
  •  1
  •   Kirill V. Lyadvinsky    15 年前

    如果您关心优化问题,请尝试如下操作:

    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;
    }
    

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

        7
  •  1
  •   Tadeusz Kopec for Ukraine yespbs    15 年前

    使用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);
    }
    
        8
  •  0
  •   Will    15 年前

    总的来说,结果将大同小异。您描述的是向量线性内存上的O(N)迭代。

    如果您有一个指针向量,那么性能会突然变得更糟,因为引用的内存位置会丢失。

    然而,更一般的事情是,即使是上网本CPU也可以每秒执行大量操作。在阵列上循环最不可能是性能关键的代码。

    您应该为可读性而写,然后对代码进行概要分析,并考虑在概要分析突出显示任何性能问题的根本原因时进行更复杂的手工调整。

    而性能的提高通常来自于算法的改变;例如,如果在从向量中添加和删除元素时保持对优势数的计数,那么检索……

        9
  •  0
  •   Aleksandar    15 年前

    我看到你在用 长长 对于counter,这可能意味着您期望向量中有大量元素。在这种情况下,我肯定会使用模板实现(因为代码可读性),并将if条件移出for循环。

    如果我们假设编译器不进行任何优化,那么您将有1个条件,并且可能有超过20亿次的向量迭代。另外,因为条件是 如果(真) 如果(假) 分支预测可以很好地工作,并且执行将少于1条CPU指令。

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

        10
  •  0
  •   Will    15 年前

    如果你真的很荒谬的关心 代码:

    (一个聪明的编译器,或者一个暗示使用指令或内部函数的编译器,可以使用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);
    }