В дополнение к превосходному ответу по настройке оборудования / настройки от @jimwise, «linux linux с низкой задержкой» подразумевает:
- C ++ по причинам детерминизма (без неожиданной задержки при включении GC), доступа к низкоуровневым средствам (ввод / вывод, сигналы), языковой мощности (полное использование TMP и STL, безопасность типов).
- предпочтение скорости над памятью:> 512 ГБ ОЗУ является общим; базы данных находятся в памяти, кэшируются заранее или являются экзотическими продуктами NoSQL.
- выбор алгоритма: как можно быстрее против разумного / понятного / расширяемого, например, без блокировки, многобитовые массивы вместо свойств «массив объектов с булевыми».
- полное использование возможностей ОС, таких как общая память, между процессами на разных ядрах.
- ненадежно. Программное обеспечение HFT обычно размещается на фондовой бирже, поэтому возможности вредоносного ПО неприемлемы.
Многие из этих методов частично совпадают с разработкой игр, что является одной из причин того, почему индустрия финансового программного обеспечения поглощает любых недавно избыточных программистов игр (по крайней мере, до тех пор, пока они не выплатят свои долги по аренде).
Основная потребность заключается в том, чтобы иметь возможность прослушивать очень рыночный поток рыночных данных, таких как цены ценных бумаг (акции, товары, форекс), а затем принимать очень быстрое решение купить / продать / ничего не делать, основываясь на безопасности, цене и текущие авуары.
Конечно, все это тоже может пойти не так .
Поэтому я подробно остановлюсь на точке битовых массивов . Допустим, у нас есть система высокочастотной торговли, которая работает с длинным списком ордеров (купить 5 тысяч IBM, продать 10 тысяч DELL и т. Д.). Допустим, нам нужно быстро определить, все ли заказы выполнены, чтобы мы могли перейти к следующему заданию. В традиционном ОО-программировании это будет выглядеть так:
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(); } );
алгоритмическая сложность этого кода будет O (N), поскольку это линейное сканирование. Давайте посмотрим на профиль производительности с точки зрения доступа к памяти: каждая итерация цикла внутри std :: any_of () будет вызывать o.isFilled (), который является встроенным, поэтому становится доступ к памяти _isFilled, 1 байт (или 4 в зависимости от вашей архитектуры, настроек компилятора и компилятора) в объекте, скажем, всего 128 байт. Таким образом, мы получаем доступ к 1 байту на каждые 128 байтов. Когда мы прочитаем 1 байт, предполагая наихудший случай, мы получим промах кэша данных ЦП. Это вызовет запрос на чтение в ОЗУ, который считывает всю строку из ОЗУ ( см. Здесь для получения дополнительной информации ) просто для считывания 8 бит. Таким образом, профиль доступа к памяти пропорционален N.
Сравните это с:
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); }
профиль доступа к памяти этого, при условии, опять же, наихудшего случая, представляет собой ELEMS, деленные на ширину линии ОЗУ (варьируется - может быть двухканальным или трехканальным и т. д.).
Итак, по сути, мы оптимизируем алгоритмы для шаблонов доступа к памяти. Никакой объем оперативной памяти не поможет - это обусловлено размером кэша данных ЦП.
Это помогает?
На YouTube есть отличный рассказ о программировании с малой задержкой (для HFT): https://www.youtube.com/watch?v=NH1Tta7purM