【问题标题】:`std::variant` vs. inheritance vs. other ways (performance)`std::variant` vs. 继承 vs. 其他方式(性能)
【发布时间】:2020-01-03 16:30:53
【问题描述】:

我想知道std::variant 的性能。我什么时候不应该使用它?似乎虚函数仍然比使用 std::visit 好得多,这让我感到惊讶!

在“C++ 之旅”中,Bjarne Stroustrup 在解释了 std::holds_alternativesoverloaded 方法后谈到了 pattern checking

这基本上相当于一个虚函数调用,但可能更快。与所有索赔一样 性能,这种“可能更快”应该通过测量来验证,当性能是 危急。对于大多数用途,性能差异是微不足道的。

我已经对我想到的一些方法进行了基准测试,结果如下:

http://quick-bench.com/N35RRw_IFO74ZihFbtMu4BIKCJg

开启优化会得到不同的结果:

http://quick-bench.com/p6KIUtRxZdHJeiFiGI8gjbOumoc

这是我用于基准测试的代码;我确信有更好的方法来实现和使用变体来代替虚拟关键字(inheritance vs. std::variant):

删除了旧代码;查看更新

谁能解释为std::variant 实现这个用例的最佳方法是什么让我进行测试和基准测试:

我目前正在实现RFC 3986,它是'URI',对于我的用例,这个类将更多地用作一个常量,可能不会有太多改变,用户更有可能使用这个类查找 URI 的每个特定部分,而不是制作一个 URI;因此使用std::string_view 而不用自己的std::string 分隔URI 的每个段是有意义的。问题是我需要为它实现两个类;当我只需要一个 const 版本时;另一个用于当用户想要创建 URI 而不是提供一个并通过它进行搜索时。

所以我使用template 来修复它本身的问题;但后来我意识到我可以使用std::variant<std::string, std::string_view>(或者std::variant<CustomStructHoldingAllThePieces, std::string_view>);所以我开始研究看看它是否真的有助于使用变体。从这些结果来看,如果我不想实现两个不同的const_uriuri 类,似乎使用继承和virtual 是我最好的选择。

你觉得我应该怎么做?


更新(2)

感谢@gan_ 在我的基准代码中提及并修复了提升问题。 http://quick-bench.com/Mcclomh03nu8nDCgT3T302xKnXY

我对 try-catch hell 的结果感到惊讶,但感谢 this comment 现在这很有意义。

更新(3)

我删除了try-catch 方法,因为它真的很糟糕;并且还随机更改了选定的值,从外观上看,我看到了更现实的基准。看来virtual 毕竟不是正确答案。 http://quick-bench.com/o92Yrt0tmqTdcvufmIpu_fIfHt0

http://quick-bench.com/FFbe3bsIpdFsmgKfm94xGNFKVKs(没有内存泄漏哈哈)

更新(4)

我消除了生成随机数的开销(我在上次更新中已经这样做了,但似乎我抓取了错误的 URL 进行基准测试)并添加了一个 EmptyRandom 来了解生成随机数的基线。并且还在 Virtual 中做了一些小的改动,但我认为它不会影响任何东西。 http://quick-bench.com/EmhM-S-xoA0LABYK6yrMyBb8UeI

http://quick-bench.com/5hBZprSRIRGuDaBZ_wj0cOwnNhw(删除了虚拟,以便您可以更好地比较其余部分)


更新(5)

作为 cmets 中的 Jorge Bellon said,我没有考虑分配成本;所以我将每个基准转换为使用指针。这种间接性当然会对性能产生影响,但现在更公平了。所以现在循环中没有分配。

代码如下:

删除了旧代码;查看更新

到目前为止,我运行了一些基准测试。似乎 g++ 在优化代码方面做得更好:

-------------------------------------------------------------------
Benchmark                         Time             CPU   Iterations
-------------------------------------------------------------------
EmptyRandom                   0.756 ns        0.748 ns    746067433
TradeSpaceForPerformance       2.87 ns         2.86 ns    243756914
Virtual                        12.5 ns         12.4 ns     60757698
Index                          7.85 ns         7.81 ns     99243512
GetIf                          8.20 ns         8.18 ns     92393200
HoldsAlternative               7.08 ns         7.07 ns     96959764
ConstexprVisitor               11.3 ns         11.2 ns     60152725
StructVisitor                  10.7 ns         10.6 ns     60254088
Overload                       10.3 ns         10.3 ns     58591608

对于铿锵声:

