Unix / Linux à faible latence

11

La plupart des travaux de programmation à faible latence / haute fréquence (basés sur les spécifications du travail) semblent être implémentés sur les plates-formes Unix. Dans beaucoup de spécifications, ils font une demande particulière pour les personnes ayant une expérience de type "Linux à faible latence".

En supposant que cela ne signifie pas un système d'exploitation Linux en temps réel, les gens pourraient-ils m'aider à ce à quoi cela pourrait faire référence? Je sais que vous pouvez définir l'affinité CPU pour les threads, mais je suppose qu'ils demandent beaucoup plus que cela.

Réglage du noyau? (même si j'ai entendu des fabricants comme solarflare produire des cartes réseau de contournement du noyau de toute façon)?

Qu'en est-il du DMA ou éventuellement de la mémoire partagée entre les processus? Si les gens pouvaient me donner de brèves idées, je peux aller faire des recherches sur Google.

(Cette question nécessitera probablement une personne familière avec le trading haute fréquence)

user997112
la source
2
Le réglage du noyau est la voie à suivre pour rendre un système d'exploitation non en temps réel aussi en temps réel que possible. L'épinglage du fil est également obligatoire. Vous pouvez en savoir plus à ce sujet dans cet article: coralblocks.com/index.php/2014/04/…
rdalmeida
Également lié: stackoverflow.com/q/15702601/632951
Pacerier

Réponses:

26

J'ai fait pas mal de travail pour soutenir les groupes HFT dans les milieux IB et Hedge Fund. Je vais répondre à partir de la vue sysadmin, mais une partie de cela s'applique également à la programmation dans de tels environnements.

Il y a deux ou trois choses qu'un employeur recherche habituellement lorsqu'il se réfère à un soutien «à faible latence». Certaines d'entre elles sont des questions de "vitesse brute" (savez-vous quel type de carte 10g acheter et dans quel emplacement la mettre?), Mais la plupart concernent les différences entre un environnement de trading à haute fréquence et un environnement traditionnel. Environnement Unix. Quelques exemples:

  • Unix est traditionnellement réglé pour prendre en charge l'exécution d'un grand nombre de processus sans aucun d'entre eux pour les ressources, mais dans un environnement HFT, vous souhaiterez probablement exécuter une application avec un minimum absolu de surcharge pour le changement de contexte, etc. À titre d'exemple classique, l'activation de l'hyperthreading sur un processeur Intel permet à plus de processus de s'exécuter à la fois - mais a un impact significatif sur les performances de la vitesse à laquelle chaque processus individuel est exécuté. En tant que programmeur, vous devrez également examiner le coût des abstractions telles que le filetage et le RPC, et découvrir où une solution plus monolithique - tout en étant moins propre - évitera les frais généraux.

  • TCP / IP est généralement réglé pour éviter les interruptions de connexion et utiliser efficacement la bande passante disponible. Si votre objectif est d'obtenir la latence la plus faible possible d'une liaison très rapide - au lieu d'obtenir la bande passante la plus élevée possible d'une liaison plus contrainte - vous allez vouloir ajuster le réglage de la pile réseau. Du côté de la programmation, vous souhaiterez également examiner les options de socket disponibles et déterminer celles dont les paramètres par défaut sont plus adaptés à la bande passante et à la fiabilité qu'à la réduction de la latence.

  • Comme pour la mise en réseau, donc pour le stockage - vous allez vouloir savoir comment distinguer un problème de performances de stockage d'un problème d'application et apprendre quels modèles d'utilisation des E / S sont les moins susceptibles d'interférer avec les performances de votre programme (en tant que par exemple, découvrez où la complexité de l'utilisation d'E / S asynchrones peut vous rapporter et quels sont les inconvénients).

  • Enfin, et plus douloureusement: nous, les administrateurs Unix, voulons autant d' informations que possible sur l'état des environnements que nous surveillons, nous aimons donc exécuter des outils comme les agents SNMP, des outils de surveillance actifs comme Nagios et des outils de collecte de données comme sar (1). Dans un environnement où les changements de contexte doivent être absolument minimisés et l'utilisation des E / S disque et réseau étroitement contrôlée, nous devons cependant trouver le bon compromis entre les frais de surveillance et les performances nues des boîtiers surveillés. De même, quelles techniques utilisez-vous qui facilitent le codage mais vous coûtent les performances?

Enfin, il y a d'autres choses qui viennent avec le temps; astuces et détails que vous apprenez avec l'expérience. Mais ceux-ci sont plus spécialisés (quand dois-je utiliser epoll? Pourquoi deux modèles de serveur HP avec des contrôleurs PCIe théoriquement identiques fonctionnent-ils si différemment?), Plus liés à ce que votre magasin spécifique utilise, et plus susceptibles de changer d'une année à l'autre .

Jimwise
la source
1
Merci, même si j'étais intéressé par une réponse de programmation, c'était très utile et instructif.
user997112
5
@ user997112 Ceci est une réponse de programmation. Si cela ne semble pas comme tel, continuez à le lire jusqu'à ce que ce soit le cas :)
Tim Post
15

En plus de l'excellente réponse de réglage matériel / configuration de @jimwise, "Linux à faible latence" implique:

  • C ++ pour des raisons de déterminisme (pas de retard surprenant au démarrage du GC), accès à des installations de bas niveau (E / S, signaux), puissance du langage (utilisation complète de TMP et STL, sécurité de type).
  • préférez la vitesse par rapport à la mémoire:> 512 Go de RAM sont courants; les bases de données sont des produits NoSQL en mémoire, mis en cache en amont ou exotiques.
  • choix de l'algorithme: aussi rapide que possible par rapport à sain d'esprit / compréhensible / extensible, par exemple des tableaux à plusieurs bits sans verrouillage au lieu d'un tableau d'objets avec des propriétés booléennes.
  • utilisation complète des fonctionnalités du système d'exploitation telles que la mémoire partagée entre les processus sur différents cœurs.
  • sécurise. Le logiciel HFT est généralement colocalisé dans une bourse de sorte que les possibilités de logiciels malveillants sont inacceptables.

Beaucoup de ces techniques se chevauchent avec le développement de jeux, ce qui est l'une des raisons pour lesquelles l'industrie des logiciels financiers absorbe les programmeurs de jeux récemment redondants (au moins jusqu'à ce qu'ils paient leurs arriérés de loyer).

Le besoin sous-jacent est de pouvoir écouter un flux de données de marché très large bande passante telles que les prix des titres (actions, matières premières, fx), puis de prendre une décision d'achat / vente / de ne rien faire très rapide en fonction de la sécurité, du prix et les avoirs actuels.

Bien sûr, tout cela peut aussi mal tourner de façon spectaculaire .


Je vais donc développer le point des tableaux de bits . Disons que nous avons un système de trading à haute fréquence qui fonctionne sur une longue liste d'ordres (acheter 5k IBM, vendre 10k DELL, etc.). Disons que nous devons déterminer rapidement si toutes les commandes sont remplies, afin de pouvoir passer à la tâche suivante. Dans la programmation OO traditionnelle, cela ressemblera à ceci:

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 complexité algorithmique de ce code va être O (N) car c'est un balayage linéaire. Jetons un coup d'œil au profil de performance en termes d'accès à la mémoire: chaque itération de la boucle à l'intérieur de std :: any_of () va appeler o.isFilled (), qui est en ligne, devient ainsi un accès à la mémoire de _isFilled, 1 octet (ou 4 selon votre architecture, le compilateur et les paramètres du compilateur) dans un objet de disons 128 octets au total. Nous accédons donc à 1 octet sur 128 octets. Lorsque nous lisons le 1 octet, en supposant le pire des cas, nous obtiendrons un cache de données CPU manquant. Cela provoquera une demande de lecture vers la RAM qui lit une ligne entière de la RAM ( voir ici pour plus d'informations ) juste pour lire 8 bits. Le profil d'accès à la mémoire est donc proportionnel à N.

Comparez cela avec:

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); }

