n1r44
n1r44

Reputation: 581

C++17 std::variant is slower than dynamic polymorphism?

I was following this blog and was trying to replace a dynamic polymorphism code into using std::variant and std::visit. But I can not get std::variant + std::visit to work better than a virtual struct impl. Its about 1.3-1.5x slower! (GCC 10.3 -O3 C++17)

The use case is as follows. Say we are comparing i'th and j'th row of two tables. A table could have heterogeneously typed columns. Assume we can access column buffers. What I am doing is testing,

def IndexEqual(Table:A, Table:B, int:i, int:j):
  for c in range(A.num_cols):
     if not A.column(c)[i] == B.column(c)[j]:
         return False
  return True

for dynamic polymorphism I have the following for int and float

struct Comp{
  virtual bool comp(size_t i, size_t j) const = 0;
};

struct CompI: public Comp {
  CompI(const int *data_1, const int *data_2) : data1(data_1), data2(data_2) {}

  const int *data1, *data2;
  bool comp(size_t i, size_t j) const override {
    return data1[i] == data2[j];
  }
};

struct CompF: public Comp {
  CompF(const float *data_1, const float *data_2) : data1(data_1), data2(data_2) {}

  const float *data1, *data2;
  bool comp(size_t i, size_t j) const override {
    return data1[i] == data2[j];
  }
};

bool IndexEqual1(const std::vector<Comp *> &comps, size_t i, size_t j) {
  for (auto &&a: comps) {
    if (!a->comp(i, j)) {
      return false;
    }
  }
  return true;
}

This was transformed to std::variant + std::visit as follows.

struct EqualToI {
  EqualToI(const int *data_1, const int *data_2) : data1(data_1), data2(data_2) {}
  const int *data1, *data2;
  bool comp(size_t i, size_t j) const {
    return data1[i] == data2[j];
  }
};

struct EqualToF {
  EqualToF(const float *data_1, const float *data_2) : data1(data_1), data2(data_2) {}
  const float *data1, *data2;
  bool comp(size_t i, size_t j) const {
    return data1[i] == data2[j];
  }
};

using var_type = typename std::variant<EqualToI, EqualToF>;

bool IndexEqual(const std::vector<var_type> &comps, size_t i, size_t j) {
  for (auto &&a: comps) {
    if (!std::visit([&](const auto &comp) {
      return comp.comp(i, j);
    }, a)) {
      return false;
    }
  }
  return true;
}

I benchmarked this here https://quick-bench.com/q/u-cBjg4hyQjOs6fKem9XSdW7LMs

Can someone one please explain why this std::variant + std::visit option is slower than the dynamic polimorphism approach? I was expecting otherwise! Is there a problem in my approach and/or benchmark?

Upvotes: 3

Views: 2468

Answers (2)

oldcb
oldcb

Reputation: 1

In theory, using std::variant and std::visit to implement dynamic polymorphism should not be slower than the implementation based on virtual functions.

In your approach, the lambda function used in the following code

if (!std::visit([&](const auto &comp) {
  return comp.comp(i, j);
}, a)) {
  return false;
}

repeatedly copies the pointers of i and j in the std::visit function, which is the main reason for the performance difference.

Upvotes: 0

Nicol Bolas
Nicol Bolas

Reputation: 474336

Using variant does not constitute "static polymorphism". It is still dynamic polymorphism because the compiler does not know which type will actually inhabit the variant. Therefore, the code in question must attempt to figure out what the variant stores at runtime, and it must dispatch those calls accordingly.

Note that the article you link to also doesn't call it "static polymorphism". It emphasizes that it is merely a different form of "runtime polymorphism" from virtual functions.

Upvotes: 5

Related Questions