-------------------------------------------------------------------
Benchmark                         Time             CPU   Iterations
-------------------------------------------------------------------
EmptyRandom                    1.99 ns         1.99 ns    310094223
TradeSpaceForPerformance       8.82 ns         8.79 ns     87695977
Virtual                        12.9 ns         12.8 ns     51913962
Index                          13.9 ns         13.8 ns     52987698
GetIf                          15.1 ns         15.0 ns     48578587
HoldsAlternative               13.1 ns         13.1 ns     51711783
ConstexprVisitor               13.8 ns         13.8 ns     49120024
StructVisitor                  14.5 ns         14.5 ns     52679532
Overload                       17.1 ns         17.1 ns     42553366

现在,对于 clang,最好使用虚拟继承,但对于 g++,最好使用 holds_alternativeget_if,但总的来说,std::visit 对于我几乎所有的基准测试来说似乎都不是一个好的选择,所以远。

我认为如果将模式匹配(能够检查除整数文字之外的更多内容的 switch 语句)添加到 c++ 中将是一个好主意,我们将编写更清晰和更可维护的代码。

我想知道package.index() 结果。不应该更快吗?它有什么作用?

Clang 版本:http://quick-bench.com/cl0HFmUes2GCSE1w04qt4Rqj6aI

基于Maxim Egorushkin's comment 使用One one 而不是auto one = new One 的版本:http://quick-bench.com/KAeT00__i2zbmpmUHDutAfiD6-Q(对结果变化不大)


更新(6)

我做了一些更改,结果现在从编译器到编译器有很大不同。但似乎std::get_ifstd::holds_alternatives 是最好的解决方案。 virtual 现在似乎因为未知原因而使用 clang 效果最好。这真的让我感到惊讶,因为我记得virtual 在 gcc 中表现更好。而且std::visit 完全没有竞争力;在最后一个基准测试中,它甚至比 vtable 查找更糟糕。

这是基准测试(使用 GCC/Clang 以及 libstdc++ 和 libc++ 运行):

http://quick-bench.com/LhdP-9y6CqwGxB-WtDlbG27o_5Y

#include <benchmark/benchmark.h>

#include <array>
#include <variant>
#include <random>
#include <functional>
#include <algorithm>

using namespace std;

struct One {
  auto get () const { return 1; }
 };
struct Two {
  auto get() const { return 2; }
 };
struct Three { 
  auto get() const { return 3; }
};
struct Four {
  auto get() const { return 4; }
 };

template<class... Ts> struct overload : Ts... { using Ts::operator()...; };
template<class... Ts> overload(Ts...) -> overload<Ts...>;


std::random_device dev;
std::mt19937 rng(dev());
std::uniform_int_distribution<std::mt19937::result_type> random_pick(0,3); // distribution in range [1, 6]

template <std::size_t N>
std::array<int, N> get_random_array() {
  std::array<int, N> item;
  for (int i = 0 ; i < N; i++)
    item[i] = random_pick(rng);
  return item;
}

template <typename T, std::size_t N>
std::array<T, N> get_random_objects(std::function<T(decltype(random_pick(rng)))> func) {
    std::array<T, N> a;
    std::generate(a.begin(), a.end(), [&] {
        return func(random_pick(rng));
    });
    return a;
}


static void TradeSpaceForPerformance(benchmark::State& state) {
    One one;
    Two two;
    Three three;
    Four four;

  int index = 0;

  auto ran_arr = get_random_array<50>();
  int r = 0;

  auto pick_randomly = [&] () {
    index = ran_arr[r++ % ran_arr.size()];
  };

  pick_randomly();


  for (auto _ : state) {

    int res;
    switch (index) {
      case 0:
        res = one.get();
        break;
      case 1:
        res = two.get();
        break;
      case 2:
        res = three.get();
        break;
      case 3:
        res = four.get();
        break;
    }
    
    benchmark::DoNotOptimize(index);
    benchmark::DoNotOptimize(res);

    pick_randomly();
  }


}
// Register the function as a benchmark
BENCHMARK(TradeSpaceForPerformance);


