Du Rust dans NodeJs graçe a Neon : le courant passe !

Après 3 jours d’immersion dans Neon j’ai réussi a enrober la crate wotb (crate=librairie rust) dans un module npm :

Et j’ai depuis 1h environ un nœud duniter-ts qui tourne avec wotb-rs, la synchro complete s’est bien passée (c’est comme ça que j’ai obtenu la taille du fichier wotb-rs pour la blockchain actuelle).

le dépôt est là :

Niveau intégration c’est assez fort : Neon permet a Rust de manipuler directement des types JS et de caster d’un type JS vers un type Rust et vice versa dans une macro try dont les exceptions sont captés par la VM Node !!!

Autre énorme avantage de Neon : les variables de type JS créer par le code Rust et fournies en sortie sont gérés par le GC de Node, donc le binding ne peut pas causer de fuite mémoire.
Il reste des cas de fuite mémoire possible si le code Rust contiens des blocs unsafe (ce qui est le cas dans mon 1er jet mais je prévois de refondre tout ça avec du code 100% safe, la c’était juste un 1er jet pour prouver que ça marche).

Il m’a fallu modifier un peu le code du cœur (très peu c’est cosmétique) : les changements sont sur la branche dédiée :
https://git.duniter.org/nodes/typescript/duniter/tree/1302-migrating-from-wotb-c-to-wotb-rs-rust

Ce qu’il reste a faire :

  • refondre le module npm wotb-rs pour utiliser uniquement du code safe
  • écrire dans duniter-ts un code de migration qui copie une wotb-cpp et une wotb-rs (sinon tout le monde serait obliger de faire une resync complete lors de la mise jours).

Une fois que j’aurais fini ça, fort de cette 1ère expérience de binding Node<->Rust, je pourrai enfin m’occuper de réparer g1-monit :slight_smile:

14 Likes

Très intéressant comme avancée :slight_smile:

1 Like

Bon boulot !

C’est vraiment dommage que l’encapsulation de struct/classes soit si foireuse, ça éviterait d’avoir une HashMap statique et du unsafe; mais bon déjà tu as réussi à faire l’interrop :stuck_out_tongue:

1 Like

@nanocryk en fait j’ai une idée pour résoudre ce problème sans passer par les classes. Créer un objet JS qui contient les données de la WoT. Rust ne sera appelé que pour les fonctions de calcul (distance, paths, centralité), et lira alors l’objet JS pour recréer une WoT rust juste le temps du calcul. Du coup toutes les fonctions qui ne calculent rien seront en pur JS :wink:

1 Like

Il ne risque pas d’y avoir de ralentissement pendant la conversion des données JS vers Rust ?

Bien sur que si mais cela ne sera fait que lors d’un calcul de distance, pas a chaque modif de la wot, le coût sera très faible et me semble largement en valoir la chandelle !

Et tu convertis quoi comme donnée JS, d’ailleurs ? Des structures entières ou juste quelques nombres ?

En fait avec Neon tout est forcément un type JS et entrée, on ne peut pas recevoir de “nombre” directement, donc que ce soit un JsObject ou un JsArray le coût sera le même.
A priori je vais laisser les deux usages possibles : créer une instance safe ou une instance unsafe.
Duniter-ts 1.7 pourra utiliser les instances unsafe, pour le moment il faut juste s’assurer que la méthode clear() est bien appelée pour toute instance wot, ce qui semble être le cas pour le cœur actuel. Je testerai la création d’instances safe sur g1-monit, puis si on arrive a quelque chose de stable et pas trop coûteux niveau perf ou l’utilisera dans le cœur :wink:

Oulà tu vas vite en besogne : il y a toutes les étapes de livraison à passer aussi (compilation pour Desktop notamment), et puis je n’intègre pas un truc que je ne peux pas moi-même retoucher au besoin, donc cela nécessitera quelques tutos pour s’approprier le module développé.

C’est peut-être pas trop difficile ceci dit, je n’en sais rien :slight_smile:

1 Like

Je l’ai bien en tête t’inquiète :wink:

Arf va falloir que tu te mette au Rust alors :stuck_out_tongue:

1 Like

Ça ne me dérange pas, je ne demande que des prétextes :slight_smile:

7 Likes

J’avais encore zéro connaissance en binding node<->rust dimanche matin, par contre je pratique beaucoup Rust depuis 5 mois. Je n’irai pas jusqu’a dire que c’est simple, mais tu a largement le niveau, c’est quand même (je trouve) plus simple que le binding avec wotb C++ :wink:

1 Like

Ma mission est à présent terminée, je retourne en croisade. Derust vult !

1 Like

La version Desktop Linux fonctionne nickel chez moi, reste a tester sur Windows et Arm, mais je préfère avancer un peu avant de tester ça.

1 Like

Tu parle de type JS un peu partout, il s’agit bien de type JS ou des type TypeScript ?

C’est typé le JS ?

De types NodeJS, pas de typescript ici, Neon fait du binding NodeJS<->Rust. Et en fait Neon communique directement avec la VM Node qui elle manipule bien des types en interne même si le code source qu’elle exécute n’est pas typé :wink:

3 Likes

Tout à fait, la VM Node est aussi appelée V8, le moteur JS créé par Google. On peut notamment voir dans la documentation les types JavaScript ainsi manipulés.

1 Like

C’est surtout la version Windows que je crains, car pour les modules C++ il y a de la recompilation.

Alors j’ai finie un 1er jet de la version safe, voila le verdict :

wotb-rs performances tests
Basic operations
✓ should add 1_000_000 nodes (74ms)
✓ should add 500_000 links (1507ms)
✓ should make a mem copy (907ms)
✓ should make a unsafe mem copy (150ms)
✓ should make a unsafe mem copy and copy them (191ms)

Il s’agit d’une instance safe, donc pur js, les deux premières lignes ,n’exécutent que du js, la 1ère ajoute un million de nœuds en la deuxième ajoute 500_000 certifications aléatoirement. Ce qui nous intéresse c’est les 3 dernières lignes :

ligne 3 : transforme la wot js en wot rust puis transformer la wot rust en wot js.
ligne 4 : transforme la wot js en wot rust et retourne un instanceId vers celle ci (donc stockée comme variable globale,d’ou le unsafe).
ligne 5: Comme ligne 4 mais ensuite on copie la wot rust.

On en déduit donc que :
La tranformation wot js → wot rust prend au plus 150ms pour une très grosse wot.
La transformation wot rust → wot js est considérablement plus lente, au alentours de 700 ms pour une très grosse wot.
L’appel a memCopy() en mode unsafe prend environ 40ms pour une très grosse wot.

Sachant que ce qui va nous servir concrètement c’est uniquement le sens wot js → wot rust, et que l’on peut réaliser plusieurs calcul en même temps (possibilité de fournir un tableau de requêtes de calcul), une latence de 150ms avant chaque série de calculs ce n’est pas non plus la mort, surtout que le temps que demandera le calcul de distance pour une wot si grande sera de plusieurs secondes a minima, et 150ms sera négligeable par rapport a plusieurs secondes.

EDIT : tests réalisés avec un processeur AMD AthlonX4 845.

1 Like