На додаток до чудової відповіді на налаштування обладнання та налаштування від @jimwise, "Linux з низькою затримкою" означає:
- C ++ з міркувань детермінізму (несподівана затримка під час запуску GC), доступ до об'єктів низького рівня (введення / виведення, сигнали), мовна потужність (повне використання TMP та STL, тип безпеки).
- віддайте перевагу швидкості над пам'яттю:> 512 Gb оперативної пам’яті є загальним; бази даних - це пам'ять, кешована наперед або екзотичні продукти NoSQL.
- вибір алгоритму: якнайшвидший як можливо порівняно з розумним / зрозумілим / розширюваним, наприклад, без блокування, декілька бітових масивів замість властивостей масиву об’єктів-з-bool-властивостями.
- повне використання засобів ОС, таких як Спільна пам'ять між процесами на різних ядрах.
- захищений. Програмне забезпечення HFT зазвичай розташоване на фондовій біржі, тому можливості зловмисного програмного забезпечення неприйнятні.
Багато з цих методів перетинаються з розвитком ігор, що є однією з причин, чому галузь фінансового програмного забезпечення поглинає будь-яких нещодавно зайвих програмістів ігор (принаймні, поки вони не сплатять заборгованість за оренду).
Основна потреба полягає в тому, щоб мати можливість прослуховувати дуже високий пропускну здатність потоку ринкових даних, таких як цінність цінних паперів (акції, товари, валюта), а потім дуже швидко приймати рішення про покупку / продаж / не робити нічого, виходячи з цінних паперів, ціна та поточні фонди.
Звичайно, це теж може піти напрочуд неправильно .
Тож я детальніше розробив точку масивів бітів . Скажімо, у нас є високочастотна система торгівлі, яка працює за довгим списком замовлень (Купіть 5k IBM, продайте 10k DELL тощо). Скажімо, нам потрібно швидко визначити, чи всі замовлення заповнені, щоб ми могли перейти до наступного завдання. У традиційному програмуванні OO це буде виглядати так:
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 біт. Отже профіль доступу до пам'яті пропорційний Н.
Порівняйте це з:
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