Show HN: Woid – 3x Faster Runtime Polymorphism. C++23

3 pointsposted a month ago
by akopich

3 Comments

akopich

a month ago

Author here. It started when I felt virtual functions and associated heap allocations are too expensive. Then I've realized std::any doesn't work for move-only types, let alone its SBO storage isn't configurable. Meanwhile microsoft/proxy does SBO only for trivial types. And there we go...

Woid is an extremely customizable high-performance type-erasure header-only library. It provides containers like std::any, std::function and tools for non-intrusive polymorphism.

Key features:

- Performance

- Value semantics

- Move-only type support

- Duck typing

- Extreme customizability

In my current benchmarks it outperforms std::any, std::function, inheritance-based polymorphism and some well-known libraries like function2, boost::te and microsoft/proxy.

I want to make sure the comparison is fair, therefore, your advice on how to better tune the existing libraries is rather welcome.

  struct Square {
      double side;
      double area() const { return side * side; }
  };
  struct Shape : woid::InterfaceBuilder
             ::Fun<"area", [](const auto& obj) -> double { return obj.area(); } >
             ::Build {
      auto area() const { return call<"area">(); }
  };
  
  auto a = Shape{Square{1.5}}.area();
And of course I'm here to answer your questions.

hebecker

25 days ago

Looks interesting.

In my experience the typical implementations of std::function are slow-ish during construction/copying but calling an existing object is okay.

Have you compared just the performance of calling type erased functions? Is there still a difference due to cache locality?

akopich

25 days ago

Yes! https://github.com/akopich/woid/blob/main/bench/plots/FunBen...

Here I wrap std::less<int> into different wrappers (and of them manage to do SBO) and feed it into std::sort. Unless std::sort does a ton of copies, this is predominantly down to function invocation, not instantiation/lifetime management.

There Woid is up to about 60% faster than std::function if it leverages the triviality of std::less<int>. The advantage goes to 30% if don't optimize for trivial types. And either of the wrappers considered are much slower than just using the lambda directly (see the Id "wrapper") as the type-erased wrappers inhibit inlining.

I also do the same bench, but with a "big" less<int>, so that std::function fails the SBO, there the gains go up to 2-2.5x. https://github.com/akopich/woid/blob/main/bench/plots/FunBen...