I'm using a DAG (directed acyclic graph) to represent and evaluate expressions; each node represents an operation (+
, -
, /
, *
, accumulate, etc...) and evaluation of the entire expression is achieved by sequentially evaluating each node in topologically sorted order. Each node inherits for a base class RefNode
and implements a virtual function, evaluate, according to the operator it represents. The Node class is templatized on a functor that represents the operator. The node evaluation order is maintained in a vector<RefNode*>
with ->evaluate()
calls made to each element.
Some quick profiling shows that a virtual evaluate
slows down an addition node by a factor of 2x [1], either from the overhead or trashing the branch prediction.
As a first step encoded the type information as an integer an used static_cast
accordingly. This did help, but its clunky and I'd rather not jump around in the hot portion of my code.
struct RefNode {
double output;
inline virtual void evaluate(){}
};
template<class T>
struct Node : RefNode {
double* inputs[NODE_INPUT_BUFFER_LENGTH];
T evaluator;
inline void evaluate(){ evaluator(inputs, output); }
};
struct Add {
inline void operator()(double** inputs, double &output)
{
output=*inputs[0]+*inputs[1];
}
};
An evaluation may look like:
Node<Add>* node_1 = ...
Node<Add>* node_2 = ...
std::vector<RefNode*> eval_vector;
eval_vector.push_back(node_1);
eval_vector.push_back(node_2);
for (auto&& n : eval_vector) {
n->evaluate();
}
I have the following questions, bearing in mind performance is critical:
- How can I avoid virtual functions in this situation?
- If not, how can I change the way I represent an expression graph to support multiple operations, some of which must hold state, and avoid virtual function calls.
- How do other frameworks such as Tensorflow/Theano represent computational graphs?
[1] A single addition operation on my system takes ~ 2.3ns with virtual functions and 1.1ns without. While this is small, the entire computational graph is mostly addition nodes and hence there is a good portion of time to be saved.