小编典典

现代 C++ 可以免费为您提供性能吗?

all

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

那么我的问题是,您能否给我一个 C++98
代码的实际示例,该代码无需修改即可使用支持新语言功能的编译器运行得更快。我确实明白,不需要符合标准的编译器来执行复制省略,正因为如此,移动语义可能会带来速度,但如果你愿意的话,我希望看到一个不那么病态的情况。

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


阅读 65

收藏
2022-06-24

共1个答案

小编典典

我知道 5 个一般类别,其中将 C03 编译器重新编译为 C11 会导致性能无限提高,这实际上与实现质量无关。这些都是移动语义的变体。

std::vector重新分配

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

每次foo在 C++03 中重新分配 ‘s 缓冲区时,它都会vectorbar.

在 C++11 中,它改为移动bar::datas,这基本上是免费的。

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

NRVO 故障

当 NRVO(命名返回值优化)失败时,在 C03 中它回退到复制,在 C11 中它回退到移动。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 省略是脆弱的,并且在return站点附近没有更改的代码可能会突然在该位置大幅降低性能,而不会发出诊断。在大多数 NRVO
失败的情况下,C11 以 a 结尾move,而 C03 以副本结尾。

返回函数参数

省略在这里也是不可能的:

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

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

但是,C++11 可以从一个转移到另一个。(在一个不太玩具的例子中,可能会对 做一些事情set)。

push_back或者insert

最后省略到容器中不会发生:但是 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 中会创建一个临时whatever对象,然后将其复制到 vectorv中。分配了2
std::string缓冲区,每个缓冲区都有相同的数据,一个被丢弃。

在 C++11 中创建了一个临时whatever文件。然后whatever&&
push_back重载move将临时放入向量v中。分配一个std::string缓冲区,并将其移动到向量中。一个空std::string的被丢弃。

任务

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

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

std::set<int> some_function();

std::set<int> some_value;

// code

some_value = some_function();

这里some_function返回一个候选者来省略,但因为它不用于直接构造对象,所以不能省略。在 C03
中,上述结果会导致临时文件的内容被复制到some_value. 在 C
11 中,它被移入some_value,基本上是免费的。


要获得上述的全部效果,您需要一个为您合成移动构造函数和赋值的编译器。

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

所以包含std::vectors 和类似的类型在 MSVC2013 中不会得到这样的改进,但会在 MSVC2015 中开始得到它们。

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

2022-06-24