Oltre all'eccellente risposta all'ottimizzazione hardware / setup di @jimwise, "linux a bassa latenza" sta implicando:
- C ++ per ragioni di determinismo (nessun ritardo a sorpresa mentre GC entra in gioco), accesso a strutture di basso livello (I / O, segnali), potenza della lingua (pieno utilizzo di TMP e STL, sicurezza di tipo).
- preferisci la velocità di memoria: > 512 Gb di RAM sono comuni; i database sono in-memory, cache nascoste o esotici prodotti NoSQL.
Scelta dell'algoritmo - : il più veloce possibile rispetto a sano / comprensibile / estensibile, ad es. array di bit multipli senza blocco anziché proprietà array-of-objects-with-bool.
- pieno utilizzo delle funzionalità del sistema operativo come la memoria condivisa tra processi su diversi core.
- sicuro. Il software HFT è solitamente collocato in una borsa valori, pertanto le possibilità di malware sono inaccettabili.
Molte di queste tecniche si sono sovrapposte allo sviluppo dei giochi e questo è uno dei motivi per cui il settore del software finanziario assorbe programmatori di giochi ridondanti di recente (almeno fino a quando non pagano gli arretrati).
L'esigenza di fondo è quella di essere in grado di ascoltare un flusso molto elevato di dati di mercato come la sicurezza (scorte, materie prime, fx) e quindi prendere una decisione di acquisto / vendita / rinunciare molto veloce basata sulla sicurezza , il prezzo e le disponibilità attuali.
Naturalmente, tutto questo può andare in modo spettacolare sbagliato , anche.
Quindi elaborerò il punto matrici bit . Diciamo che abbiamo un sistema di trading ad alta frequenza che opera su una lunga lista di ordini (Acquista 5k IBM, Vendi 10k DELL, ecc.). Diciamo che dobbiamo determinare rapidamente se tutti gli ordini sono stati completati, in modo che possiamo passare all'attività successiva. Nella programmazione OO tradizionale, questo assomiglierà 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 dell'architettura, del compilatore e delle impostazioni del compilatore) in un oggetto di diciamo 128 byte totali. Quindi stiamo accedendo a 1 byte ogni 128 byte. Quando leggiamo l'1 byte, presumendo il caso peggiore, avremo una perdita della 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 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, assumendo di nuovo il caso peggiore, è ELEMS diviso per la larghezza di una linea RAM (varia - potrebbe essere doppio canale o triplo canale, ecc.)
Quindi, in effetti, stiamo ottimizzando gli algoritmi per i modelli di accesso alla memoria. Nessuna quantità di RAM aiuterà - è la dimensione della cache dei dati della CPU che causa questa necessità.
Questo aiuto?
C'è un CPPCon eccellente che parla di programmazione a bassa latenza (per HFT) su YouTube: link