【问题标题】:Does noexcept improve performance?noexcept 会提高性能吗?
【发布时间】:2013-04-12 19:31:39
【问题描述】:

noexcept 函数说明符是否旨在提高性能,因为生成的对象中可能没有记录异常的代码,因此应尽可能将其添加到函数声明和定义中?我首先想到了可调用对象的包装器,noexcept 可能会有所不同,尽管检查表达式可能会“膨胀”源代码。值得吗?

【问题讨论】:

  • 我认为编译器可以做到这一点,但我认为关键是允许模板在不能抛出异常时表现不同(有一个 noexcept 运算符返回 false 时参数表达式可以返回一个异常 - 如果你有一个永远不会抛出但没有标记 noexcept 的函数,它不会返回 true)。
  • 如果声明为noexcept 的函数抛出程序必须终止。编译器需要至少注入一些异常处理代码来捕获问题并终止程序。
  • @DavidRodríguez-dribeas:不过,该代码可能是离线的(甚至是 C++ 运行时的一部分)。原则上,异常处理机制可以向上遍历堆栈,并为每个调用帧检查其代码是否带有“noexcept”标志。处理没有自己框架的内联调用可能会更加复杂。这是否是编译器实际上为noexcept 所做的事情,我不太确定,但通常这是他们如何寻找异常处理程序以及在堆栈展开期间完成的工作。
  • @SteveJessop:我对异常处理机制也不是很熟悉,我只是想指出它并不是那么明确。仍然需要做一些事情,并且函数被标记为noexcept 的事实并不意味着内部也不会有异常,只是它们不会通过函数调用传播(它们可以在函数内部被抛出和捕获)。虽然可能有一些优化空间,但我不会为了性能而到处撒noexcept,因为它可能会产生不必要的副作用......
  • ...例如考虑 N3604,昨天在标准委员会中讨论过。在验证狭义合约中的先决条件时,可以检测到违反合约(即未定义的行为),并选择多个选项之一来帮助调试。其中一个选项可能是抛出异常,但如果合约违反发生在对 noexcept 的函数的调用中,该函数将转换为 std::terminate。这就是为什么在马德里,他们将noexcept 从保证不会抛出的std::vector<>::operator[] 等函数的签名中删除!

标签: c++ boost c++11


【解决方案1】:

从理论上讲,noexcept 会提高性能。但另一方面,它也可能会导致一些问题。

在大多数情况下,不应指定,因为专业人士太少而无法考虑,并且可能会使您的代码升级变得痛苦。 This post,Andrzej写的,详细介绍了原因。

如果太长,请采纳我从中得出的以下建议:

  1. noexcept 注释函数如果
    • 他们已经用 throw() 注释了,
    • 或者他们是很好的候选人(在帖子中列出)并且永远不会投掷,
    • 或者它们是移动构造函数、移动赋值,其noexcept 注释无法被编译器正确推断,并且它们的实例应该被放入某个 STL 容器中。
  2. 如果不使用noexcept 注释函数
    • 您真的很担心性能下降,
    • 或关于拨打std::terminate的风险,
    • 或者您只是不确定新功能,
    • 或者您怀疑是否应该将您的函数设为noexcept

