Oltre all'eccellente risposta di ottimizzazione hardware / setup di @jimwise, "Linux a bassa latenza" implica:
- C ++ per motivi di determinismo (nessun ritardo a sorpresa durante l'avvio di GC), accesso a strutture di basso livello (I / O, segnali), potenza del linguaggio (pieno utilizzo di TMP e STL, sicurezza del tipo).
- preferire lo speed-over-memory:> 512 Gb di RAM sono comuni; i database sono prodotti NoSQL in memoria, memorizzati nella cache o esotici.
- scelta dell'algoritmo: il più veloce possibile rispetto a sano / comprensibile / estensibile, ad esempio array a bit multipli senza lock, invece di array di oggetti con proprietà bool.
- pieno utilizzo delle funzionalità del sistema operativo come la memoria condivisa tra processi su diversi core.
- sicuro. Il software HFT si trova di solito in una borsa valori, quindi le possibilità di malware sono inaccettabili.
Molte di queste tecniche si sovrappongono allo sviluppo di giochi, motivo per cui l'industria del software finanziario assorbe eventuali programmatori di giochi recentemente ridondanti (almeno fino a quando non pagano gli arretrati di affitto).
L'esigenza di fondo è quella di essere in grado di ascoltare un flusso di larghezza di banda molto elevato di dati di mercato come i prezzi di sicurezza (azioni, materie prime, fx) e quindi prendere una decisione molto veloce di acquisto / vendita / non fare nulla in base alla sicurezza, al prezzo e partecipazioni correnti.
Ovviamente, anche questo può andare storto .
Quindi approfondirò il punto delle matrici . Supponiamo di avere un sistema di trading ad alta frequenza che opera su un lungo elenco di ordini (Compra 5k IBM, Vendi 10k DELL, ecc.). Diciamo che dobbiamo determinare rapidamente se tutti gli ordini vengono evasi, in modo da poter passare all'attività successiva. Nella programmazione OO tradizionale, questo sarà simile a:
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(); } );
la complessità algoritmica di questo codice sarà O (N) in quanto è una scansione lineare. Diamo un'occhiata al profilo delle prestazioni in termini di accessi alla memoria: ogni iterazione del ciclo all'interno di std :: any_of () chiamerà o.isFilled (), che è in linea, quindi diventa un accesso alla memoria di _isFilled, 1 byte (o 4 a seconda delle impostazioni dell'architettura, del compilatore e del compilatore) in un oggetto di un totale di 128 byte. Quindi stiamo accedendo a 1 byte ogni 128 byte. Quando leggiamo il byte 1, presumendo il caso peggiore, avremo un errore nella cache dei dati della CPU. Ciò causerà una richiesta di lettura alla RAM che legge un'intera riga dalla RAM ( vedi qui per maggiori informazioni ) solo per leggere 8 bit. Quindi il profilo di accesso alla memoria è proporzionale a N.
Confronta questo con:
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); }
il profilo di accesso alla memoria di questo, supponendo il caso peggiore, è ELEMS diviso per la larghezza di una linea RAM (varia - potrebbe essere a doppio canale o triplo canale, ecc.).
Quindi, in effetti, stiamo ottimizzando gli algoritmi per i modelli di accesso alla memoria. Nessuna quantità di RAM sarà di aiuto: è la dimensione della cache dei dati della CPU che causa questa necessità.
questo aiuta?
C'è un eccellente CPPCon talk tutto sulla programmazione a bassa latenza (per HFT) su YouTube: https://www.youtube.com/watch?v=NH1Tta7purM