Je fais du trading algorithmique à Raiffeisenbank. Il s'agit d'un domaine assez spécifique du secteur bancaire. Nous créons une plateforme de trading qui fonctionne avec des retards faibles et prévisibles. Le succès de l'application dépend, entre autres, de la vitesse de l'application, nous devons donc traiter toute la pile impliquée dans le trading: canaux de réseau privé, matériel spécial, paramètres du système d'exploitation et une machine virtuelle Java spéciale, et, bien sûr, l'application elle-même. Nous ne pouvons pas arrêter d'optimiser exclusivement l'application elle-même - les paramètres du système d'exploitation ou du réseau ne sont pas moins importants. Cela nécessite une expertise technique et une érudition pour comprendre comment les données circulent à travers la pile entière et où il peut y avoir un retard.

Toutes les organisations / banques ne peuvent pas se permettre le développement de cette classe de logiciels. Mais j'ai eu la chance qu'un tel projet ait été lancé dans les murs de Raiffeisenbank, et j'avais une spécialisation appropriée - je me suis spécialisé dans les performances de code au Intel Moscow Compiler Laboratory. Nous avons fait des compilateurs pour C, C ++ et Fortran. Chez Raiffeisenbank, je suis passé à Java. Si auparavant j'ai créé une sorte d'outil que beaucoup de gens utilisaient alors, maintenant je suis passé de l'autre côté des barricades et je suis engagé dans une analyse appliquée des performances non seulement du code, mais de la pile d'application entière. Régulièrement, le moyen de rechercher un problème de performances se situe bien au-delà du code, par exemple dans les paramètres du noyau ou du réseau.
Java n'est pas pour la surcharge?
Il est largement admis que Java n'est pas très adapté au développement de systèmes très chargés.
Cela ne peut être que partiellement accepté. Java est beau à bien des égards. Si nous le comparons avec un langage comme C ++, alors son surcoût potentiel peut être plus élevé, mais parfois des solutions fonctionnellement similaires en C ++ peuvent fonctionner plus lentement. Il existe des optimisations qui fonctionnent automatiquement en Java, mais ne fonctionnent pas en C ++, et vice versa. En regardant la qualité du code qui vient après le compilateur Java JIT, je veux croire que les performances seront inférieures à ce que je pourrais atteindre en pointe, pas plus de plusieurs fois. Mais en même temps, je reçois un développement très rapide, d'excellents outils et une large sélection de composants prêts à l'emploi.
Avouons-le: dans le monde C ++, les environnements de développement (IDE) sont considérablement derrière IntelliJ et Eclipse. Si un développeur utilise l'un de ces environnements, la vitesse de débogage, la recherche de bogues et l'écriture d'une logique complexe sont d'un ordre de grandeur plus élevé. En conséquence, il s'avère qu'il est plus facile de classer Java aux bons endroits afin qu'il fonctionne assez rapidement que de tout faire à partir de zéro et pendant très longtemps en C ++. La chose la plus drôle est que lors de l'écriture de code concurrentiel, les approches de synchronisation à la fois en Java et en C ++ sont très similaires: ce sont des primitives au niveau du système d'exploitation (par exemple,
synchronized / std :: mutex ) ou des primitives de fer (
Atomic * / std :: atomic <*> ) . Et il est très important de voir cette similitude.
En général, nous développons une application sans surcharge))
Quelle est la différence entre une application à charge élevée et à faible latence?
Le terme haute charge ne reflète pas pleinement les spécificités de notre travail - nous sommes engagés dans
des systèmes sensibles à la
latence . Quelle est la différence? Pour les systèmes fortement chargés, il est important de travailler en moyenne assez rapidement, en utilisant pleinement les ressources matérielles. En pratique, cela signifie que chaque centième / millième /../th millionième requête au système peut potentiellement fonctionner très lentement, car nous nous concentrons sur les valeurs moyennes et ne tenons pas toujours compte du fait que nos utilisateurs souffrent considérablement des freins.
Nous sommes engagés dans des systèmes pour lesquels le niveau de retard est critique. Notre tâche est de nous assurer que le système a toujours une réponse prévisible. Les
systèmes potentiellement
sensibles à la latence peuvent ne pas être lourdement chargés si les événements se produisent assez rarement, mais un temps de réponse garanti est requis. Et cela ne facilite pas leur développement. Bien au contraire! Les dangers guettent partout. L'écrasante majorité des composants du matériel et des logiciels modernes sont orientés vers le bon travail "en moyenne", c'est-à-dire pour le
débit .
Prenez au moins des structures de données. Nous utilisons des tables de hachage, et si un nouveau hachage de la structure de données entière se produit sur un chemin critique, cela peut conduire à des freins tangibles pour un utilisateur spécifique sur une seule demande. Ou compilateur JIT - optimise le modèle de code le plus fréquemment exécuté, en pessimisant le modèle de code rarement exécuté. Mais la vitesse de ce cas rare particulier peut être très importante pour nous!
Peut-être que ce code traite un type rare de commandes? Ou une situation de marché inhabituelle qui nécessite une réponse rapide? Nous essayons de nous assurer que la réaction de notre système à ces événements potentiellement rares prend un certain temps prévisible et, de préférence, très court.
Comment obtenir un temps de réaction prévisible?
On ne peut pas répondre à cette question en deux phrases. Dans une première approximation, il est important de comprendre s'il existe un type de synchronisation -
synchronisé, reentrantlock ou quelque chose de
java.util.concurrent . Souvent, vous devez utiliser la synchronisation sur le spin -ah occupé. L'utilisation d'une primitive de synchronisation est toujours un compromis. Et il est important de comprendre comment ces primitives de synchronisation fonctionnent et quels compromis elles entraînent. Il est également important d'évaluer la quantité de déchets générés par un morceau de code particulier. La meilleure façon de lutter contre le ramasse-miettes est de ne pas le déclencher. Moins nous générerons de déchets, moins nous exécuterons un ramasse-miettes et plus le système fonctionnera sans son intervention.
Nous utilisons également un large éventail d'outils différents qui nous permettent d'analyser non seulement des indicateurs moyens. Nous devons analyser très attentivement la vitesse à laquelle le système fonctionne chaque centième, chaque millième fois. Évidemment, ces indicateurs seront pires que la médiane ou la moyenne. Mais il est très important pour nous de savoir combien. Et des outils tels que
Grafana ,
Prométhée , les
histogrammes HDR et
JMH aident à le montrer.
Puis-je supprimer Unsafe?
Souvent, vous devez utiliser ce que les apologistes appellent une API non documentée. Je parle du fameux Unsafe. Je crois que dangereux est devenu de facto une partie de l'API publique des machines Java. Cela n'a aucun sens de le nier. Unsafe utilise de nombreux projets que nous utilisons tous activement. Et si nous le refusons, qu'adviendra-t-il de ces projets? Soit ils vivront sur l'ancienne version de Java, soit ils devront encore dépenser beaucoup d'énergie pour réécrire tout cela. La communauté est-elle prête pour cela? Est-il prêt à perdre potentiellement des dizaines de pour cent de productivité? Et surtout, en échange de quoi?
Indirectement, mon opinion confirme une
suppression très soignée des méthodes de Unsafe - en Java11, les méthodes les plus inutiles de Unsafe ont été supprimées. Je pense que jusqu'à ce qu'au moins la moitié de tous les projets utilisant Unsafe passent à autre chose, Unsafe sera disponible sous une forme ou une autre.
Il y a une opinion: Banque + Java = entreprise sanglante ossifiée?
Notre équipe n'a pas de telles horreurs. Au printemps, nous avons probablement écrit dix lignes, et par moi)) Nous essayons de ne pas utiliser de grosses technologies. Nous préférons faire petit, soigné et rapide, afin de pouvoir le réaliser, le contrôler et, si nécessaire, le modifier. Ce dernier est très important pour les systèmes (comme le nôtre), qui ont des exigences non standard, qui peuvent différer des exigences de 90% des utilisateurs du framework. Et dans le cas de l'utilisation d'un grand cadre, nous ne serons pas en mesure de transmettre nos besoins à la communauté ou de corriger indépendamment le comportement.
À mon avis, les développeurs devraient toujours pouvoir utiliser tous les outils disponibles. Je suis venu dans le monde Java à partir de C ++ et je suis très frappé par la division de la communauté en ceux qui développent le
runtime de la machine / compilateur virtuel ou de la machine virtuelle elle-même et en développeurs d'applications. Cela se voit clairement dans le code de classe JDK standard. Les auteurs JDK utilisent souvent une API différente. Potentiellement, cela signifie que nous ne pouvons pas atteindre des performances de pointe. En général, je pense que l'utilisation de la même API pour écrire à la fois la bibliothèque standard et le code d'application est un excellent indicateur de la maturité de la plateforme.
Encore une chose
Je pense qu'il est très important pour tous les développeurs de savoir comment, sinon toute la pile, alors au moins la moitié fonctionne: code Java, code octet, composants internes du runtime et assembleur de la machine virtuelle, matériel, système d'exploitation, réseau. Cela permet une vision plus large des problèmes.
La performance mérite également d'être mentionnée. Il est très important de ne pas se concentrer sur la moyenne et de toujours regarder les centiles médian et élevé (le pire des mesures 10/100/1000 / ...).
Je parlerai de tout cela lors d'une
réunion du groupe d'utilisateurs Java le 30 mai à Saint-Pétersbourg. Rencontre avec Sergei Melnikov, c'est juste moi)) Vous pouvez vous inscrire
ici .
De quoi allons-nous parler?- À propos du profilage et de l'utilisation du profileur Linux et perf standard: comment vivre avec eux, ce qu'ils mesurent et comment interpréter leurs résultats. Ce sera une introduction au profilage général, avec des conseils, des astuces de vie, comment tirer tout ce qui est possible des profileurs afin qu'ils profilent avec une précision et une fréquence maximales.
- À propos des fonctionnalités de l'équipement pour obtenir un profil encore plus détaillé et afficher le profil des événements rares. Par exemple, lorsque votre code s'exécute 10 fois plus lentement toutes les centièmes fois. Aucun profileur ne le dira. Nous allons écrire notre petit profileur en utilisant le mécanisme standard du noyau Linux et essayer de voir le profil d'un événement rare.
Venez à la réunion, ce sera une grande soirée, il y aura beaucoup d'histoires intéressantes sur notre plateforme et sur notre langue préférée.
A plus tard;)