le profil d'accès à la mémoire de celui-ci, en supposant à nouveau le pire des cas, est ELEMS divisé par la largeur d'une ligne RAM (varie - pourrait être double canal ou triple canal, etc.).

Donc, en fait, nous optimisons les algorithmes pour les modèles d'accès à la mémoire. Aucune quantité de RAM ne sera utile - c'est la taille du cache de données CPU qui provoque ce besoin.

est-ce que cela aide?


Il y a un excellent CPPCon parler de la programmation à faible latence (pour HFT) sur YouTube: https://www.youtube.com/watch?v=NH1Tta7purM

JBRWilkinson
la source
"tableaux multi-bits au lieu de tableaux-d'objets-avec-propriétés-booléennes" que voulez-vous dire par là?
user997112
1
J'ai élaboré avec des exemples et des liens.
JBRWilkinson
Pour aller plus loin - plutôt que d'utiliser un octet entier pour indiquer si une commande est exécutée ou non - vous pouvez simplement utiliser un seul bit. Donc, dans une seule ligne de cache (64 octets) - vous pourriez représenter l'état de 256 commandes. Donc - moins de ratés.
quixver
De plus - si vous effectuez des analyses linéaires de la mémoire - le préfetcher matériel fait un excellent travail de chargement de vos données. À condition d'accéder à la mémoire de manière séquentielle ou à pas ou quelque chose de simple. Mais si vous accédez à la mémoire de quelque manière que ce soit de manière non séquentielle - le préfet du CPU devient confus. Par exemple, une recherche binaire. À ce stade, le programmeur peut aider le processeur avec des conseils - _mm_prefetch.
quixver
-2