【讨论】:

    【解决方案2】:

    顶级编译器生成的代码已经优化,很像不能抛出的代码,然后发生异常的情况由异常处理机制通过查看元数据发现的外线代码处理。有关功能的数据。不过,我认为在已知不需要时省略它对代码大小有一些好处。

    在某些情况下,nothrow 规范确实允许某些特定的优化:

    int main() {
        int i = 0;
        try {
            ++i;
            thing_that_cannot_throw();
            ++i;
            thing_that_can_throw();
            ++i;
        } catch (...) {}
        std::cout << i << "\n";
    }
    

    理论上,这里的第二个 ++i 可以在调用 thing_that_cannot_throw 之前重新排序(而 i 刚刚初始化为 2)。但是,是否在实践中是另一回事,因为在调试器或函数调用上方的堆栈中保证变量状态的实现会希望i 在该调用期间具有值1,即使它是任何标准方法都无法观察到的局部变量。

    我怀疑 nothrow 保证对程序员比对编译器更有价值。如果您正在编写提供强异常保证的代码,那么通常您会执行某些关键操作,您需要提供 nothrow 保证(交换、移动和析构函数是常见的候选者)。

    【讨论】:

    • 当然,我们可以让编译器警告关于可能从noexcept 函数中抛出的异常来帮助我们走完这条线......尽管不幸的是,C++ 无处不在new 能够投掷的问题阻止了很多:(
    • @MatthieuM。 new 有一个 nothrow 版本,不是吗?
    • @Martin:有,std::string 使用什么?问题是你不能在noexcept 方法中创建一个新的std::string;不是因为您的代码有缺陷,而是因为您运行的硬件可能更加有限。这很烦人。
    • @MatthieuM:也许我误解了你,但你当然可以创建一个字符串,你只需要处理错误的可能性。如果您不相信会发生错误,因为您的硬件不受限制,那么void myfunc() noexcept { try { myfunc_impl(); } catch(...) {} } 会这样做,尽管您可能想要写一些不太确信操作会成功的东西!无论如何myfunc_impl 不需要是noexcept 函数。仅在编写异常安全代码总是令人讨厌的情况下才令人讨厌-您不能忽略易错的操作。
    【解决方案3】:

    我偶然发现了一个“真实世界”示例,其中 noexcept 有所作为。我想在这里分享它,因为它可能有助于其他人形成意见。

    首先介绍一下背景: 标准库容器尝试“异常安全”。这意味着在引发(并捕获)异常后,它们会为您提供有关容器状态的某些保证。一个很好的例子是 std::vector::emplace_back。如果由于某种原因插入失败, emplace_back 保证向量看起来没有改变。请参阅emplace_back 上的cppreference。 然而,当向量需要重新定位以响应 emplace 时,这会变得很有趣。重新定位预先存在的向量项的(希望)最快的方法是将move它们到新的扩大缓冲区。不幸的是,move-construction 可能引发异常,因此如果值类型的 move-ctor 不是异常安全的,emplace_back 需要使用复制操作。但是,由于可以在编译时探测其move-noexept'ness 的类型,std::vector 仍将采用更快的方法,如果这证明是合法的。

    我将以下谷歌基准放在一起以在本地进行测量:

    #include "benchmark/benchmark.h"
    
    #include <vector>
    
    // This type really benefits from being moved instead of being copied
    struct SlowCopy {
      SlowCopy(const size_t theSize) {
        for (int i = 0; i < theSize; ++i)
          itsData.emplace_back(i);
      }
      SlowCopy(const SlowCopy &) = default;
      SlowCopy(SlowCopy &&) noexcept = default;
    
      std::vector<int> itsData;
    };
    
    // The template parameter specifies whether the move constructor is noexcept or not
    template<bool YesNo>
    struct MovableNoexcept {
      MovableNoexcept(const size_t theSize) : itsData{theSize} {}
      MovableNoexcept(const MovableNoexcept &) = default;
      MovableNoexcept(MovableNoexcept &&) noexcept(YesNo) = default;
      MovableNoexcept& operator=(const MovableNoexcept &)  = default;
      MovableNoexcept& operator=(MovableNoexcept &&) noexcept(false) = default;
      SlowCopy itsData;
    };
    
    // This benchmark takes 2 arguments:
    // 1. How many items do we push into a vector
    // 2. How big are the items that are in the vector
    template<bool IsNoexcept>
    static void BM_MoveRelocateNoexcept(benchmark::State& state) {
      std::vector<MovableNoexcept<IsNoexcept>> aExcepts;
      for (auto _ : state) {
        for (int i = 0; i < state.range(0); ++i)
          aExcepts.emplace_back(state.range(1));
        benchmark::ClobberMemory();
      }
    }
    
    // Test 1k elements @ 64*sizeof(int) kb
    BENCHMARK_TEMPLATE(BM_MoveRelocateNoexcept, false)->Args({1000, 1 << 16})->Repetitions(20);
    BENCHMARK_TEMPLATE(BM_MoveRelocateNoexcept, true)->Args({1000, 1 << 16})->Repetitions(20);
    
    // Test 100 elements @ 512*sizeof(int) kb
    BENCHMARK_TEMPLATE(BM_MoveRelocateNoexcept, false)->Args({100, 1 << 19})->Repetitions(20);
    BENCHMARK_TEMPLATE(BM_MoveRelocateNoexcept, true)->Args({100, 1 << 19})->Repetitions(20);
    
    // Run the benchmark
    BENCHMARK_MAIN();
    

    在我的本地系统上,我在运行基准测试时测量了以下结果:

    Running ./noexcept_bench
    Run on (8 X 4400 MHz CPU s)
    CPU Caches:
      L1 Data 32 KiB (x4)
      L1 Instruction 32 KiB (x4)
      L2 Unified 256 KiB (x4)
      L3 Unified 8192 KiB (x1)
    Load Average: 0.58, 0.70, 0.69
    ------------------------------------------------------------------------------------------------------
    Benchmark                                                            Time             CPU   Iterations
    ------------------------------------------------------------------------------------------------------
    BM_MoveRelocateNoexcept<false>/1000/65536/repeats:20_mean    157793886 ns    157556651 ns           20
    BM_MoveRelocateNoexcept<false>/1000/65536/repeats:20_median  157752118 ns    157511285 ns           20
    BM_MoveRelocateNoexcept<false>/1000/65536/repeats:20_stddev     294024 ns       292420 ns           20
    BM_MoveRelocateNoexcept<true>/1000/65536/repeats:20_mean     119320642 ns    119235176 ns           20
    BM_MoveRelocateNoexcept<true>/1000/65536/repeats:20_median   119256119 ns    119187012 ns           20
    BM_MoveRelocateNoexcept<true>/1000/65536/repeats:20_stddev      190923 ns       180183 ns           20
    BM_MoveRelocateNoexcept<false>/100/524288/repeats:20_mean    127031806 ns    126834505 ns           20
    BM_MoveRelocateNoexcept<false>/100/524288/repeats:20_median  126939978 ns    126741072 ns           20
    BM_MoveRelocateNoexcept<false>/100/524288/repeats:20_stddev     381682 ns       380187 ns           20
    BM_MoveRelocateNoexcept<true>/100/524288/repeats:20_mean      95281309 ns     95175234 ns           20
    BM_MoveRelocateNoexcept<true>/100/524288/repeats:20_median    95267762 ns     95152072 ns           20
    BM_MoveRelocateNoexcept<true>/100/524288/repeats:20_stddev      176838 ns       176834 ns           20
    

    查看这些结果,可以使用 noexcept-move 的测试在两个基准测试中相对于它们的 non-noexcept-movable 对应物而言加速了 ~1.3

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-07-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-08-13
      • 2010-10-23
      • 2012-12-14
      相关资源
      最近更新 更多