static void Virtual(benchmark::State& state) {

  struct Base {
    virtual int get() const noexcept = 0;
    virtual ~Base() {}
  };

  struct A final: public Base {
    int get()  const noexcept override { return 1; }
  };

  struct B final : public Base {
    int get() const noexcept override { return 2; }
  };

  struct C final : public Base {
    int get() const noexcept override { return 3; }
  };

  struct D final : public Base {
    int get() const noexcept override { return 4; }
  };

  Base* package = nullptr;
  int r = 0;
  auto packages = get_random_objects<Base*, 50>([&] (auto r) -> Base* {
          switch(r) {
              case 0: return new A;
              case 1: return new B;
              case 3: return new C;
              case 4: return new D;
              default: return new C;
          }
    });

  auto pick_randomly = [&] () {
    package = packages[r++ % packages.size()];
  };

  pick_randomly();

  for (auto _ : state) {

    int res = package->get();

    benchmark::DoNotOptimize(package);
    benchmark::DoNotOptimize(res);

    pick_randomly();
  }


  for (auto &i : packages)
    delete i;

}
BENCHMARK(Virtual);




static void FunctionPointerList(benchmark::State& state) {

    One one;
    Two two;
    Three three;
    Four four;
  using type = std::function<int()>;
  std::size_t index;

  auto packages = get_random_objects<type, 50>([&] (auto r) -> type {
        switch(r) {
        case 0: return std::bind(&One::get, one);
        case 1: return std::bind(&Two::get, two);
        case 2: return std::bind(&Three::get, three);
        case 3: return std::bind(&Four::get, four);
        default: return std::bind(&Three::get, three);
        }
    });
  int r = 0;

  auto pick_randomly = [&] () {
    index = r++ % packages.size();
  };


  pick_randomly();

  for (auto _ : state) {

    int res = packages[index]();

    benchmark::DoNotOptimize(index);
    benchmark::DoNotOptimize(res);

    pick_randomly();
  }

}
BENCHMARK(FunctionPointerList);



static void Index(benchmark::State& state) {

    One one;
    Two two;
    Three three;
    Four four;
  using type = std::variant<One, Two, Three, Four>;
  type* package = nullptr;

  auto packages = get_random_objects<type, 50>([&] (auto r) -> type {
        switch(r) {
            case 0: return one;
            case 1: return two;
            case 2: return three;
            case 3: return four;
            default: return three;
        }
    });
  int r = 0;

  auto pick_randomly = [&] () {
    package = &packages[r++ % packages.size()];
  };


  pick_randomly();

  for (auto _ : state) {

    int res;
    switch (package->index()) {
      case 0: 
        res = std::get<One>(*package).get();
        break;
      case 1:
        res = std::get<Two>(*package).get();
        break;
      case 2:
        res = std::get<Three>(*package).get();
        break;
      case 3:
        res = std::get<Four>(*package).get();
        break;
    }

    benchmark::DoNotOptimize(package);
    benchmark::DoNotOptimize(res);

    pick_randomly();
  }

}
BENCHMARK(Index);



static void GetIf(benchmark::State& state) {
    One one;
    Two two;
    Three three;
    Four four;
  using type = std::variant<One, Two, Three, Four>;
  type* package = nullptr;

  auto packages = get_random_objects<type, 50>([&] (auto r) -> type {
        switch(r) {
            case 0: return one;
            case 1: return two;
            case 2: return three;
            case 3: return four;
            default: return three;
        }
    });
  int r = 0;

  auto pick_randomly = [&] () {
    package = &packages[r++ % packages.size()];
  };

  pick_randomly();

  for (auto _ : state) {

    int res;
    if (auto item = std::get_if<One>(package)) {
      res = item->get();
    } else if (auto item = std::get_if<Two>(package)) {
      res = item->get();
    } else if (auto item = std::get_if<Three>(package)) {
      res = item->get();
    } else if (auto item = std::get_if<Four>(package)) {
      res = item->get();
    }

    benchmark::DoNotOptimize(package);
    benchmark::DoNotOptimize(res);

    pick_randomly();
  }
  

}
BENCHMARK(GetIf);

static void HoldsAlternative(benchmark::State& state) {
    One one;
    Two two;
    Three three;
    Four four;
  using type = std::variant<One, Two, Three, Four>;
  type* package = nullptr;

  auto packages = get_random_objects<type, 50>([&] (auto r) -> type {
        switch(r) {
            case 0: return one;
            case 1: return two;
            case 2: return three;
            case 3: return four;
            default: return three;
        }
    });
  int r = 0;

  auto pick_randomly = [&] () {
    package = &packages[r++ % packages.size()];
  };

  pick_randomly();

  for (auto _ : state) {

    int res;
    if (std::holds_alternative<One>(*package)) {
      res = std::get<One>(*package).get();
    } else if (std::holds_alternative<Two>(*package)) {
      res = std::get<Two>(*package).get();
    } else if (std::holds_alternative<Three>(*package)) {
      res = std::get<Three>(*package).get();
    } else if (std::holds_alternative<Four>(*package)) {
      res = std::get<Four>(*package).get();
    }

    benchmark::DoNotOptimize(package);
    benchmark::DoNotOptimize(res);

    pick_randomly();
  }

}
BENCHMARK(HoldsAlternative);


static void ConstexprVisitor(benchmark::State& state) {

    One one;
    Two two;
    Three three;
    Four four;
  using type = std::variant<One, Two, Three, Four>;
  type* package = nullptr;

  auto packages = get_random_objects<type, 50>([&] (auto r) -> type {
        switch(r) {
            case 0: return one;
            case 1: return two;
            case 2: return three;
            case 3: return four;
            default: return three;
        }
    });
  int r = 0;

  auto pick_randomly = [&] () {
    package = &packages[r++ % packages.size()];
  };

  pick_randomly();

  auto func = [] (auto const& ref) {
        using type = std::decay_t<decltype(ref)>;
        if constexpr (std::is_same<type, One>::value) {
            return ref.get();
        } else if constexpr (std::is_same<type, Two>::value) {
            return ref.get();
        } else if constexpr (std::is_same<type, Three>::value)  {
          return ref.get();
        } else if constexpr (std::is_same<type, Four>::value) {
            return ref.get();
        } else {
          return 0;
        }
    };

  for (auto _ : state) {

    auto res = std::visit(func, *package);

    benchmark::DoNotOptimize(package);
    benchmark::DoNotOptimize(res);

    pick_randomly();
  }

}
BENCHMARK(ConstexprVisitor);

static void StructVisitor(benchmark::State& state) {

  

  struct VisitPackage
  {
      auto operator()(One const& r) { return r.get(); }
      auto operator()(Two const& r) { return r.get(); }
      auto operator()(Three const& r) { return r.get(); }
      auto operator()(Four const& r) { return r.get(); }
  };

    One one;
    Two two;
    Three three;
    Four four;
  using type = std::variant<One, Two, Three, Four>;
  type* package = nullptr;

  auto packages = get_random_objects<type, 50>([&] (auto r) -> type {
        switch(r) {
            case 0: return one;
            case 1: return two;
            case 2: return three;
            case 3: return four;
            default: return three;
        }
    });
  int r = 0;

  auto pick_randomly = [&] () {
    package = &packages[r++ % packages.size()];
  };

  pick_randomly();

  auto vs = VisitPackage();

  for (auto _ : state) {

    auto res = std::visit(vs, *package);

    benchmark::DoNotOptimize(package);
    benchmark::DoNotOptimize(res);

    pick_randomly();
  }

}
BENCHMARK(StructVisitor);


static void Overload(benchmark::State& state) {


    One one;
    Two two;
    Three three;
    Four four;
  using type = std::variant<One, Two, Three, Four>;
  type* package = nullptr;

  auto packages = get_random_objects<type, 50>([&] (auto r) -> type {
        switch(r) {
            case 0: return one;
            case 1: return two;
            case 2: return three;
            case 3: return four;
            default: return three;
        }
    });
  int r = 0;

  auto pick_randomly = [&] () {
    package = &packages[r++ % packages.size()];
  };

  pick_randomly();

  auto ov = overload {
      [] (One const& r) { return r.get(); },
      [] (Two const& r) { return r.get(); },
      [] (Three const& r) { return r.get(); },
      [] (Four const& r) { return r.get(); }
    };

  for (auto _ : state) {

    auto res = std::visit(ov, *package);

  
    benchmark::DoNotOptimize(package);
    benchmark::DoNotOptimize(res);

    pick_randomly();
  }

}
BENCHMARK(Overload);


// BENCHMARK_MAIN();

GCC 编译器的结果:

-------------------------------------------------------------------
Benchmark                         Time             CPU   Iterations
-------------------------------------------------------------------
TradeSpaceForPerformance       3.71 ns         3.61 ns    170515835
Virtual                       12.20 ns        12.10 ns     55911685
FunctionPointerList           13.00 ns        12.90 ns     50763964
Index                          7.40 ns         7.38 ns    136228156
GetIf                          4.04 ns         4.02 ns    205214632
HoldsAlternative               3.74 ns         3.73 ns    200278724
ConstexprVisitor              12.50 ns        12.40 ns     56373704
StructVisitor                 12.00 ns        12.00 ns     60866510
Overload                      13.20 ns        13.20 ns     56128558

clang 编译器的结果(我对此感到惊讶):

-------------------------------------------------------------------
Benchmark                         Time             CPU   Iterations
-------------------------------------------------------------------
TradeSpaceForPerformance       8.07 ns         7.99 ns     77530258
Virtual                        7.80 ns         7.77 ns     77301370
FunctionPointerList            12.1 ns         12.1 ns     56363372
Index                          11.1 ns         11.1 ns     69582297
GetIf                          10.4 ns         10.4 ns     80923874
HoldsAlternative               9.98 ns         9.96 ns     71313572
ConstexprVisitor               11.4 ns         11.3 ns     63267967
StructVisitor                  10.8 ns         10.7 ns     65477522
Overload                       11.4 ns         11.4 ns     64880956

迄今为止的最佳基准(将更新): http://quick-bench.com/LhdP-9y6CqwGxB-WtDlbG27o_5Y(也可以查看 GCC)

【问题讨论】:

  • “如果开启优化,你会得到不同的结果”:在未优化的构建中测量性能几乎毫无意义。
  • 我很想以“基于意见”的方式投票结束,但由于问题的工作量很大,我会避免。希望你能得到一个好的答案。
  • 在您的 5 个基准测试实现中,编译器能够将访问提升到循环之外。 You need to use DoNotOptimize on the variant itself如果你想拆箱。
  • 您的Virtual 基准测试可能实际上并未使用动态调度,因为编译器可能能够跟踪调用图并将对get 的调用去虚拟化。 this answer 给出了一个很好的解释。使用 gcc,您可以提供标志 -fno-devirtualize 以防止这种特定优化,同时仍然不完全放弃优化。
  • @moisrex 你的基准代码中的一个问题是你总是使用第一个替代方案(你存储一个One)。 The try/catch alternative is basically made to be very fast when the first alternative is chosen.如果我将您的代码更改为保留第三种选择 (Three),try/catch 将比任何其他版本慢数千倍。 quick-bench.com/yIDclCuxfzjzBlCjbVCDNrp13aU

标签: c++ performance inheritance c++17 std-variant


【解决方案1】:

std::visit 在某些实现上似乎还缺乏一些优化。话虽如此,在这个类似实验室的设置中,有一个中心点不太明显——即基于 的设计是基于堆栈的,而虚拟 模式自然会倾向于基于堆。在现实世界的场景中,这意味着内存布局很可能是碎片化的(可能随着时间的推移——一旦对象离开缓存等)——除非它可以以某种方式避免。相反的是基于 的设计,可以在连续内存中进行布局。我相信这是一个极其重要的点,当涉及到不可低估的性能时要考虑这一点。

为了说明这一点,请考虑以下几点:

std::vector<Base*> runtime_poly_;//risk of fragmentation

对比

std::vector<my_var_type> cp_time_poly_;//no fragmentation (but padding 'risk')

这种碎片化有点难以构建到像这样的基准测试中。 如果这(也)在 bjarne 声明的上下文中,当他说它可能会更快(我相信这是正确的)时,我不清楚。

对于基于std::variant 的设计要记住的另一个非常重要的事情是每个元素的大小都用尽了最大可能元素的大小。因此,如果对象的大小不大致相同,则必须仔细考虑这一点,因为它可能会对缓存产生不良影响。

综合考虑这些点,很难说在一般情况下哪个最适合使用 - 但是,如果该集合是一个封闭的“小型”且大小大致相同,则应该足够清楚 - 那么变体样式显示出巨大的潜力为了更快(如 bjarne 所述)。

我们现在只考虑性能,而选择其中一种模式确实还有其他原因:最后,您只需要走出舒适的“实验室”,设计和基准测试您的实际使用情况案例。

【讨论】:

  • 这可能是一个愚蠢的问题,但是为什么在示例中建议std::vector&lt;Base*&gt;,而不是std::vector&lt;Base&gt;
  • 我认为这是为了节省空间,因为创建指向堆分配对象而不是对象本身的指针向量更好。
  • @luizfls std::vector&lt;Base&gt; 不是多态的
  • @luizfls 查找“C++ 中的对象切片”以了解Base* 的用法。简而言之,使用 Base 意味着不在 Base 中而是在其派生类中的所有内容都被“切掉”。
【解决方案2】:

如果您可以保证变体永远不会因异常为空,则可以将它们全部与访问实现匹配。这是一个单一的访问者,它与上面的虚拟匹配并且与 jmp 表很好地内联。 https://gcc.godbolt.org/z/kkjACx

