Unix / Linux a bassa latenza


11

La maggior parte dei lavori di programmazione a bassa latenza / alta frequenza (in base alle specifiche del lavoro) sembrano essere implementati su piattaforme unix. In molte specifiche fanno particolare richiesta per persone con tipo di esperienza "bassa latenza linux".

Supponendo che ciò non significhi un sistema operativo Linux in tempo reale, le persone potrebbero darmi aiuto con ciò a cui questo potrebbe riferirsi? So che puoi impostare l'affinità della CPU con i thread, ma presumo che stiano chiedendo molto di più.

Ottimizzazione del kernel? (anche se ho sentito produttori come solarflare produrre comunque schede di rete di bypass del kernel)?

Che dire del DMA o eventualmente della memoria condivisa tra i processi? Se le persone potessero darmi brevi idee, posso andare a fare ricerche su Google.

(Questa domanda richiederà probabilmente qualcuno che abbia familiarità con il trading ad alta frequenza)


2
L'ottimizzazione del kernel è la strada da percorrere per rendere il sistema operativo non in tempo reale il più possibile in tempo reale. Anche il pin threading è obbligatorio. Puoi leggere di più a riguardo in questo articolo: coralblocks.com/index.php/2014/04/…
rdalmeida

Risposte:


26

Ho svolto una buona dose di lavoro a supporto dei gruppi HFT in contesti IB e Hedge Fund. Risponderò dal punto di vista di amministratore di sistema, ma parte di questo è applicabile anche alla programmazione in tali ambienti.

Ci sono un paio di cose che un datore di lavoro di solito cerca quando si riferiscono al supporto "Bassa latenza". Alcune di queste sono domande sulla "velocità non elaborata" (sai quale tipo di carta da 10 g acquistare e in quale slot inserirla?), Ma altre riguardano il modo in cui un ambiente di trading ad alta frequenza differisce da un tradizionale Ambiente Unix. Qualche esempio:

  • Unix è tradizionalmente sintonizzato per sostenere l'esecuzione di un gran numero di processi senza morire di fame nessuno di loro per le risorse, ma in un ambiente HFT, si rischia di voler eseguire un'applicazione con un minimo assoluto di overhead per la commutazione contesto, e così via. Come un piccolo esempio classico, l'attivazione dell'hyperthreading su una CPU Intel consente l'esecuzione di più processi contemporaneamente, ma ha un impatto significativo sulle prestazioni sulla velocità con cui viene eseguito ogni singolo processo. Come programmatore, dovrai anche guardare al costo di astrazioni come il threading e l'RPC e capire dove una soluzione più monolitica, sebbene meno pulita, eviterà le spese generali.

  • Il protocollo TCP / IP è in genere ottimizzato per evitare interruzioni della connessione e rendere disponibile in modo efficiente la larghezza di banda. Se il tuo obiettivo è quello di ottenere la latenza più bassa possibile da un collegamento molto veloce - invece di ottenere la massima larghezza di banda possibile da un collegamento più vincolato - vorrai regolare l'ottimizzazione dello stack di rete. Dal punto di vista della programmazione, vorrai anche esaminare le opzioni socket disponibili e capire quali sono le impostazioni predefinite più ottimizzate per la larghezza di banda e l'affidabilità rispetto alla riduzione della latenza.

  • Come per le reti, quindi con l'archiviazione: vorrai sapere come distinguere un problema di prestazioni dell'archiviazione da un problema dell'applicazione e scoprire quali schemi di utilizzo I / O hanno meno probabilità di interferire con le prestazioni del tuo programma (come esempio, scopri dove la complessità dell'utilizzo dell'IO asincrono può ripagarti e quali sono gli svantaggi).

  • Infine, e più dolorosamente: noi amministratori Unix desideriamo quante più informazioni possibili sullo stato degli ambienti che monitoriamo, quindi ci piace eseguire strumenti come agenti SNMP, strumenti di monitoraggio attivi come Nagios e strumenti di raccolta dati come sar (1). In un ambiente in cui gli switch di contesto devono essere assolutamente minimizzati e l'uso di IO del disco e della rete strettamente controllati, tuttavia, dobbiamo trovare il giusto compromesso tra le spese di monitoraggio e le prestazioni bare metal delle scatole monitorate. Allo stesso modo, quali tecniche stai usando per rendere la codifica più semplice ma ti stanno costando le prestazioni?

Infine, ci sono altre cose che arrivano col tempo; trucchi e dettagli che impari con l'esperienza. Ma questi sono più specializzati (quando uso epoll? Perché due modelli di server HP con controller PCIe teoricamente identici funzionano in modo così diverso?), Più legati a tutto ciò che il tuo negozio specifico sta usando e più probabilità di cambiare da un anno all'altro .


1
Grazie, anche se ero interessato a una risposta di programmazione, questo è stato molto utile e informativo.
user997112,

5
@ user997112 Questa è una risposta di programmazione. Se non sembra tale, continua a leggerlo fino a quando non lo fa :)
Tim Post

15

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


"array di bit multipli invece di array-of-object-with-bool-properties" cosa intendi con questo?
user997112,

1
Ho elaborato con esempi e collegamenti.
JBR Wilkinson,

Andando oltre, anziché utilizzare un intero byte per indicare se un ordine è stato completato o meno, è possibile utilizzare solo un singolo bit. Quindi, in una singola cache (64 byte), potresti rappresentare lo stato di 256 ordini. Quindi - meno miss.
Quixver,

