提问者:小点点

现代C能让你免费表演吗?


有时有人声称,即使仅仅编译C 98代码,C 11/14也能提高性能。理由通常是沿着移动语义学的思路,因为在某些情况下,右值构造函数是自动生成的,或者现在是STL的一部分。现在我想知道这些情况以前是否真的已经由RVO或类似的编译器优化处理了。

我的问题是,如果你能给我一个实际的例子,一段C 98代码,无需修改,使用支持新语言特性的编译器运行得更快。我确实理解标准的编译器不需要做复制省略,正因为如此,移动语义学可能会带来速度,但如果你愿意,我希望看到一个不那么病态的案例。

编辑:为了清楚起见,我不是在问新编译器是否比旧编译器更快,而是如果有代码将-std=c 14添加到我的编译器标志中,它会运行得更快(避免复制,但如果你能想出除了移动语义学之外的其他东西,我也会感兴趣)


共2个答案

匿名用户

我知道将C 03编译器重新编译为C 11会导致无限的性能提升,这实际上与实现质量无关。这些都是移动语义学的变体。

struct bar{
  std::vector<int> data;
};
std::vector<bar> foo(1);
foo.back().data.push_back(3);
foo.reserve(10); // two allocations and a delete occur in C++03

每次在C 03中重新分配foo的缓冲区时,它都会复制bar中的每个向量

在C 11中,它将栏移动::datas,这基本上是免费的。

在这种情况下,这依赖于std容器向量内部的优化。在下面的每种情况下,使用std容器只是因为它们是C对象,当您升级编译器时,它们在C 11中“自动”具有高效的移动语义学。包含std容器的不阻塞它的对象也继承了自动改进的移动构造函数。

当NRVO(命名为返回值优化)失败时,在C 03中它会回落到复制上,在C 11上它会回落到移动上。NRVO的失败很容易:

std::vector<int> foo(int count){
  std::vector<int> v; // oops
  if (count<=0) return std::vector<int>();
  v.reserve(count);
  for(int i=0;i<count;++i)
    v.push_back(i);
  return v;
}

甚至:

std::vector<int> foo(bool which) {
  std::vector<int> a, b;
  // do work, filling a and b, using the other for calculations
  if (which)
    return a;
  else
    return b;
}

我们有三个值——返回值和函数内的两个不同值。Elision允许函数内的值与返回值“合并”,但不能相互合并。如果不相互合并,它们都不能与返回值合并。

基本问题是NRVO省略是脆弱的,并且在返回站点附近进行更改的代码可能会在该位置突然大幅降低性能,而没有发出诊断。在大多数NRVO失败案例中,C 11最终以移动结束,而C 03最终以副本结束。

Elision在这里也是不可能的:

std::set<int> func(std::set<int> in){
  return in;
}

在C 11中这很便宜:在C 03中没有办法避免复制。函数的参数不能用返回值省略,因为参数和返回值的生命周期和位置由调用代码管理。

但是,C 11可以从一个移动到另一个。(在一个不那么玩具的示例中,可能会对set做一些事情)。

最后,省略到容器中不会发生:但是C 11重载右值移动插入运算符,从而保存副本。

struct whatever {
  std::string data;
  int count;
  whatever( std::string d, int c ):data(d), count(c) {}
};
std::vector<whatever> v;
v.push_back( whatever("some long string goes here", 3) );

在C 03中,创建一个临时,然后将其复制到向量v中。2个d::字符串缓冲区被分配,每个缓冲区都有相同的数据,一个被丢弃。

在C 11中,创建了一个临时任何无论

从下面@Jarod42的回答中窃取。

分配时不能发生省略,但移出可以。

std::set<int> some_function();

std::set<int> some_value;

// code

some_value = some_function();

这里some_function返回一个要省略的候选对象,但是因为它不是用来直接构造对象的,所以不能省略它。在C 03中,上述情况导致临时的内容被复制到some_value中。在C 11中,它被移动到some_value中,这基本上是免费的。

为了实现上述效果,您需要一个为您合成移动构造函数和赋值的编译器。

MSVC 2013在std容器中实现了移动构造函数,但不会在您的类型上合成移动构造函数。

因此,包含std::向量和类似的类型在MSVC2013中没有得到这样的改进,但将在MSVC2015中开始得到它们。

clang和gcc早就实现了隐式移动构造函数。英特尔2013年的编译器将支持隐式生成移动构造函数,如果你通过-Qoption, cpp,--gen_move_operations(为了与MSVC2013交叉兼容,它们默认不这样做)。

匿名用户

如果你有类似的东西:

std::vector<int> foo(); // function declaration.
std::vector<int> v;

// some code

v = foo();

你在C 03中得到了一个副本,而在C 11中得到了一个移动分配。所以在这种情况下你可以自由优化。

相关问题