34

関数指定子はnoexcept、生成されたオブジェクトの例外を記録するコードがない可能性があるため、パフォーマンスの向上を目的としているので、可能な限り関数の宣言と定義に追加する必要がありますか? 最初に、呼び出し可能なオブジェクトのラッパーを考えますnoexcept。チェック式によってソース コードが「肥大化」する可能性がありますが、ここで違いが生じる可能性があります。それは価値がありますか?

4

3 に答える 3

9

noexcept が違いを生む「現実世界」の例に出くわしました。他の人が意見を形成するのに役立つかもしれないので、ここで共有したいと思います。

最初に少し背景を説明します。標準ライブラリ コンテナーは「例外セーフ」になろうとします。つまり、例外が発生した (およびキャッチされた) 後のコンテナーの状態について、一定の保証が得られます。これの非常に良い例は std::vector::emplace_back です。何らかの理由で挿入が失敗した場合、 emplace_back はベクトルが変更されていないように見えることを保証します。のcppreferenceを参照してくださいemplace_back。ただし、これは emplace に応答してベクトルを再配置する必要がある場合に興味深いものになります。既存のベクトル項目を再配置する (うまくいけば) 最速の方法は、moveそれらを新しい拡大されたバッファに移動することです。残念ながら、move-construction は例外を発生させる可能性があるため、値型のmove-ctor が例外セーフでない場合、emplace_back代わりにコピー操作に頼る必要があります。しかし、コンパイル時に型のmove-noexeptstd::vector性をプローブすることは可能であるため、それが合法であることが判明した場合でも、より高速なアプローチが採用されます。

これをローカルで測定するために、次のGoogleベンチマークをまとめました。

#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 が可能なテストでは、両方のベンチマークで noexcept-movable 以外の対応するテストに比べて最大1.3の高速化が見られました。

于 2020-12-17T18:52:09.267 に答える