Inoltre, se stai eseguendo scansioni lineari di memoria, il prefetcher hardware fa un ottimo lavoro nel caricare i tuoi dati. A condizione che tu acceda alla memoria in sequenza o camminando o qualcosa di semplice. Ma se si accede alla memoria in qualsiasi modo non sequenziale, il prefetcher della CPU viene confuso. Ad esempio una ricerca binaria. A quel punto il programmatore può aiutare la cpu con suggerimenti - _mm_prefetch.
Quixver,

-2

Dato che non avevo messo in produzione uno o due software ad alta frequenza, direi le cose più importanti:

  1. La configurazione hardware e gli amministratori di sistema insieme agli ingegneri di rete NON definiscono un buon esito del numero di ordini elaborati dal sistema di trading, ma possono declassarlo alla grande se non conoscono le basi di cui sopra.
  2. L'unica persona che crea effettivamente il sistema per fare trading ad alta frequenza è un informatico che mette insieme il codice in c ++

    Tra le conoscenze utilizzate c'è

    A. Operazioni di confronto e scambio.

    • come CAS viene utilizzato nel processore e come il computer lo supporta per essere utilizzato nella cosiddetta elaborazione della struttura senza blocco. O elaborazione senza blocco. Non entrerò a scrivere un intero libro qui. In breve il compilatore GNU e il compilatore Microsoft supportano l'uso diretto delle istruzioni CAS. Permette al tuo codice di avere "No.Wair" durante l'estrazione dell'elemento dalla coda o inserendone uno nuovo nella coda.
  3. Lo scienziato di talento ne utilizzerà di più. Dovrebbe trovare nei nuovi "modelli" recenti quello che è apparso per primo in Java. Chiamato modello DISRUPTOR. Piegare nello scambio LMAX in Europa ha spiegato alla comunità ad alta frequenza che l'utilizzo basato su thread nei processori moderni perderebbe il tempo di elaborazione sul rilascio della cache di memoria da parte della CPU se la coda daya non fosse allineata con la dimensione della cache della CPU moderna = 64

    Quindi, per quella lettura, hanno reso pubblico un codice Java che consente al processo multi-threading di utilizzare correttamente la cache della CPU hardware senza risoluzioni di conflitto. E il bravo scienziato informatico DEVE scoprire che quel modello era già stato portato su c ++ o eseguiva il porting stesso.

    Questa è una competenza ben oltre ogni configurazione di amministratore. Questo è nel vero cuore dell'alta frequenza oggi.

  4. Il ragazzo dell'informatica DEVE scrivere molto codice C ++ non solo per aiutare gli addetti al controllo qualità. Ma anche a
    • convalidare in termini di comprovata velocità raggiunta dai trader
    • condannare ha usato diverse vecchie tecnologie ed esponendole con il proprio codice per dimostrare che non riescono a produrre buoni risultati
    • scrivere il proprio codice c ++ di comunicazione multi-threading basato su pupe comprovata / selezionare la velocità del kernel invece di utilizzare nuovamente le vecchie tecnologie. Vi farò un esempio: la moderna libreria tcp è ICE. E le persone che lo hanno fatto sono brillanti. Ma le loro priorità erano nel campo della compatibilità con molte lingue. Così. Puoi fare di meglio in c ++. Quindi cerca gli esaples con le massime prestazioni in base alla selezione ASYNCHRONOUS. E non andare per più consumatori più produttori - non per HF.
      E sarai sorpreso di scoprire che pipe viene usata SOLO PER la notifica del kernel del messaggio arrivato. È possibile inserire qui il numero del messaggio a 64 bit, ma per il contenuto si passa alla coda CAS senza blocco. Attivato dalla select()chiamata asincrona del kernel .
    • inoltre. Scopri come assegnare con l'affinità di thread c ++ al thread che esegue il piping / accodamento dei messaggi. Quel thread dovrebbe avere affinità di base. Nessun altro dovrebbe usare lo stesso numero di core della CPU.
    • e così via.

Come puoi vedere, l'alta frequenza è un CAMPO IN VIA DI SVILUPPO. Non puoi essere solo un programmatore C ++ per avere successo.

E quando dico di avere successo, intendo che l'hedge fund per cui lavoreresti riconoscerà gli sforzi del tour in compensi annuali oltre il numero di persone e reclutatori di cui parlano.

I tempi delle semplici domande frequenti su costruttore / distruttore sono andati per sempre. E c ++ ... stesso è migrato con nuovi compilatori per liberarti dalla gestione della memoria e per imporre la non ereditarietà di grande profondità nelle classi. Perdita di tempo. Il paradigma del riutilizzo del codice è cambiato. Non si tratta solo di quante lezioni hai fatto in polimorfo. Si tratta di prestazioni temporali confermate direttamente del codice che è possibile riutilizzare.

Quindi è la tua scelta di entrare nella curva di apprendimento lì o no. Non colpirà mai un segnale di stop.


6
Potresti voler impegnarti in ortografia e formattazione. Nella sua forma attuale, questo post è appena comprensibile.
CodesInChaos,

1
Descrivi la situazione di 10 anni fa. Al giorno d'oggi le soluzioni basate su hardware superano facilmente il C ++ puro, non importa quanto sia ottimizzato il C ++.
Sjoerd,

Per coloro che vogliono sapere che cos'è una soluzione basata su hardware - si tratta principalmente di soluzioni FPGA in cui il codice viene effettivamente masterizzato nella memoria veloce e non viene modificato senza il ripristino della cosiddetta memoria ROM. Sola lettura
alex p

@alexp Chiaramente non sai di cosa stai parlando. FPGA è qualcosa di diverso dal "codice masterizzato nella memoria veloce".
Sjoerd,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.