Comment il reste rapide sur un portable ordinaire.
Les écrans de trading sont lourds : de longs historiques, des ticks qui ne s’arrêtent jamais, beaucoup de graphiques sur une page. TradeCanvas ne fait que le travail dont chaque image a besoin, sur le thread principal, sans code tiers. Cette page montre comment, et où il reste à l’améliorer.
Travail en cours : le moteur est assez rapide pour la production, mais pas encore aussi rapide qu’il le sera. La dernière section liste ce qu’il reste à faire.
De petits paquets, dans un seul sens
Chaque paquet ne dépend que de ceux qui sont en dessous. Rien d’extérieur à TradeCanvas ne s’exécute dans votre page : les composants de framework prennent React, Vue ou Svelte en peer, et le moteur de rendu WebGL est un chunk à part, chargé seulement quand un graphique le demande.
0 dépendance tierce à l’exécution
Le chemin d’un tick
Un prix arrive de votre flux et atteint l’écran en une image. Chaque étape ne traite que ce qui a changé.
- 1Votre flux
DataAdapterWebSocket, REST, un courtier : tout ce qui fournit un historique et un flux continu. - 2Gestionnaire de flux
StreamManagerL’historique d’abord, puis le direct. Les réponses à une ancienne requête sont ignorées ; un flux muet est repéré en 30 s. - 3Agrégateur de ticks
TickAggregatorLes ticks entrent dans un tampon circulaire de 1 000 et sont intégrés à la barre de leur unité de temps. - 4Indicateurs
IndicatorEngineRecalculés depuis la dernière barre, pas depuis le début de la série. - 5Viewport
ViewportSeules les barres à l’écran sont parcourues ; l’échelle des prix vient d’elles seules. - 6Boucle de rendu
RenderLoopQuel que soit le nombre de ticks reçus, la scène est dessinée au plus une fois par image d’animation. - 7Canvas 2D ou WebGL
RenderEngineDeux canvas, et un canvas GPU en dessous quand WebGL est activé.
Un tick coûte une barre, pas tout l’historique
Quand un tick modifie la dernière barre, chaque indicateur reprend à partir de cette barre (et des quelques barres précédentes que sa formule consulte) au lieu de recalculer la série. Si les barres qu’il a mémorisées ne correspondent plus, il repasse à un recalcul complet : une correction n’est jamais manquée.
Une vingtaine d’indicateurs ont leur propre mise à jour incrémentale, et 31 autres reposent sur une base qui reprend point par point. Les indicateurs qui lisent la ligne d’un autre indicateur reprennent aussi.
En vue d’ensemble : une colonne par pixel
Quand les barres deviennent plus étroites qu’un pixel, dessiner chacune gaspille un travail que l’œil ne voit pas. Bougies, barres, haut-bas et volume sont dessinés en une colonne par pixel, avec la première ouverture, le plus haut, le plus bas et la dernière clôture des barres qu’elle couvre. Les lignes et les aires gardent environ deux points par pixel (LTTB) ; les lignes et bandes d’indicateurs dessinent un trait par colonne de pixels.
- barres visibles
- 313
- barres par pixel
- 0,39
- formes dessinées
- 313
Une barre n’est jamais plus étroite qu’un quart de pixel CSS : un graphique d’un million de barres en montre donc au plus environ quatre par pixel en vue d’ensemble complète, soit environ 6 400 sur un graphique de 1 600 px. Faire défiler le reste, c’est ce que mesure le benchmark du million de barres.
Deux canvas, pour qu’un survol ne coûte presque rien
La scène (grille, série, indicateurs, dessins, ordres) vit sur un canvas ; le réticule, la légende, les étiquettes des axes et le compte à rebours vivent sur un canvas léger au-dessus. Bouger la souris ne redessine que celui du dessus. Une mise à jour des données redessine la scène une fois, à l’image suivante.
Chaque canvas pleine taille coûte au navigateur une composition par image : le moteur en garde donc peu. Un canvas d’arrière-plan pour le dessin en 2D seul n’existe que tant que quelque chose en a besoin.
WebGL par enregistrement, pas par réécriture
Le graphique dessine avec des appels Canvas 2D. Avec WebGL activé, ces appels vont à un enregistreur qui transforme tracés, remplissages et contours en commandes GPU, en pixels de l’appareil. Ce que le GPU ne sait pas dessiner à l’identique reste en Canvas 2D, dans l’ordre : l’image est la même dans les deux cas.
- tracés, remplissages et contours
- bougies, volume et grille en dessins instanciés
- la série dans un seul Float32Array, six flottants par barre ; un tick ne réécrit que la fin
- le texte
- les images et les motifs
- ombres, filtres, découpes inhabituelles
- tout ce qui suit la première étape que le GPU ne sait pas faire
renderer: 'auto'demande au navigateur un contexte sans réserve majeure de performance : une machine dont le WebGL tournerait en logiciel garde Canvas 2D.- Au plus 8 graphiques d’une page détiennent un contexte WebGL (
setMaxWebGLCharts) ; les autres attendent dans une file et dessinent en Canvas 2D. - Si le GPU perd le contexte, le graphique continue en Canvas 2D et se reconstruit en WebGL quand le contexte revient.
- Les bougies sont arrondies aux pixels entiers de l’appareil avec la même règle dans les shaders et sur le CPU : les deux moteurs de rendu les placent sur les mêmes pixels.
Conçu pour les machines modestes
- Canvas 2D par défautWebGL reste optionnel tant qu’il n’a pas été mesuré sur davantage d’appareils. Sur une puce graphique intégrée à ratio de pixels élevé, le goulot d’étranglement est la composition du navigateur lui-même, pas le dessin.
- Un redimensionnement par imageLes événements de redimensionnement sont regroupés en un seul par image, et une taille qui n’a pas changé ne déclenche rien.
- L’historique par pagesLes barres plus anciennes se chargent par 500 quand vous remontez dans le temps, une requête à la fois, avec un délai croissant après un échec.
- Pas de clignotement au chargementLe voile de chargement attend 200 ms : un changement de symbole rapide ne clignote jamais.
- Léger à téléchargerLe graphique headless pèse 176 KB compressé en gzip, le widget complet 315 KB, et le moteur de rendu WebGL 18 KB de plus quand il sert.
- Des tableaux qui se mettent à jour sur placeLe tableau des cours bo-grid regroupe les ticks en un correctif par image et ne redessine que les cellules qui ont changé.