I am trying to implement a stock matching engine/order book in C++, and am searching for a more cache friendly architecture. Currently, my data structures are as follows:
- An intrusive rb-tree for the limit prices.
- An intrusive doubly linked list for holding the orders at the limit prices.
I have thought about ways to replace the rb-tree, such as a sparse array of limit prices which are themselves linked, but I believe the rb-tree is a better use case as I'm dealing with a sparse book. Now, for the doubly linked list, I have thought about using an array. Aside from resizing it if it fills up, appending and traversing would be optimal, but deletion would require either shifting or skipping deleted entries. I have also considered an unrolled linked list, but from my research and testing, it seems that it works much better when entries are a couple of bytes instead of larger Order structure.
Are there any other data structures anyone could point me to, particularly to optimize the cache friendliness?
On another note, if I used a LIFO stack as a memory pool and provided the doubly linked quote lists with objects from this stack to reuse recently deleted quotes, it would preserve cache-time locality, but not necessarily spatial locality. Are my instincts correct in this?
Also, I've tried to do quite a bit of testing and analyzing the cache with perf stat in linux, but it hasn't been easy. If someone has any more tips on how to do cache analysis, they would be more than welcome.
Lastly, please no comments on premature optimization. I am doing this largely as an exercise and to learn more. This project is not for production and I don't have a completion timeline. Thanks!
Edit for some more clarity, this is similar to my current implementation, originally taken from https://web.archive.org/web/20110219163448/http://howtohft.wordpress.com/2011/02/15/how-to-build-a-fast-limit-order-book/:
There are three main operations that a limit order book (LOB) has to implement: add, cancel, and execute. The goal is to implement these operations in O(1) time while making it possible for the trading model to efficiently ask questions like “what are the best bid and offer?”, “how much volume is there between prices A and B?” or “what is order X’s current position in the book?”
The vast majority of the activity in a book is usually made up of add and cancel operations as market makers jockey for position, with executions a distant third (in fact I would argue that the bulk of the useful information on many stocks, particularly in the morning, is in the pattern of adds and cancels, not executions, but that is a topic for another post). An add operation places an order at the end of a list of orders to be executed at a particular limit price, a cancel operation removes an order from anywhere in the book, and an execution removes an order from the inside of the book (the inside of the book is defined as the oldest buy order at the highest buying price and the oldest sell order at the lowest selling price). Each of these operations is keyed off an id number (Order.idNumber in the pseudo-code below), making a hash table a natural structure for tracking them.
Order
int idNumber;
bool buyOrSell;
int shares;
int limit;
int entryTime;
int eventTime;
Order *nextOrder;
Order *prevOrder;
Limit *parentLimit;
Limit // representing a single limit price
int limitPrice;
int size;
int totalVolume;
Limit *parent;
Limit *leftChild;
Limit *rightChild;
Order *headOrder;
Order *tailOrder;
Book
Limit *buyTree;
Limit *sellTree;
Limit *lowestSell;
Limit *highestBuy;
The idea is to have a binary tree of Limit objects sorted by limitPrice, each of which is itself a doubly linked list of Order objects. Each side of the book, the buy Limits and the sell Limits, should be in separate trees so that the inside of the book corresponds to the end and beginning of the buy Limit tree and sell Limit tree, respectively. Each order is also an entry in a map keyed off idNumber, and each Limit is also an entry in a map keyed off limitPrice.
With this structure you can easily implement these key operations with good performance:
- Add – O(log M) for the first order at a limit, O(1) for all others
- Cancel – O(1)
- Execute – O(1)
- GetVolumeAtLimit – O(1)
- GetBestBid/Offer – O(1)