·在C++中使用Lambda函数提高代码性能

商帝国咨询头像

阅读 15677 | 分类:文章

使编译器以及操作系统从正在创建的应用中榨取更高性能的关键在于提供充足的有关代码意图的信息。在充分了解这个代码意图实现的功能等信息的情况下, 就有可能将代码在编译时和运行时的并行吞吐量最大化,令开发者可以将更多精力放在他们所关注的商业领域的问题,将重量级的多核多处理器的任务计划交托给编 译器,运行时库以及操作系统中的基础设施代码来处理。
  循环函数是很重要的一个环节,因为在所有可用的硬件资源中,被分离的循环中的各个部分在一般情况下能够提供更高的应用性能。考虑这样一个小情况:迭代选定组合中的全部元素以求得总和。最简单最直接的执行方法如下:

  std::vector<int> v;
  v.push_back(1);
  v.push_back(5);
  int total = 0;
  for (int ix = 0; ix < v.size(); ++ix){
  total += v[ix];
  }

  以上的例子十分便于人工读写。对于熟悉C语言家族语法的开发者而言,这个循环的意图也十分容易理解。然而对于编译器以及运行时库的组合而言,要在多个线程之间计划好这个循环,它还需要类似于OpenMP编译指示一类的指示来告诉它哪里有优化的空间:

  std::vector<int> v;
  v.push_back(1);
  v.push_back(5);
  int total = 0;
  #pragma omp for
  for (int ix = 0; ix < v.size(); ++ix){
  #pragma omp atomic
  total += v[ix];
  }

  第一个OpenMP指示提出了多线程运行for循环的要求,而第二个omp atomic指示则被用来防止多线程同时向总数变量上写入。对于OpenMP,在MSDN库的参考文档中有关于所有指示的详细介绍。

  如果使用了声明式循环技巧,那么将并行方法应用在矢量求和上则更加干净简单。STL for_each函数是一个理想的替代品,以上的例子则被改写如下:

  class Adder{
  private:
  int _total;
  public:
  Adder() : _total(0) {}

  void operator ( ) ( int& i )
  {
    _total += i;
  }

  operator int ( )
  {
    return  _total;
  }
  };

  void VectorAdd()
  {
  std::vector<int> v;
  v.push_back(1);
  v.push_back(5);
  int total = std::for_each(v.begin(), v.end(), Adder());
  }

  这里,具体的for循环被舍弃,求矢量和的代码变得干净了一些;但是由于需要使用一系列运行符来定义一个类,这使得这个解决方案被大大的复杂化了。 除非代码库中还有大量类似的求和声明,否则一个开发者是不会仅仅为了STL for_each的那点好处而多花费功夫去定义一个新类的。

  仔细检查这个Adder类,可以很明显的看出其大部分内容都仅仅是用来满足将实例用作函数对象的调用条件的。这个类中唯一起到计算作用的仅仅是那一 行_total += i。考虑到这一点,C++ 0x提供了一个被大大简化了的、以lambda函数方式来实现的语法技巧。Lambda函数移除了对这些搭架子代码的需求,并允许在另外的一个声明中定义 一个谓词函数。由此,VectorAdd函数可以被改写如下:

  std::vector<int> v;
  v.push_back(1);
  v.push_back(5);
  int total = 0;
  std::for_each(v.begin(), v.end(),
  [&total](int x) {total += x;}
  );

  Lambda函数的语法相当直截了当。方括号中的第一个lambda元素告诉编译器,本地变量total通过引用被捕捉(这样的情况下最好用引用捕 捉,因为你需要矢量和的结果在for_each之后仍然有效),而lambda的第二部分则是参数列表。Lambda的最后一部分是函数的主体,这个例子 中就是将参数x的值加到变量total中去。

  如果在lambda函数中没有需要捕捉的变量,或者只需要捕捉变量的一个副本,那么函数开始的方括号可以留空:

  std::for_each(v.begin(), v.end(), [](int x) {
  std::cout << x << std::endl;
  });

  混合的捕捉方法也可以使用:

  int total = 0;
  bool displayInput = true;
  std::for_each(v.begin(), v.end(), [&total, displayInput](int x) {
  total += x;
  if (displayInput){
    std::cout << x << std::endl;
  }
  });

  这里,变量displayInput通过副本被捕捉。Visual C++编译器在编译时会报错C3491:''displayInput'':一个在lambda函数内数值被改变的变量无法在一个非可变lambda中通过数值被捕捉。

留言区(共0条)

暂无留言,快来抢占沙发~
作者声明:非原创
平台声明:文章及评论仅代表作者本人观点,五略商书系信息发布平台,仅提供信息存储空间服务,如有侵权,请联系13521509118。转载请注明作者和出处:转自五略商书m.shangdiguo.com,作者商帝国咨询!