Eduard Rostomyan
Eduard Rostomyan

Reputation: 6546

How really distinguish compile time and run time information?

This question might seem broad, but it really isn't.
More specifically, if we are using templates, how do we distinguish what is compile time information and run time information?

I was watching CppCon video on the optimization and HFT systems and came across this example. If you are not familiar with high frequency trading systems, and insight is that they are really trying to squeeze the last few nanosecond from the benchmark, often violating the classy OOD principles in order to gain performance.

The example I want more to elaborate on is the following:

template<Side T>
void Strategy<T>::run()
{
    const float orderPrice = calcPrice(fairValue, credit);
    //....
    sendOrder(orderPrice);
}

template<>
float Strategy<Side::buy>::calcPrice(float value, float credit)
{
    return value - credit;
}

template<>
float Strategy<Side::sell>::calcPrice(flaot value, float credit)
{
    return value + credit;
}

According to the author, this example illustrates a classical way of avoiding the run time if-else branching.
But at the time of compilation, we don't really know whether we are going to buy or cell right? The decision is made during the run time depending on market movement.
Can someone please explain how the link between run time and compile time, specifically, how we are creating a compile time branching depending on the run time information?

Upvotes: 1

Views: 89

Answers (3)

t.niese
t.niese

Reputation: 40842

For this simplified example you show, it probably might not make much of a difference.

But for HFT you have a task that is either sell or buy and all operations for that task should be done with as little latency as possible. And you want to get the best optimizations for each of these tasks.

Those two code flows might be similar in many parts. But if you have a larger code flow for which you would have multiple if conditions that check whether your sell or buy, then you have two possibilities:

  1. you can hope that the optimizer of the compiler will create two distinct code flows, one for selling and one for buying and reducing the branching possibilities and those the latency. A good compiler might be indeed able to do that, but there is no guarantee for that, and doing some changes to that code might have the result that the compiler for some reason stops doing that optimization.

  2. create a construct as shown that utilizes templates. This will have the guarantee that you only have one check for sell and one for buy at the point where you decide which of those tasks you want to execute. And you don't need to rely on the compiler to do that optimization for you, and you still prevent code duplication in your source code.

2. has the additional benefit that the compiler might be able to do even further optimizations like inlining, instruction reordering, … because you remove any additional checks for buy/sell after you did that check once. And reducing the branching possibilities can also help the branch prediction of the CPU.

Upvotes: 1

MSalters
MSalters

Reputation: 179819

You're right on the general principle: at compile time, you won't know whether to sell or buy. That's going to take an if-statement.

However, the technique used there is still useful when the single if-statement to control buy/sell controls two large blocks of similar code. You can now write out those blocks explicitly (code duplication), write it once but repeat the if-statement for all the parts where the two blocks would diverge (slow), or use templates to have the compiler generate two blocks of code for you.

I have a even better example in my own code base, where I use a switch outside to choose one of 7 template instantiations. Each template instantiation is a loop over hundreds of elements, and the switch outside means I don't have the switch inside the loop. Sure, I have 7 template instantiations, but RAM is cheap.

Upvotes: 1

Aykhan Hagverdili
Aykhan Hagverdili

Reputation: 29975

The Strategy is specialized for buy and sell cases. So, once you decide whether you're buying or selling, you use one or the other and there are no more branches inside the class methods. It's essentially generating 2 code paths and you pick one of them with a single branch:

if (buying) {
  Strategy<Side::buy> strat;
  strat.run();
} else {
  Strategy<Side::sell> strat;
  strat.run();
}

Upvotes: 2

Related Questions