Comme je n'avais pas mis en production un ou deux logiciels haute fréquence, je dirais les choses les plus importantes:

  1. La configuration matérielle et les administrateurs système, ainsi que les ingénieurs réseau, ne définissent PAS un bon résultat du nombre d'ordres traités par le système commercial, mais ils peuvent le rétrograder considérablement s'ils ne connaissent pas les bases décrites ci-dessus.
  2. La seule personne qui fabrique réellement le système pour faire du trading haute fréquence est un informaticien qui assemble le code en c ++

    Parmi les connaissances utilisées figure

    A. Comparer et échanger des opérations.

    • comment CAS est utilisé dans le processeur et comment l'ordinateur le prend en charge pour être utilisé dans ce qu'on appelle le traitement de structure sans verrouillage. Ou Traitement sans verrouillage. Je n'entrerai pas dans l'écriture d'un livre entier ici. En bref, le compilateur GNU et le compilateur Microsoft prennent en charge l'utilisation directe des instructions CAS. Il permet à votre code d'avoir "No.Wair" lors de l'extraction de l'élément de la file d'attente ou d'en mettre un nouveau dans la file d'attente.
  3. Le scientifique talentueux en utilisera davantage. Il devrait en trouver dans les nouveaux "patterns" récents qui sont apparus en premier en Java. Appelé motif DISRUPTOR. Un échange LMAX en Europe a expliqué à la communauté haute fréquence que l'utilisation basée sur les threads dans les processeurs modernes perdrait du temps de traitement sur la libération du cache mémoire par le CPU si la file d'attente daya n'était pas alignée avec la taille du cache cpu moderne = 64

    Donc, pour cette lecture, ils ont rendu public un code java qui permet au processus multithread d'utiliser correctement le cache du processeur matériel sans résolution de conflits. Et un bon informaticien DOIT trouver que ce modèle a déjà été porté en c ++ ou le faire lui-même.

    Il s'agit d'un moyen de compétence au-delà de toute configuration d'administration. C'est en vrai coeur de haute fréquence aujourd'hui.

  4. Le gars de l'informatique DOIT écrire beaucoup de code C ++ non seulement pour aider les gens d'AQ. Mais aussi
    • valider chez les commerçants face à une vitesse avérée
    • condamner différentes technologies anciennes utilisées et les exposer avec son propre code pour montrer qu'elles ne produisent pas de bons résultats
    • écrire son propre code c ++ de communication multi-threading basé sur la vitesse éprouvée du noyau pupe / select au lieu d'utiliser à nouveau les anciennes technologies. Je vais vous donner un exemple - la bibliothèque tcp moderne est ICE. Et les gens qui l'ont fait sont brillants. Mais leurs priorités étaient dans le domaine de la compatibilité avec de nombreuses langues. Donc. Vous pouvez faire mieux en c ++. Recherchez donc les exemples les plus performants basés sur l'appel sélectif ASYNCHRONE. Et n'allez pas pour plusieurs consommateurs, plusieurs producteurs - pas pour HF.
      Et vous serez étonné de constater que le canal est utilisé UNIQUEMENT POUR la notification par le noyau du message arrivé. Vous pouvez y mettre le numéro de message 64 bits - mais pour le contenu, vous accédez à votre file d'attente CAS sans verrouillage. Déclenché par un select()appel de noyau asynchrone .
    • en outre. En savoir plus sur l'affectation avec l'affinité de thread c ++ à votre thread qui effectue le piping / la mise en file d'attente de vos messages. Ce fil devrait avoir une affinité de base. Personne d'autre ne devrait utiliser le même numéro de cœur de processeur.
    • etc.

Comme vous pouvez le voir - la haute fréquence est un CHAMP EN DÉVELOPPEMENT. Vous ne pouvez pas être un simple programmeur C ++ pour réussir.

Et quand je dis pour réussir, je veux dire que le hedge fund pour lequel vous travaillerez reconnaîtra les efforts de tournée en compensation annuelle au-delà des chiffres dont parlent les gens et les recruteurs.

L'époque de la simple FAQ constructeur / destructeur est révolue pour toujours. Et c ++… lui-même a migré avec de nouveaux compilateurs pour vous soulager de la gestion de la mémoire et pour imposer le non-héritage de grande profondeur dans les classes. Perte de temps. Le paradigme de réutilisation du code a changé. Il ne s'agit pas seulement du nombre de classes que vous avez créées en polymorphe. Il s'agit de performances temporelles confirmées directement du code que vous pouvez réutiliser.

C'est donc votre choix de vous lancer dans la courbe d'apprentissage, ou non. Il ne frappera jamais un panneau d'arrêt.

alex p
la source
6
Vous voudrez peut-être mettre un peu d'effort dans l'orthographe et la mise en forme. Dans sa forme actuelle, ce message est à peine compréhensible.
CodesInChaos
1
Vous décrivez la situation d'il y a 10 ans. Les solutions basées sur le matériel surpassent facilement le C ++ pur de nos jours, quelle que soit l'optimisation de votre C ++.
Sjoerd
Pour ceux qui veulent savoir ce qu'est une solution basée sur le matériel - ce sont principalement des solutions FPGA où le code est réellement gravé dans la mémoire rapide et n'est pas modifié sans reburning de la mémoire dite ROM. Lecture seule
alex p
@alexp Vous ne savez clairement pas de quoi vous parlez. Le FPGA est quelque chose de différent du "code gravé dans la mémoire rapide".
Sjoerd