Wasm ou pas Wasm?

Chez Linkurious travaillons sur Linkurious Enterprise. Il s'agit d'une plate-forme Web qui utilise la puissance des graphiques et de leurs outils de visualisation pour aider les entreprises et les autorités du monde entier à lutter contre la criminalité financière.



L'une des principales fonctionnalités de Linkurious Enterprise est une interface de visualisation graphique facile à apprendre et à utiliser, conçue pour les profanes. En 2015, frustrés par les capacités des bibliothèques JavaScript existantes pour la visualisation de graphes, nous avons commencé à développer notre propre bibliothèque - Ogma.











Ogma est une bibliothèque JS de rendu et de performances de calcul visant à rendre les structures de réseau. Vous avez peut-être vu comment les structures de réseau sont rendues à l'aide d'autres outils JavaScript tels que D3.js ou Sigma.js. Nous n'avions pas les capacités de ces outils. Il était important pour nous que la solution que nous utilisions ait des capacités spécifiques afin de répondre à certaines exigences de performances. Nous n'avons trouvé ni l'un ni l'autre dans des bibliothèques tierces. Par conséquent, nous avons décidé de développer notre propre bibliothèque à partir de zéro.



Tâche





Visualisation de la structure du réseau



La bibliothèque Ogma a été conçue pour utiliser les algorithmes les plus avancés. Tous ses composants, du moteur de rendu avancé basé sur WebGL aux travailleurs Web utilisés dans ses profondeurs, ont pour objectif de fournir les meilleures performances de rendu réseau disponibles. Notre objectif était de rendre la bibliothèque très interactive lors de l'exécution de tâches à long terme, et afin que les principaux algorithmes de visualisation de graphes mis en œuvre dans celle-ci fonctionnent rapidement et de manière stable.



La technologie WebAssembly (Wasm), dès le moment où elle a été signalée pour la première fois, a promis aux développeurs Web un niveau de performance qui se compare favorablement à ce qui leur était auparavant disponible. Dans le même temps, les développeurs eux-mêmes n'ont pas eu besoin de faire des efforts excessifs pour utiliser la nouvelle technologie. Ils avaient juste à écrire du code dans un langage très performant qu'ils connaissaient, qui, après compilation, pouvait être exécuté dans un navigateur.



Après que la technologie Wasm se soit un peu développée, nous avons décidé de l'essayer et de réaliser des tests de performances avant de l'implémenter. En général, avant de sauter dans le train rapide Wasm sans regarder en arrière, nous avons décidé de jouer prudemment.



Ce qui nous a attirés vers Wasm, c'est que cette technologie pourrait bien résoudre nos problèmes, en utilisant les ressources mémoire et processeur plus efficacement que JavaScript.



Notre recherche



Notre recherche a commencé par la recherche d'un algorithme visant à travailler avec des graphiques qui pourraient être facilement portés dans différentes langues en utilisant des structures de données similaires.



Nous avons choisi l'algorithme à n corps. Il est souvent utilisé comme base de comparaison des algorithmes de visualisation de graphes de force. Les calculs effectués conformément à cet algorithme sont la partie la plus gourmande en ressources des systèmes de visualisation. Si cette partie du travail de ces systèmes pouvait être effectuée plus efficacement qu'auparavant, cela aurait un effet très positif sur tous les algorithmes de visualisation des graphes de force implémentés dans Ogma.



Référence



Il y a des mensonges, des mensonges grossiers et des repères.



Max De Marzi



Développer un benchmark «honnête» est souvent une tâche impossible. Le fait est que dans un environnement créé artificiellement, il est difficile de reproduire des scénarios typiques du monde réel. Créer un environnement adéquat pour des systèmes complexes est toujours extrêmement difficile. En effet, dans un environnement de laboratoire, il est facile de contrôler les facteurs externes, mais en réalité, de nombreux inattendus influencent à quoi ressemble la «performance» d'une solution.



Dans notre cas, le benchmark visait à résoudre un seul problème bien défini, pour étudier les performances de l'implémentation de l'algorithme à n corps.



Il s'agit d'un algorithme clair et bien documenté utilisé par des organisations respectées pour mesurer les performances.langages de programmation.



Comme pour tout test équitable, nous avons prédéfini des règles pour les différentes langues que nous étudions:



  • Différentes implémentations de l'algorithme doivent utiliser des structures de code similaires.
  • Il est interdit d'utiliser plusieurs processus ou plusieurs threads.
  • L'utilisation de SIMD est interdite .
  • Seules les versions stables des compilateurs sont testées. L'utilisation de versions comme nightly, beta, alpha, pre-alpha est interdite.
  • Seules les versions les plus récentes du compilateur sont utilisées pour chaque langue.


