Comme je n'ai pas pris la première place dans la compétition pour les développeurs JavaScript de Telegram

Les utilisateurs actifs de Telegram, en particulier ceux qui sont abonnés à Pavel Durov, ont probablement entendu parler du fait que Telegram a organisé un concours pour les développeurs iOS, Android et JavaScript, ainsi que pour les concepteurs, sur ces sites Internet. Malgré le fait que ce fut un événement plutôt épique avec la distribution de prix solides (l'un des participants a reçu 50k $ pour la première place, ayant écrit l'application Android la plus rapide et la plus facile), ils ont en quelque sorte peu écrit à ce sujet, du moins sur Runet. Mon premier article tentera de corriger la situation.


Étant donné que je suis un développeur JavaScript à pile complète (pour être exact, un développeur TypeScript), j'ai décidé de me tester. Manille n'est pas seulement un fonds de prix, mais aussi le format lui-même: ce n'est pas un concours de programmation où l'abstrait et la vitesse de réflexion sont importants. Tout dans le complexe était important ici: expérience, vitesse de développement à moyen terme, goût pour les questions d'interface utilisateur, connaissance de l'informatique dans son ensemble et autocritique. Selon les termes du concours, il a été nécessaire de développer une bibliothèque d'affichage de graphiques pour l'une des plateformes: iOS, Android ou Web.


Les développeurs de différentes plates-formes ne se faisaient pas concurrence et chaque plate-forme avait ses propres gagnants. Les principaux critères étaient: la vitesse de travail (y compris sur les appareils plus anciens), la cohérence avec la conception, l'animation fluide et la taille minimale de l'application. Les solutions et bibliothèques déjà existantes ne pouvaient pas être utilisées, tout devait être écrit à partir de zéro.

Avant cela, j'ai participé à des concours pour les développeurs, où pas plus de 5 heures étaient allouées pour toutes les tâches, ces heures devaient être passées dans un stress énorme. Malgré le fait que le Telegram n'ait pas exigé une telle pression pour terminer la tâche, c'est l'un des concours les plus difficiles auxquels j'ai dû participer. La tâche apparemment simple s'est avérée si vaste que si j'étais payé, je pourrais couper ces «graphiques» pendant des mois, essayant de trouver un compromis entre les performances du code et son harmonie architecturale. Cela a permis que trois semaines ( upd: deux, grâce à vlad2711 pour l'amendement) soient allouées à la solution. Certains concurrents ont spécifiquement pris congé pour consacrer plus de temps à la compétition, et j'ai décidé de combiner le développement de la compétition le soir et le week-end avec un travail à « Ontanta » comme d'habitude.

CANVAS contre SVG


Le problème architectural le plus important qui nous a tous confrontés a été le choix d'un outil de rendu graphique. Actuellement, les normes Web nous proposent deux approches: à travers la génération de graphiques svg à la volée et le bon vieux canevas. Voici les avantages et les inconvénients de chacun.

Toile


+ Polyvalence absolue - ayant la possibilité de changer la couleur de n'importe quel pixel sur la toile, vous pouvez dessiner tout ce que vous voulez.
+ [Potentiel] Haute performance - si vous pouvez préparer un canevas, il peut montrer de bonnes performances. Ce serait génial d'utiliser webgl, mais son support sur les smartphones est médiocre.

- Tous les calculs et tous les rendus à la main - contrairement à SVG, où les points intermédiaires de la polyligne peuvent être définis une fois, puis vous pouvez manipuler la boîte de vue pour déplacer la «caméra» le long des sections de la polyligne, avec le canevas, tout est plus compliqué: il n'y a pas de «caméras» ici, il y a coordonne uniquement à partir du coin supérieur gauche; si vous devez "déplacer" la zone de visualisation actuelle du graphique, vous devez recalculer toutes les coordonnées de tous ses points par rapport à la nouvelle position de la zone de visualisation. En d'autres termes, la boîte de visualisation, qui en svg est prête à l'emploi, doit être implémentée manuellement dans le canevas.
- L'animation entière est manuelle - sur la base du paragraphe précédent, toutes les animations possibles sont implémentées en recalculant les coordonnées, les valeurs de couleur et de transparence et en redessinant la scène entière N-ème nombre de fois par seconde, et plus il était possible de recompter et de redessiner la scène, plus l'animation est fluide.

Svg


+ Dessin simple - ajoutez simplement les lignes, formes et plus nécessaires au SVG une fois, en manipulant les paramètres de la fenêtre d'affichage, de la couleur et de la transparence, fournissez une navigation graphique.
+ Implémentation simple des animations - encore une fois, sur la base du paragraphe précédent, il suffit de Ne pour spécifier de nouvelles valeurs pour la fenêtre d'affichage, la couleur et la transparence nombre de fois par seconde, et l'image sera redessinée elle-même, le navigateur s'en chargera. De plus, n'oubliez pas que les formes et les primitives en SVG peuvent être stylisées en CSS, afin qu'elles puissent être animées à l'aide d'animations CSS3, ce qui ouvre les possibilités les plus larges d'obtenir des animations sympas avec un minimum d'effort.
+ Bonnes performances par défaut - si vous pouvez facilement mettre quelque chose de lent et consommer des centaines de ressources sur le canevas, le résultat basé sur SVG sera toujours assez léger, décent et lisse.

