Zusätzlich zu der hervorragenden Antwort zur Hardware- / Setup-Optimierung von @jimwise bedeutet "Linux mit geringer Latenz":
- C ++ aus Gründen des Determinismus (keine überraschende Verzögerung beim Einsetzen des GC), des Zugriffs auf Einrichtungen auf niedriger Ebene (E / A, Signale), Sprachleistung (volle Nutzung von TMP und STL, Typensicherheit).
- bevorzugen Sie Speed-over-Memory:> 512 GB RAM sind üblich; Datenbanken sind speicherinterne, im Voraus zwischengespeicherte oder exotische NoSQL-Produkte.
- Algorithmusauswahl: so schnell wie möglich im Vergleich zu vernünftigen / verständlichen / erweiterbaren, z. B. sperrfreien Mehrbit-Arrays anstelle von Array-von-Objekten-mit-Bool-Eigenschaften.
- Vollständige Nutzung von Betriebssystemfunktionen wie Shared Memory zwischen Prozessen auf verschiedenen Kernen.
- sichern. HFT-Software befindet sich normalerweise an einer Börse, sodass Malware-Möglichkeiten nicht akzeptabel sind.
Viele dieser Techniken überschneiden sich mit der Spieleentwicklung, was ein Grund dafür ist, dass die Finanzsoftwareindustrie kürzlich überflüssige Spieleprogrammierer aufnimmt (zumindest bis sie ihre Mietrückstände bezahlen).
Das zugrunde liegende Bedürfnis besteht darin, in der Lage zu sein, einen Strom von Marktdaten mit sehr hoher Bandbreite wie Wertpapierpreise (Aktien, Rohstoffe, Devisen) abzuhören und dann auf der Grundlage des Wertpapiers, des Preises, eine sehr schnelle Kauf- / Verkaufs- / Nichtstun-Entscheidung zu treffen und aktuelle Bestände.
Natürlich kann das alles auch spektakulär schief gehen .
Also werde ich auf den Punkt der Bit-Arrays näher eingehen . Nehmen wir an, wir haben ein Hochfrequenz-Handelssystem, das mit einer langen Liste von Aufträgen arbeitet (Kaufen Sie 5.000 IBM, verkaufen Sie 10.000 DELL usw.). Angenommen, wir müssen schnell feststellen, ob alle Aufträge erfüllt sind, damit wir mit der nächsten Aufgabe fortfahren können. In der traditionellen OO-Programmierung sieht dies folgendermaßen aus:
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(); } );
Die algorithmische Komplexität dieses Codes wird O (N) sein, da es sich um einen linearen Scan handelt. Werfen wir einen Blick auf das Leistungsprofil in Bezug auf Speicherzugriffe: Jede Iteration der Schleife in std :: any_of () ruft o.isFilled () auf, das inline ist, und wird so zu einem Speicherzugriff von _isFilled, 1 Byte (oder 4 abhängig von Ihrer Architektur, dem Compiler und den Compilereinstellungen) in einem Objekt von insgesamt 128 Bytes. Wir greifen also auf 1 Byte pro 128 Byte zu. Wenn wir das 1-Byte lesen, was im schlimmsten Fall angenommen wird, erhalten wir einen CPU-Datencache-Fehler. Dies führt zu einer Leseanforderung an den RAM, die eine ganze Zeile aus dem RAM liest ( siehe hier für weitere Informationen ), um nur 8 Bits auszulesen. Das Speicherzugriffsprofil ist also proportional zu N.
Vergleichen Sie dies mit:
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); }
Das Speicherzugriffsprofil davon ist unter der Annahme des schlimmsten Falls ELEMS geteilt durch die Breite einer RAM-Leitung (variiert - kann zweikanalig oder dreikanalig sein usw.).
Tatsächlich optimieren wir Algorithmen für Speicherzugriffsmuster. Es hilft keine RAM-Größe - es ist die Größe des CPU-Datencaches, die diesen Bedarf verursacht.
Hilft das?
Auf YouTube gibt es einen ausgezeichneten CPPCon-Vortrag über Programmierung mit geringer Latenz (für HFT): https://www.youtube.com/watch?v=NH1Tta7purM