Après avoir décidé des règles, nous pourrions déjà procéder à la mise en œuvre de l'algorithme. Mais, avant de faire cela, nous devions encore choisir les langues dont nous étudierons la performance.



Concurrents JS



Wasm est un format binaire pour les instructions qui s'exécutent dans un navigateur. Le code écrit dans divers langages de programmation est compilé dans ce format. Ils disent que le code Wasm est un code lisible par l'homme, mais écrire des programmes directement dans Wasm n'est pas une décision qu'une personne saine d'esprit peut prendre. En conséquence, nous avons mené une enquête sur le benchmark et sélectionné les candidats suivants:





L'algorithme à n corps a été implémenté dans ces trois langages. Les performances de diverses implémentations ont été comparées aux performances de l'implémentation JavaScript de base.



Dans chacune des implémentations de l'algorithme, nous avons utilisé 1000 points et exécuté l'algorithme avec un nombre d'itérations différent. Nous avons mesuré le temps nécessaire pour terminer chaque session du programme.



Les tests ont été réalisés à l'aide des logiciels et matériels suivants:



  • NodeJS 12.9.1
  • Chrome 79.0.3945.130 (version officielle) (64 bits)
  • clang 10.0.0 - pour la version de l'algorithme implémenté en langage C
  • emcc 1.39.6 - interface pour appeler le compilateur Emscripten depuis la ligne de commande, une alternative à gcc / clang, ainsi qu'un éditeur de liens
  • fret 1.40.0
  • wasm-pack 0.8.1
  • AssemblyScript 0.9.0
  • MacOS 10.15.2
  • Macbook Pro 2017 Retina
  • Intel Dual Core i5 2,3 GHz, 8 Go DDR3, 256 Go SSD


Comme vous pouvez le voir, pour les tests, nous n'avons pas choisi l'ordinateur le plus rapide, mais nous testons Wasm, c'est-à-dire le code qui sera exécuté dans le contexte du navigateur. Et le navigateur, de toute façon, n'a généralement pas accès à tous les cœurs de processeur disponibles dans le système et à toute la RAM.



Pour le rendre plus intéressant, nous avons créé plusieurs versions de chaque implémentation de l'algorithme. À un moment donné dans le système à n corps, ils avaient une représentation numérique de 64 bits des coordonnées, à l'autre - 32 bits.



Il est également intéressant de noter que nous avons utilisé une implémentation "double" de l'algorithme dans Rust. Tout d'abord, sans utiliser d'outils Wasm, une implémentation Rust "native", "non sûre" a été écrite. Plus tard, en utilisant wasm-pack, une implémentation Rust "sûre" supplémentaire a été créée. On s'attendait à ce que cette implémentation de l'algorithme soit plus facile à intégrer avec JS, et qu'il soit capable de mieux gérer la mémoire dans Wasm.



Essai



Nous avons mené nos expériences dans deux environnements principaux. Il s'agit de Node.js et du navigateur (Chrome).



Dans les deux cas, les benchmarks ont été réalisés à l'aide d'un script chaleureux. À savoir, le garbage collection n'a pas été démarré avant l'exécution des tests. Lorsque nous avons exécuté le ramasse-miettes après l'exécution de chaque test, cela ne semblait pas avoir beaucoup d'impact sur les résultats.



Basé sur le code source écrit en AssemblyScript, ce qui suit a été généré:



  • Implémentation JS de base de l'algorithme.
  • Module Wasm.
  • Module Asm.js.


Il est intéressant de noter que le module asm.js n'était pas entièrement compatible avec asm.js. Nous avons essayé d'ajouter une directive "use asm" en haut du module, mais le navigateur n'a pas accepté cette optimisation. Plus tard, nous avons découvert que le compilateur binaryen que nous avons utilisé n'essayait pas vraiment de rendre le code entièrement compatible avec asm.js. Au lieu de cela, il s'est concentré sur la formation d'une sorte de version JS efficace de Wasm.



Nous avons d'abord effectué des tests dans Node.js.





Exécution du code dans l'environnement Node.js



Ensuite, nous avons mesuré les performances du même code dans le navigateur.





Exécution du code dans un navigateur



Nous avons immédiatement remarqué que la version asm.js du code fonctionne plus lentement que les autres versions. Mais ces graphiques ne permettent pas une comparaison assez claire des résultats de différentes variantes de code avec l'implémentation JS de base de l'algorithme. Par conséquent, afin de mieux tout comprendre, nous avons construit quelques diagrammes supplémentaires.





