【问题标题】:Profiling Expression Template分析表达式模板
【发布时间】:2015-01-29 17:50:43
【问题描述】:

我正在尝试分析类似于 David Vandevoorde 的“C++ 模板”一书中的表达式模板。下面是我的理论分析,这可能是错误的,因为测试显示了意想不到的结果。 假设测试是关于:

R = A + B + C;

其中 A、B、C、R 是在堆上分配的数组。数组的大小是2,所以会执行如下:

R[0] = A[0] + B[0] + C[0]; // 3 loads + 2 additions + 1 store
R[1] = A[1] + B[1] + C[1];

大约有 12 条指令(每条 6 条)。

现在,如果启用了表达式模板(显示在最底部),在编译时进行类型推导后,将在运行时处理以下内容,然后执行与上述相同的评估:

A + B --> expression 1 // copy references to A & B 
expression 1 + C --> expression 2 // copy the copies of references to A & B
                                  // + copy reference to C

因此,在评估之前总共有 2+3=5 条指令,约占总指令的 5/(5+12)=30%。所以我应该能够看到这种开销,尤其是当向量大小很小的时候。

但结果表明两者的成本几乎相同。我将测试迭代 1E+09 次。当然,两者的汇编代码是相同的。但是我找不到这个“构造”部分需要花费任何时间或说明的部分。

movsdq  (%r9,%rax,8), %xmm0
addsdq  (%r8,%rax,8), %xmm0
addsdq  (%rdi,%rax,8), %xmm0
movsdq  %xmm0, (%rcx,%rax,8)

我没有良好的CS背景,所以这个问题可能很愚蠢。但是这几天我一直在摸不着头脑。因此,任何帮助都是值得的!

--- 我的表达模板---

template< typename Left, typename Right >
class V_p_W // stands for V+W
{
public:
   typedef typename array_type::value_type             value_type;
   typedef double                                      S_type;
   typedef typename traits< Left >::type               V_type;
   typedef typename traits< Right >::type              W_type;

   V_p_W ( const Left& _v, const Right& _w ) : V(_v), W(_w)
   {}

   inline value_type operator [] ( std::size_t i )        { return V[i] + W[i]; }
   inline value_type operator [] ( std::size_t i ) const  { return V[i] + W[i]; }
   inline std::size_t size () const                       { return V.size();  }

private:
   V_type V;
   W_type W;
};

其中 traits 什么都不做,只是决定是否应该采用对象的引用值。例如,值是为整数复制的,但引用是为数组获取的。

【问题讨论】:

  • 我可能遗漏了一些东西,但是您所指的“the”表达式模板是什么?您似乎在假设某个库,但除非我们知道哪个,否则任何推测都是完全武断的。尽管如此,如果你将表达式模板分配给一个实际值(R 应该是堆上的一个数组),它当然必须被解析......
  • @gha.st 我的意思是经典的表达式模板,类似于《C++模板》一书中的那个。我会更新我的问题。谢谢!顺便说一句,您所说的“已解决”是什么意思?
  • 需要实际执行的操作。要获得正确的R[0],您需要执行A[0] + B[0] + C[0],如果您只有两个操作数加法,那么两个加法是唯一合理的方法!坦率地说,这些添加的发生方式最起码如何并不重要。
  • 不过,如果你想要任何明智的答案,你需要给出一个完整的例子,其他人可以实际编译和查看。
  • 天啊,你的意思是经典的表达模板?没听说过。

标签: c++ performance templates profiling expression-templates


【解决方案1】:

我的自制测试。理想情况下,表达式模板在幼稚的情况下节省了临时向量所需的额外分配。

expr.cpp:

#include <vector>
#include <stdlib.h>
#include <iostream>
#include <ctime>

using namespace std;

typedef vector<int> Valarray;

template<typename L, typename R>
struct BinOpPlus {
  const L& left;
  const R& right;

  BinOpPlus(const L& l, const R& r)
    : left(l), right(r)
  {}

  int operator[](int i) const { return left[i] + right[i]; }
};

template<typename L, typename R>
BinOpPlus<L, R> operator+(const L& left, const R& right){
  return BinOpPlus<L, R>(left, right);
}

int main() {
  int size = 10000000;
  Valarray v[3];
  for(int n=0; n<3; ++n){
    for(int i=0; i<size; ++i){
      int val = rand() % 100;
      v[n].push_back(val);
    }
  }

  std::clock_t start = std::clock();
  auto tmp = v[0] + v[1];
  auto out = tmp + v[2];

  int sum = 0;
  for(int i=0; i<size; ++i)
    sum += out[i];

  std::clock_t stop = std::clock();
  cout << "Sum: " << sum << endl;
  cout << "Time: " << (stop-start) << endl;
  return 0;
}

vala.cpp:

#include <vector>
#include <stdlib.h>
#include <iostream>
#include <ctime>

using namespace std;

class Valarray : public vector<int> {
  public:
    Valarray operator+(const Valarray& r) const {
      Valarray out;
      out.reserve(r.size());
      for(size_t i=0; i<r.size(); ++i)
        out.push_back((*this)[i] + r[i]);
      return out;
    }

    Valarray operator+(Valarray&& r) const {
      for(size_t i=0; i<r.size(); ++i)
        r[i] = (*this)[i] + r[i];
      return r;
    }
};

int main() {
  int size = 10000000;
  Valarray v[3];
  for(int n=0; n<3; ++n){
    for(int i=0; i<size; ++i){
      int val = rand() % 100;
      v[n].push_back(val);
    }
  }

  std::clock_t start = std::clock();
  Valarray out = v[0] + v[1] + v[2];

  int sum = 0;
  for(int i=0; i<size; ++i)
    sum += out[i];

  std::clock_t stop = std::clock();
  cout << "Sum: " << sum << endl;
  cout << "Time: " << (stop-start) << endl;
  return 0;
}

命令行:

g++ -Wfatal-errors -std=c++11 -Wall -Werror vala.cpp -o vala
g++ -Wfatal-errors -std=c++11 -Wall -Werror expr.cpp -o expr
~/example$ ./vala
Sum: 1485274472
Time: 680000
~/example$ ./expr
Sum: 1485274472
Time: 130000

优化:

g++ -Wfatal-errors -std=c++11 -Wall -Werror vala.cpp -o vala -O3
g++ -Wfatal-errors -std=c++11 -Wall -Werror expr.cpp -o expr -O3
na:~/example$ ./vala
Sum: 1485274472
Time: 290000
na:~/example$ ./expr
Sum: 1485274472
Time: 10000

大幅改进了表达式模板,因为它避免了额外的向量分配。

【讨论】: