60 votes

`std::variant` vs. héritage vs. autres moyens (performance)

Je m'interroge sur std::variant performance. Quand ne dois-je pas l'utiliser ? Il semble que les fonctions virtuelles soient encore bien meilleures que l'utilisation de std::visit ce qui m'a surpris !

Dans "A Tour of C++" Bjarne Stroustrup dit ceci à propos de pattern checking après avoir expliqué std::holds_alternatives y el overloaded méthodes :

C'est fondamentalement équivalent à un appel de fonction virtuelle, mais potentiellement plus rapide. Comme pour toutes les revendications de performance, ce ''potentiellement plus rapide'' doit être vérifié par des mesures lorsque la performance est critiques. Pour la plupart des utilisations, la différence de performance est insignifiante.

J'ai testé quelques méthodes qui me sont venues à l'esprit et voici les résultats :

benchmark http://quick-bench.com/N35RRw_IFO74ZihFbtMu4BIKCJg

Vous obtiendrez un résultat différent si vous activez l'optimisation :

benchmark with op enabled

http://quick-bench.com/p6KIUtRxZdHJeiFiGI8gjbOumoc

Voici le code que j'ai utilisé pour les benchmarks ; je suis sûr qu'il y a une meilleure façon d'implémenter et d'utiliser les variantes pour les utiliser à la place des mots-clés virtuels ( héritage vs. std::variant ):

supprimé l'ancien code ; regardez les mises à jour

Quelqu'un peut-il expliquer quelle est la meilleure façon de mettre en œuvre ce cas d'utilisation pour std::variant ce qui m'a amené à faire des tests et des analyses comparatives :

Je suis actuellement en train de mettre en œuvre RFC 3986 qui est 'URI' et pour mon cas d'utilisation, cette classe sera plus utilisée comme une constante et ne sera probablement pas beaucoup modifiée. Il est plus probable que l'utilisateur utilise cette classe pour trouver chaque portion spécifique de l'URI plutôt que de créer un URI ; il était donc logique d'utiliser la classe std::string_view et ne pas séparer chaque segment de l'URI dans son propre std::string . Le problème était que j'avais besoin d'implémenter deux classes pour cela ; une pour quand je n'ai besoin que d'une version const ; et une autre pour quand l'utilisateur veut créer l'URI plutôt que d'en fournir un et de le rechercher.

J'ai donc utilisé un template pour réparer ce qui avait ses propres problèmes ; mais ensuite j'ai réalisé que je pouvais utiliser std::variant<std::string, std::string_view> (ou peut-être std::variant<CustomStructHoldingAllThePieces, std::string_view> ) ; j'ai donc commencé à faire des recherches pour voir si l'utilisation de variantes est réellement utile ou non. D'après ces résultats, il semble que l'utilisation de l'héritage et des virtual est mon meilleur choix si je ne veux pas implémenter deux différents const_uri y uri classes.

Que pensez-vous que je doive faire ?


Mise à jour (2)

Merci à @gan_ d'avoir mentionné et corrigé le problème de levage dans mon code de référence. benchmark http://quick-bench.com/Mcclomh03nu8nDCgT3T302xKnXY

J'ai été surpris par le résultat de l'enfer try-catch mais grâce à ce commentaire ça a du sens maintenant.

Mise à jour (3)

J'ai retiré le try-catch J'ai également modifié de manière aléatoire la valeur sélectionnée et, à première vue, j'ai obtenu des résultats plus réalistes. Il semble que virtual n'est pas la bonne réponse après tout. random access http://quick-bench.com/o92Yrt0tmqTdcvufmIpu_fIfHt0

http://quick-bench.com/FFbe3bsIpdFsmgKfm94xGNFKVKs (sans la fuite de mémoire lol)

Mise à jour (4)

J'ai supprimé l'overhead de la génération de nombres aléatoires (je l'ai déjà fait dans la dernière mise à jour mais il semble que j'avais pris la mauvaise URL pour le benchmark) et j'ai ajouté un EmptyRandom pour comprendre la ligne de base de la génération de nombres aléatoires. J'ai également effectué quelques petits changements dans Virtual, mais je ne pense pas que cela ait eu une incidence sur quoi que ce soit. empty random added http://quick-bench.com/EmhM-S-xoA0LABYK6yrMyBb8UeI

http://quick-bench.com/5hBZprSRIRGuDaBZ_wj0cOwnNhw (j'ai enlevé le virtuel pour que vous puissiez mieux comparer les autres)


Mise à jour (5)

comme Jorge Bellon a déclaré dans les commentaires, je n'avais pas pensé au coût de l'allocation ; j'ai donc converti chaque benchmark pour utiliser des pointeurs. Cette indirection a un impact sur les performances bien sûr, mais c'est plus juste maintenant. Donc, pour l'instant, il n'y a pas d'allocation dans les boucles.

Voici le code :

supprimé l'ancien code ; regardez les mises à jour

J'ai effectué quelques benchmarks jusqu'à présent. Il semble que g++ fasse un meilleur travail d'optimisation du code :

-------------------------------------------------------------------
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

Et pour le clang :

-------------------------------------------------------------------
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

Actuellement, pour clang, il est préférable d'utiliser l'héritage virtuel mais pour g++ il est préférable d'utiliser holds_alternative o get_if mais dans l'ensemble, std::visit ne semble pas être un bon choix pour presque tous mes benchmarks jusqu'à présent.

Je pense que ce serait une bonne idée d'ajouter le pattern matching (des instructions switch capables de vérifier plus de choses que de simples littéraux entiers) au c++, nous écririons un code plus propre et plus facile à maintenir.

Je m'interroge sur le package.index() résultats. Ne devrait-il pas être plus rapide ? Que fait-il ?

Version de Clang : http://quick-bench.com/cl0HFmUes2GCSE1w04qt4Rqj6aI

La version qui utilise One one au lieu de auto one = new One sur la base de Commentaire de Maxim Egorushkin : http://quick-bench.com/KAeT00__i2zbmpmUHDutAfiD6-Q (ne changeant pas beaucoup le résultat)


Mise à jour (6)

J'ai fait quelques changements et les résultats sont maintenant très différents d'un compilateur à l'autre. Mais il semble que std::get_if y std::holds_alternatives sont les meilleures solutions. virtual semble fonctionner mieux pour des raisons inconnues avec clang maintenant. Cela me surprend vraiment car je me souviens virtual étant mieux dans gcc. Et aussi std::visit est totalement hors compétition ; dans ce dernier benchmark, il est même pire que vtable lookup.

Voici le benchmark (exécuté avec GCC/Clang et aussi avec libstdc++ et 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();

Résultats pour le compilateur 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

Résultats pour le compilateur clang (qui me surprend) :

-------------------------------------------------------------------
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

Meilleur benchmark à ce jour (sera mis à jour) : http://quick-bench.com/LhdP-9y6CqwGxB-WtDlbG27o_5Y (consultez également le CCG)

13voto

darune Points 3515

std::visit semble encore manquer d'optimisations sur certaines implémentations. Ceci étant dit, il y a un point central qui n'est pas très bien vu dans cette configuration de type laboratoire - qui est que variante est basée sur la pile par rapport au virtuel héritage qui s'orientera naturellement vers un modèle basé sur le tas. Dans un scénario du monde réel, cela signifie que la disposition de la mémoire pourrait très bien être fragmentée (peut-être au fil du temps - une fois que les objets quittent le cache, etc.) - à moins que cela ne puisse être évité d'une manière ou d'une autre. L'opposé est le variante conception qui peut être mise en page dans la mémoire contiguë. Je pense que c'est un extrêmement important Un point à considérer en matière de performance qui ne doit pas être sous-estimé.

Pour illustrer cela, considérons l'exemple suivant :

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

vs.

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

Cette fragmentation est quelque peu difficile à intégrer dans un test de référence comme celui-ci. Si c'est (aussi) dans le contexte de la déclaration de bjarne n'est pas clair pour moi quand il a dit qu'il pourrait potentiellement être plus rapide (ce qui, je crois, est vrai).

Un autre très important chose à retenir pour le std::variant est que la taille de chaque élément utilise la taille de l'élément le plus grand possible. Par conséquent, si les objets n'ont pas à peu près la même taille, il faut en tenir compte, car cela peut avoir un impact négatif sur le cache.

En considérant l'ensemble de ces points, il est difficile de dire quelle est la meilleure solution à utiliser dans le cas général - cependant, il devrait être assez clair que si le jeu est un jeu fermé "petit" de taille à peu près identique - alors le style variante montre un grand potentiel pour être plus rapide (comme le note bjarne).

Nous n'avons considéré que les performances et il existe en effet d'autres raisons de choisir l'un ou l'autre modèle : En fin de compte, il vous suffit de sortir du confort du "laboratoire" pour concevoir et évaluer vos cas d'utilisation réels.

4voto

Beached Points 559

Vous pouvez tous les faire correspondre avec une implémentation de visite si vous pouvez garantir que la variante ne sera jamais vide par exception. Voici un visiteur à visite unique qui correspond au virtuel ci-dessus et s'aligne très bien avec les tableaux 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
                                        ...);

Vous l'appelez d'abord avec la variante, puis avec les visiteurs. Voici le quickbench de la mise à jour 6 avec cet ajout Quickbench benchmark showing performance of  visit_nt . Un lien vers le banc est disponible ici http://quick-bench.com/98aSbU0wWUsym0ej-jLy1POmCBw

Ainsi, je pense que la décision de visiter ou non le site se résume à ce qui est le plus expressif et le plus clair dans l'intention. La performance peut être atteinte dans les deux cas.

2voto

ColdCat Points 599

Basé sur la mise à jour 6 http://quick-bench.com/LhdP-9y6CqwGxB-WtDlbG27o_5Y

Je suppose que nous ne pouvons pas comparer les temps mais, les uns par rapport aux autres, les résultats semblent suffisamment différents pour montrer des choix dans l'implémentation de la bibliothèque.

  • Visual 2019 v16.8.3

  • cl 19.28.29335 x64

  • compiler dans /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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X