首页 文章

现代C可以免费获得性能吗?

提问于
浏览
199

有时声称即使只编译C 98代码,C 11/14也可以提升性能 . 理由通常是移动语义,因为在某些情况下,rvalue构造函数是自动生成的,或者现在是STL的一部分 . 现在我想知道这些案例以前是否已经由RVO或类似的编译器优化处理过了 .

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

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

2 回答

  • 217

    我知道5个一般类别,其中重新编译C 03编译器作为C 11可以导致无限的性能增加,这实际上与实现质量无关 . 这些都是移动语义的变体 .

    std :: vector reallocate

    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 中复制 vector .

    在C 11中它反而移动 bar::data ,这基本上是免费的 .

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

    NRVO失败

    当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 elision是脆弱的,并且不在 return 站点附近进行更改的代码可以突然在该位置大幅降低性能而不会发出诊断信息 . 在大多数NRVO失败案例中,C 11以 move 结束,而C 03最终以副本结束 .

    返回函数参数

    Elision在这里也是不可能的:

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

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

    但是,C 11可以从一个移动到另一个 . (在较少玩具的例子中,可能会对 set 做些什么) .

    push_back或插入

    最后,不会发生对容器的省略:但是C 11会重载rvalue移动插入操作符,从而节省了副本 .

    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 ,然后将其复制到向量 v 中 . 分配2个 std::string 缓冲区,每个缓冲区具有相同的数据,并丢弃一个缓冲区 .

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

    作业

    从@ Jarod42的答案中偷来了 .

    分配时不能进行Elision,但移动可以 .

    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::vector 和类似的类型不会得到这样的MSVC2013的改进,但将开始在MSVC2015中获得它们 .

    clang和gcc早就实现了隐式移动构造函数 . 如果你传递 -Qoption,cpp,--gen_move_operations ,那么英特尔的2013编译器将支持隐式生成移动构造函数(默认情况下它们并不是为了与MSVC2013交叉兼容) .

  • 44

    如果你有类似的东西:

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

    你在C 03中得到了一个副本,而你在C 11中得到了一个移动任务 . 所以你可以在这种情况下进行自由优化 .

相关问题