Mais il y a un revers à la médaille.

- Possibilités d'optimisation modestes - puisque nous ne dessinons pas de svg, mais le navigateur, il est impossible de contrôler ce processus - si vous voulez augmenter les performances, par exemple, en mettant déjà en cache des éléments dessinés individuellement, vous ne pouvez pas le faire de quelque manière que ce soit. Très probablement, cela est déjà fait par le navigateur, mais nous ne pouvons pas être sûrs jusqu'à la fin.
- Outils limités - en SVG, nous ne contrôlons plus chaque pixel de la toile, mais pensons et codons dans le cadre de primitives vectorielles. Cependant, pour cette tâche, il s'agit d'un inconvénient insignifiant, imposant certaines restrictions, encore une fois insignifiantes dans le contexte de la tâche de compétition.

Je n'ai jamais eu à me soucier de choisir un outil, car j'ai un trait de caractère dégoûtant - je suis maximaliste et je n'utilisais que mon outil préféré dans mon travail. Il se trouve que depuis mes jours d'étudiant, quand je m'amusais avec DirectDraw, mon outil préféré était toujours une toile sur laquelle "faites ce que vous voulez". Et la toile pour résoudre un problème concurrentiel s'est vraiment avérée bonne, mais elle n'a vraiment joué dans mes mains qu'un seul plus: les plus grandes opportunités d'optimisation, car le critère principal était toujours la performance des applications.

Un bon code n'est pas bon


La tâche est claire: vous devez dessiner des points sur la toile au bon endroit et au bon moment. Reste à écrire le code. Encore une fois, il fallait choisir, cette fois entre écrire un code compact productif avec un «footcloth» dans le style procédural ou peu productif et encore plus non compact dans mon préféré orienté objet. Vous avez probablement déjà deviné que j'avais choisi la deuxième option, en l'assaisonnant avec un autre de mes favoris - TypeScript.

Et ce choix n'était pas très correct. En raison de l'utilisation d'abstractions et d'encapsulation, il n'est pas toujours possible d'enregistrer, de transmettre et de réutiliser les résultats de calcul intermédiaires, ce qui affecte mal les performances. Et en raison de l'utilisation généralisée de cela, sans laquelle la POO dans JS est impossible, le code est mal minifié, tandis que la taille importait également.

Il est temps de donner un lien vers le github: github.com/native-elements/telechart . Si vous êtes intéressé, je vous recommande de faire attention à l'historique des validations; il conserve une mémoire des épreuves d'optimisation et des tentatives infructueuses pour extraire quelques images de rendu supplémentaires par seconde.

Eh bien, au concours, je n'ai pas remporté le prix. Et le problème, comme cela arrive souvent avec nous, les programmeurs, s'est avéré ne pas être une expérience insuffisante, un esprit rapide ou une vitesse, mais une autocritique insuffisante: le fait même que j'ai réussi à le faire fonctionne et ressemble à l'image, j'étais content, mais En ce qui concerne les freins de rendu, je pensais avoir fait tout ce que je pouvais, le reste a probablement fait de même. J'ai honte d'en parler, mais j'étais sûr que je prendrais la première ou la deuxième place. En fait, il s'est avéré que j'ai écrit un programme de freinage et de buggy, pas le pire, mais loin d'être le meilleur. Quand j'ai vu le travail d'autres développeurs, j'ai réalisé que je n'avais aucune chance et ne pouvais que me mordre les coudes. Si j'étais impartial dans mon travail, je serais engagé dans la productivité, la partie la plus importante de la tâche de compétition.

L'une des leçons les plus précieuses de ma vie professionnelle que je ne me lasse pas d'obtenir est qu'un bon ingénieur, contrairement, par exemple, à un artiste, est obligé d'évaluer objectivement la qualité de son travail, en abandonnant la confiance en soi, car le résultat de son travail ne doit pas seulement plaire aux yeux mais devrait fonctionner correctement et bien.

Ce fut la première étape de la compétition. Les gagnants ont été généreusement récompensés. À ma joie indescriptible, l'histoire ne s'arrête pas là, car la deuxième étape est annoncée:


Il a fallu affiner votre métier, en seulement une semaine, en mettant en place des types de cartes supplémentaires. Je montrerai tout de suite ce qui s'est passé, et ci-dessous je vous dirai comment cela s'est produit.


Dans mon cas, avant d'ajouter de nouvelles fonctionnalités, je devais comprendre les performances de l'ancienne. Le premier problème que j'ai résolu est