Différences entre les autres implémentations de l'algorithme et l'implémentation JS (version de référence avec coordonnées de points 64 bits)





Différences entre les autres implémentations de l'algorithme et l'implémentation JS (version de référence avec coordonnées de point 32 bits)



Les versions de référence avec coordonnées de point 64 bits et 32 ​​bits diffèrent sensiblement. Cela peut nous amener à penser qu'en JavaScript, les nombres peuvent être les deux. Le fait est que les nombres dans JS, dans l'implémentation de l'algorithme, pris comme base de comparaison, sont toujours 64 bits, mais les compilateurs qui convertissent le code d'autres langages vers Wasm travaillent avec ces nombres de différentes manières.



En particulier, cela a un impact énorme sur la version asm.js du test. Sa version avec des coordonnées de points 32 bits est très inférieure en performances à la fois à l'implémentation JS de base et à la version asm.js, qui utilise des nombres 64 bits.



Dans les schémas précédents, il est difficile de comprendre comment les performances des autres variantes de code sont liées à la variante JS. C'est parce que les métriques d'AssemblyScript sont trop différentes des autres. Afin de comprendre cela, nous avons construit un autre diagramme, supprimant les résultats de asm.js.





Différences entre les autres implémentations de l'algorithme et l'implémentation JS (variante de référence avec coordonnées de points 64 bits, sans asm.js)





Différences entre les autres implémentations de l'algorithme et l'implémentation JS (version de référence avec coordonnées 32 bits de points, sans asm.js)



Différentes représentations de nombres semblent avoir influencé d'autres versions du test. Mais ils ont influencé de différentes manières. Par exemple, la variante C, qui utilisait des nombres 32 bits (flottants), est devenue plus lente que la variante C, qui utilisait des nombres 64 bits (double). Les deux versions Rust du test avec des nombres 32 bits (f32) sont devenues plus rapides que leurs versions avec des nombres 64 bits (f64).



Mauvaise mise en œuvre de l'algorithme?



L'analyse des données ci-dessus peut suggérer la réflexion suivante. Étant donné que tous les assemblys Wasm testés présentent des performances très proches des implémentations JavaScript, est-il possible que les implémentations Wasm reflètent uniquement les fonctionnalités de performances des implémentations d'algorithmes natifs?





Comparaison des implémentations natives de l'algorithme avec l'implémentation JavaScript



Les versions natives des implémentations d'algorithme sont toujours plus rapides que l'implémentation JavaScript.



Nous avons également remarqué que les assemblys Wasm sont plus lents que les versions natives du code utilisé pour créer de tels assemblys. La différence de performance est de 20 à 50%. Nous l'avons découvert sur une version abrégée du benchmark avec 1000 itérations.





Implémentation C et assemblage Wasm correspondant





Implémentation de Rust et build Wasm correspondant





Implémentation de Rust, qui a été créée à l'aide de wasm-pack, et de l'assembly Wasm correspondant.



Lors de la mesure du temps d'exécution du code natif, le temps nécessaire au chargement du programme a été pris en compte, et dans le cas des variantes Wasm, ce temps n'a pas été pris en compte.



Résultat



En moyenne, le gain de performances des deux implémentations Rust de l'algorithme, par rapport à son implémentation JS de base, était de 20%. C'est probablement bon pour l'image de Rust, mais c'est trop peu de gain de performance par rapport à l'effort qu'il a fallu pour l'obtenir.



Quelles conclusions pouvons-nous tirer de ces tests? Et en voici quelques-uns: une écriture réfléchie du code JS vous permet d'obtenir des performances assez élevées et ne nécessite pas de passer à d'autres langages de programmation.



Apprendre de nouveaux langages de programmation est toujours bon. Mais il doit y avoir de bonnes raisons d'apprendre de nouvelles langues. Les performances sont souvent la «mauvaise» raison, car l'architecture de conception de haut niveau est plus importante pour les performances que les compilateurs ou les micro-optimisations.



À titre expérimental, nous avons changé JavaScript en TypeScript pour écrire une implémentation de l'algorithme de visualisation du graphe de force. En conséquence, nous avons amélioré la qualité de la base de code, mais pas les performances. Nous avons spécifiquement mesuré la performance, et il s'est avéré qu'après la transition, elle a légèrement augmenté, de 5%. La raison en est probablement la refactorisation du code.



Si vous êtes intéressé par le développement et les performances JavaScript, vous serez peut-être intéressé à regarder cette conférence, qui a donné des résultats similaires à ceux que nous avons obtenus.



Comment abordez-vous le développement des parties «lourdes» des projets web?






All Articles