struct overload : Fs... {
  using Fs::operator()...;
};

template <typename... Fs>
overload(Fs...) -> overload<Fs...>;

template <size_t N, typename R, typename Variant, typename Visitor>
[[nodiscard]] constexpr R visit_nt(Variant &&var, Visitor &&vis) {
  if constexpr (N == 0) {
    if (N == var.index()) {
      // If this check isnt there the compiler will generate
      // exception code, this stops that
      return std::forward<Visitor>(vis)(
          std::get<N>(std::forward<Variant>(var)));
    }
  } else {
    if (var.index() == N) {
      return std::forward<Visitor>(vis)(
          std::get<N>(std::forward<Variant>(var)));
    }
    return visit_nt<N - 1, R>(std::forward<Variant>(var),
                              std::forward<Visitor>(vis));
  }
  while (true) {
  }  // unreachable but compilers complain
}

template <class... Args, typename Visitor, typename... Visitors>
[[nodiscard]] constexpr decltype(auto) visit_nt(
    std::variant<Args...> const &var, Visitor &&vis, Visitors &&... visitors) {
  auto ol =
      overload{std::forward<Visitor>(vis), std::forward<Visitors>(visitors)...};
  using result_t = decltype(std::invoke(std::move(ol), std::get<0>(var)));

  static_assert(sizeof...(Args) > 0);
  return visit_nt<sizeof...(Args) - 1, result_t>(var, std::move(ol));
}

template <class... Args, typename Visitor, typename... Visitors>
[[nodiscard]] constexpr decltype(auto) visit_nt(std::variant<Args...> &var,
                                                Visitor &&vis,
                                                Visitors &&... visitors) {
  auto ol =
      overload(std::forward<Visitor>(vis), std::forward<Visitors>(visitors)...);
  using result_t = decltype(std::invoke(std::move(ol), std::get<0>(var)));

  static_assert(sizeof...(Args) > 0);
  return visit_nt<sizeof...(Args) - 1, result_t>(var, std::move(ol));
}

template <class... Args, typename Visitor, typename... Visitors>
[[nodiscard]] constexpr decltype(auto) visit_nt(std::variant<Args...> &&var,
                                                Visitor &&vis,
                                                Visitors &&... visitors) {
  auto ol =
      overload{std::forward<Visitor>(vis), std::forward<Visitors>(visitors)...};
  using result_t =
      decltype(std::invoke(std::move(ol), std::move(std::get<0>(var))));

  static_assert(sizeof...(Args) > 0);
  return visit_nt<sizeof...(Args) - 1, result_t>(std::move(var), std::move(ol));
}

template <typename Value, typename... Visitors>
inline constexpr bool is_visitable_v = (std::is_invocable_v<Visitors, Value> or
                                        ...);

您首先使用变体调用它,然后是访问者。 这是添加了 的Update 6 quickbench。板凳链接在这里http://quick-bench.com/98aSbU0wWUsym0ej-jLy1POmCBw

因此,我认为是否访问的决定归结为更具表现力和意图的明确性。无论哪种方式都可以实现性能。

【讨论】:

    【解决方案3】:

    基于更新 6 http://quick-bench.com/LhdP-9y6CqwGxB-WtDlbG27o_5Y

    我想我们无法比较时间,但相对于彼此的结果似乎不同,足以显示库实现中的选择。

    • Visual 2019 v16.8.3

    • cl 19.28.29335 x64

    • 在 /std:c++17 中编译

       Run on (8 X 3411 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)
      
       -------------------------------------------------------------------
       Benchmark                         Time             CPU   Iterations
       -------------------------------------------------------------------
       TradeSpaceForPerformance       5.41 ns         5.47 ns    100000000
       Virtual                        11.2 ns         10.9 ns     56000000
       FunctionPointerList            13.2 ns         13.1 ns     56000000
       Index                          4.37 ns         4.37 ns    139377778
       GetIf                          4.79 ns         4.87 ns    144516129
       HoldsAlternative               5.08 ns         5.16 ns    100000000
       ConstexprVisitor               4.16 ns         4.14 ns    165925926
       StructVisitor                  4.26 ns         4.24 ns    165925926
       Overload                       4.21 ns         4.24 ns    165925926
      

    【讨论】:

      猜你喜欢
      • 2018-12-20
      • 2019-10-11
      • 2019-02-09
      • 2023-04-04
      • 1970-01-01
      • 1970-01-01
      • 2011-06-16
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多