Animation de contraction

Même si vous avez suffisamment de puissance pour produire 60 images par seconde, l'animation ne sera pas fluide si la position de l'élément ou sa transparence n'est pas déterminée par le temps écoulé depuis le début de l'animation. Cela est dû à des intervalles de temps inégaux entre les ticks: par exemple, un tick a fonctionné après 10 ms, et le second après 40, tandis que pour les premier et deuxième ticks, l'objet s'est déplacé vers la gauche de 1 pixel - c'est-à-dire que sa vitesse de déplacement flotte constamment, visuellement, cela ressemble à un «tic». En d'autres termes, vous devez faire quelque chose de mal:

let left = 10, interval = setInterval(() => { left += 1 if (left >= 90) { clearInterval(interval) } }, 10) 

Et donc:

 let left = 10, startLeft = 10, targetLeft = 90, startTime = Date.now(), duration = 1000, interval = setInterval(() => { left = startLeft + (targetLeft - startLeft) * (Date.now() - startTime) / duration if (left >= targetLeft) { left = targetLeft clearInterval(interval) } }) 

Puisqu'il y a beaucoup de paramètres animés dans le code, j'ai filmé une classe universelle qui facilite la tâche et ajoute également de l'animation à l'animation. Il est assez simple à utiliser:

 let left = Telemation.create(10, 90, 1000) … drawVerticalLine(left.value) //      ,  . 

Ensuite, la règle des 60 ips entre en jeu. Les joueurs sur PC me comprendront: pour qu'une animation soit parfaite, elle doit être rendue à une vitesse d'au moins 60 ips. Par conséquent, chaque rendu du cadre ne devrait pas prendre plus de 1/60 de seconde. Cela nécessite un matériel puissant et un bon code.

Des recherches complémentaires ont montré que

Le canevas de peinture ralentit s'il y a des éléments html au-dessus du canevas .

Au départ, j'ai utilisé des éléments html "vides" afin d'implémenter le contrôle de la fenêtre courante:


Ces éléments ont été placés sur le dessus de la toile, et malgré le fait qu'ils n'avaient pas de contenu, ils ont été utilisés uniquement pour suivre les événements de la souris, à la suite d'expériences, il s'est avéré que leur présence réduit les performances de rendu. En les supprimant et en compliquant la logique de détermination des événements pour contrôler un peu la zone de visualisation, j'ai augmenté la vitesse de rendu du cadre.

Il restait à tirer le dernier clou du couvercle du cercueil de performance: je l'ai fait

Mise en cache de mini-carte

Auparavant, pour les minicartes, les lignes étaient à nouveau tracées. C'est une opération coûteuse car elle affiche l'intégralité du planning de l'année (365 points par ligne). La solution évidente, que j'étais trop paresseux pour implémenter dès le début, était de tracer une fois les lignes du graphique pour la minicarte, d'enregistrer le résultat dans le cache et d'utiliser ce cache à l'avenir. Après cette optimisation, les performances des applications ne sont plus gênantes.

Et ensuite?


Il y avait encore beaucoup de succès et peu de combats pour les performances: tentatives de mise en cache des résultats des calculs de coordonnées, expériences avec les paramètres de LineRoin CanvasRenderingContext2D (onglet plus rapide), mais ils ne sont pas si intéressants, car ils n'ont pas donné de gain de performance notable ou ne l'ont pas donné du tout.

Sur les huit jours, j'ai passé cinq à accélérer le code et seulement trois à compléter la nouvelle fonctionnalité. Oui, il ne m'a fallu que trois jours pour ajouter de nouveaux types de graphiques, et ici la POO s'est avérée très pratique, avec elle la base de code a légèrement augmenté. Je n'ai pas eu assez de temps pour terminer la tâche bonus (+5 tableaux supplémentaires). Je crois que ces cinq jours que j'ai passés à éliminer les conséquences de ma confiance en moi, je pourrais passer à résoudre le problème des bonus.

Néanmoins, mon travail a donné le résultat: 4e place et un prix de «consolation» de mille dollars:


Au fait, la compétition s'est poursuivie, mais sans moi.

Je suis content de la participation: en plus d'être juste intéressant et une aventure intéressante, j'ai eu une bonne expérience professionnelle et une leçon de vie.

De plus, j'ai utilisé cette bibliothèque dans le développement de notre horodateur d'entreprise, dont je prévois également de parler dans un avenir proche.

Pour discussion, je propose la question suivante: pourquoi Telegram a-t-il besoin de tout cela? Je pense que pour un budget suffisant, Telegram recevra la meilleure bibliothèque au monde pour afficher des graphiques: le meilleur résultat de centaines de tentatives pour faire mieux que d'autres. Le principe de la concurrence vous permet d'obtenir un niveau de qualité si élevé que personne ne peut le faire sur commande et sans argent.

Et quelques liens:


Source: https://habr.com/ru/post/fr460625/


All Articles