In addition to the excellent hardware/setup tuning answer from @jimwise, "low latency linux" is implying:
- C++ for reasons of determinism (no surprise delay while GC kicks in), access to low-level facilities (I/O, signals), language power (full use of TMP and STL, type safety).
- prefer speed-over-memory: >512 Gb of RAM is common; databases are in-memory, cached up-front, or exotic NoSQL products.
- algorithm choice: as-fast-as-possible versus sane/understandable/extensible, e.g. lock-free, multiple bit arrays instead of array-of-objects-with-bool-properties.
- full use of OS facilities such as Shared Memory between processes on different cores.
- secure. HFT software is usually co-located in an Stock Exchange so malware possibilities are unacceptable.
Many of these techniques have overlap with games development which is one reason why the financial software industry absorbs any recently-redundant games programmers (at least until they pay their rent arrears).
The underlying need is to be able to listen to a very high bandwidth stream of market data such as security (stocks, commodities, fx) prices and then make a very fast buy/sell/do-nothing decision based on the security, the price and current holdings.
Of course, this can all go spectacularly wrong, too.
So i'll elaborate on the bit arrays point. Let's say we have a High Frequency Trading system that operates on a long list of Orders (Buy 5k IBM, Sell 10k DELL, etc). Let's say we need to quickly determine if all of the orders are filled, so that we can move onto the next task. In traditional OO programming, this is going to look like:
class Order {
bool _isFilled;
...
public:
inline bool isFilled() const { return _isFilled; }
};
std::vector<Order> orders;
bool needToFillMore = std::any_of(orders.begin(), orders.end(),
[](const Order & o) { return !o.isFilled(); } );
the algorithmic complexity of this code going to be O(N) as it is a linear scan. Let's take a look at the performance profile in terms of memory accesses: each iteration of the loop inside std::any_of() is going to call o.isFilled(), which is inlined, so becomes a memory access of _isFilled, 1 byte (or 4 dependending on your architecture, compiler and compiler settings) in an object of let's say 128 bytes total. So we're accessing 1 byte in every 128 bytes. When we read the 1 byte, presuming worst-case, we'll get a CPU data cache miss. This'll cause a read request to RAM which reads an entire line from RAM (see here for more info) just to read out 8 bits. So the memory access profile is proportional to N.
Compare this with:
const size_t ELEMS = MAX_ORDERS / sizeof (int);
unsigned int ordersFilled[ELEMS];
bool needToFillMore = std::any_of(ordersFilled, &ordersFilled[ELEMS+1],
[](int packedFilledOrders) { return !(packedOrders == 0xFFFFFFFF); }
the memory access profile of this, assuming worst-case again, is ELEMS divided by the width of a RAM line (varies - could be dual-channel or triple-channel, etc).
So, in effect, we're optimising algorithms for memory access patterns. No amount of RAM will help - it's the CPU data cache size that causes this need.
Does this help?
There's an excellent CPPCon talk all about low-latency programming (for HFT) on YouTube: https://www.youtube.com/watch?v=NH1Tta7purM