I have the following C++ program I have been playing around with:
#include <iostream>
enum class Channel
{
Value
};
template <size_t graphIdx, size_t nodeIdx>
struct IdxToNode;
template <size_t graphIdx, size_t nodeIdx, size_t... downstreamNodeIdxs>
struct Propogate
{
static inline void propogate()
{
(IdxToNode<graphIdx, downstreamNodeIdxs>::Type::template calc<nodeIdx>(), ...);
}
};
template <size_t graphIdx, size_t nodeIdx, Channel channel, typename ReturnType>
struct Get
{
static inline const ReturnType &get()
{
using NodeType = IdxToNode<graphIdx, nodeIdx>::Type;
if constexpr (channel == Channel::Value) {
return NodeType::value_;
} else {
static_assert(false, "Invalid channel value");
}
};
};
template <size_t graphIdx>
struct Globals
{
static inline double *bufPtr_{nullptr};
};
template <size_t graphIdx, size_t nodeIdx, size_t... downstreamNodeIdxs>
struct Source
{
static inline double value_{0.0};
static inline void calc() { Propogate<graphIdx, nodeIdx, downstreamNodeIdxs...>::propogate(); }
};
template <size_t graphIdx, size_t nodeIdx, size_t lhsIdx, size_t rhsIdx, Channel lhsChannel,
Channel rhsChannel, typename OpType, OpType init>
struct Sum
{
static inline OpType value_{init};
template <size_t clock>
static inline void calc()
{
value_ = Get<graphIdx, lhsIdx, lhsChannel, OpType>::get() +
Get<graphIdx, rhsIdx, rhsChannel, OpType>::get();
}
};
template <size_t graphIdx, size_t nodeIdx, size_t aIdx, size_t inputNodeIdx, Channel channel>
struct Output
{
template <size_t clock>
static inline void calc()
{
double value = Get<graphIdx, inputNodeIdx, channel, double>::get();
Globals<graphIdx>::bufPtr_[aIdx] = value;
}
};
/***** START *****/
template<>
struct IdxToNode<0, 0>
{
using Type= Source<0, 0, 1, 2, 3, 4>;
};
template<>
struct IdxToNode<0, 1>
{
using Type = Sum<0, 1, 0, 0, Channel::Value, Channel::Value, double, 0.0>;
};
template<>
struct IdxToNode<0, 2>
{
using Type = Sum<0, 2, 0, 1, Channel::Value, Channel::Value, double, 0.0>;
};
template<>
struct IdxToNode<0, 3>
{
using Type = Sum<0, 3, 0, 2, Channel::Value, Channel::Value, double, 0.0>;
};
template <>
struct IdxToNode<0, 4>
{
using Type = Output<0, 4, 0, 3, Channel::Value>;
};
/***** END *****/
int main()
{
double x = 0;
Globals<0>::bufPtr_ = &x;
using Source = IdxToNode<0, 0>::Type;
Source::value_ = 10;
Source::calc();
std::cout << x << std::endl;
}
The objective is to link a bunch of types together at compile time so that the hot path at runtime is fast. The example here compiles very quickly but as I expand the number of intermediate Sum nodes to higher numbers things start getting really slow. More importantly the compile times seem to grow super linearly.
I made the following rough measurements:
1000 nodes -> 1.7 seconds
2000 nodes -> 5.3 seconds
3000 nodes -> 13.2 seconds
4000 nodes -> 27.7 seconds
5000 nodes -> 50.7 seconds
Ideally I would like to be able to compile with something like 1,000,000 nodes in a reasonable time (no longer than 10 minutes). If things scaled at O(N) where N is the number of nodes this seems quite achievable.
Is there something about my code that is not O(N) in terms of compile time?
12