Journal Un premier contact avec le langage Nim

Posté par  . Licence CC By‑SA.
30
3
juil.
2019

Au gré de mes pérégrinations sur Github, j'ai trouvé un benchmark de serveur Web qui date un peu mais qui m'a interpelé.

J'avais déjà entendu parlé du langage Nim mais je le classais plutôt comme projet de recherche.
En regardant de plus près, je tombe sur une version 0.20 aboutie et un éco-système déjà très riche.

Nim se décrit comme un langage compilé statiquement typé qui propose également la méta-programmation (macros), un ramasse-miette, le "pattern-matching" fonctionnel et une compilation vers les langages C, C++, Objective-C ou JavaScript.
Le but affiché est d'être aussi rapide que le C, aussi expressif que le python et extensible comme du Lisp. Rien que ça! On est en droit d'être sceptique…

Or le langage a véritablement beaucoup d'attraits et la prise en main est quasi-immédiate surtout pour des personnes familières avec Python. Je vous propose quelques liens à la fin du journal pour parcourir plusieurs exemples de code.

Première évaluation

Mon premier objectif de test était de vouloir comparer bêtement la taille d'un exécutable avec une version de référence en C. Pour ce faire, je pars sur un équivalent classique à la commande /bin/true de GNU Coreutils.

Le code d'exemple ne comprend qu'une seule ligne:

% cat ./true.nim
quit(QuitSuccess)

Compilation

La documentation dédiée à la compilation est très bien écrite et la commande de compilation est immédiate:

        /tmp % nim c true.nim
        Hint: used config file '/etc/nim/nim.cfg' [Conf]
        Hint: system [Processing]
        Hint: widestrs [Processing]
        Hint: io [Processing]
        Hint: true [Processing]
        CC: stdlib_system.nim
        CC: true.nim
        Hint:  [Link]
        Hint: operation successful (14111 lines compiled; 0.479 sec total; 16.008MiB peakmem; Debug Build) [SuccessX]

        % ls --human --size ./true /bin/true
        88K ./true  36K /bin/true

Résultat plutôt logique pour un premier jet. Je peux améliorer la taille de l'exécutable final avec quelques options:

        nim c -d:release --newruntime --opt:size true.nim

        % ls --human --size ./true /bin/true
        40K ./true  36K /bin/true

De mieux en mieux… et en regardant de plus près avec strace, je vois que du code utilise une gestion des signaux désactivable avec l'option -d:noSignalHandler. La documentation propose également de désactiver les vérifications "runtime" avec --checks:off. Voyons ce que ça donne:

        nim c --verbosity:2 -d:release --checks:off -d:noSignalHandler --newruntime --opt:size true.nim

        % ls --human --size ./true /bin/true
        % 20K ./true  36K /bin/true

Impressionnant! Nous sommes maintenant arrivés à une taille largement inférieure à ma version de référence.
Mais nous pouvons même faire encore mieux avec un dernier appel à la commande strip

        % ls --size --human ./true =true
        16K ./true  36K /bin/true

Dernier détail

Cependant un dernier détail me chiffonne avec la sortie de ldd qui fait apparaître la librairie libdl.so.2 dans ma version. J'ai beau chercher avec les options de linkage passL: mais je n'arrive pas à l'éviter.

Il ne me reste plus qu'à reprendre la ligne du compilateur présentée avec le mode verbose et ne pas établir manuellement ce lien:

        % gcc -o /tmp/true .cache/nim/true_r/stdlib_allocators.nim.c.o .cache/nim/true_r/stdlib_system.nim.c.o .cache/nim/true_r/true.nim.c.o

        % ldd ./true
        linux-vdso.so.1 (0x00007fff5918e000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ff1d239e000)
        /lib64/ld-linux-x86-64.so.2 (0x00007ff1d258a000)

Conclusion

Victoire! Mes sorties ldd et strace sont maintenant strictement identiques à la version GNU Coreutils mais j'ai gagné 45% sur mon exécutable final ;-)

Exemples de code Nim

J'ai essayé de classer les sites dans un ordre de progression:

Épilogue

La documentation du module system précise que l'appel à quit est implicite. Le fichier source true.nim peut donc être vide pour le même résultat !

De plus, mon exemple est trop simpliste ici mais sachez que vous pouvez utiliser autre chose que gcc très facilement. Par exemple:

        nim c --gcc.exe:musl-gcc --gcc.linkerexe:musl-gcc ...
  • # Don't underestimate true

    Posté par  (site web personnel) . Évalué à 5.

    C'est pas vraiment comparable à true quand même…

    Chez moi, /bin/true --help | --version me donne de jolis messages, ta version be gère pas ça. Attention, true --help ne fait rien (shell built-in).

    • [^] # Re: Don't underestimate true

      Posté par  . Évalué à 3.

      Effectivement, petite tricherie ;-) Le support des 2 options fait monter l'exécutable à 28K.
      https://termbin.com/e8rag

      • [^] # Re: Don't underestimate true

        Posté par  . Évalué à 7.

        Encore plus léger :

        $ ls --size --human-readable true.py
        4.0K true.py
        
        $ cat true.py
        import sys
        import argparse
        
        
        if __name__ == '__main__':
            parser = argparse.ArgumentParser(add_help=False)
            parser.add_argument('--help', action="store_true")
            parser.add_argument('--version', action="store_true")
        
            args, extra_args = parser.parse_known_args()
        
            if not extra_args:
                if args.help:
                    parser.print_help()
                elif args.version:
                    print("Version")
        
            sys.exit(0)
        

        Quoi ? Comment ça c’est pas comparable ? :p

        • [^] # Re: Don't underestimate true

          Posté par  (site web personnel) . Évalué à 3. Dernière modification le 04 juillet 2019 à 17:32.

          Quoi ? Comment ça c’est pas comparable ? :p

          Tu peux créer un PEX ( https://github.com/pantsbuild/pex ) ou un nix-bundle (https://github.com/matthewbauer/nix-bundle) pour une vrai comparaison.

          Mais je suis pas sur que tu veuilles poster le résultat ^

          • [^] # Re: Don't underestimate true

            Posté par  . Évalué à 2.

            Haha, si si, carrément :

            $ python3 -m nuitka --standalone true.py
            python3 -m nuitka --standalone --mingw64 true.py  31.57s user 2.12s system 142% cpu 23.562 total
            
            $ ls --size --human-readable true.dist/true
            7.0M true.dist/true.exe
            
            $ du -hs true.*                                                                                                                                                                         
            57M     true.build                                                                                                                                                                                                 
            29M     true.dist                                                                                                                                                                                                  
            4.0K    true.py
            

            Ça doit sûrement être optimisable, mais je connais pas les « compilateurs Python ».

      • [^] # Re: Don't underestimate true

        Posté par  . Évalué à 4.

        T'aurais pu te rattraper aussi en disant que tu copiais le true d'OpenBSD. Qui a besoin d'options non spécifiées par POSIX comme --help pour true lorsqu'une page man détaillée existe pour cette commande si complexe ? :-) Par ailleurs, ça fait 10.2K chez moi.

  • # bin true aux éditions O'reilly

    Posté par  . Évalué à 4.

    Il est temps de mettre la référence à jour

    http://www.miketaylor.org.uk/tech/oreilly/truenut.html

  • # 100% static link

    Posté par  (site web personnel) . Évalué à 3.

    Est-il possible de construire un exécutable sans aucune dépendance?

    Le post ci-dessus est une grosse connerie, ne le lisez pas sérieusement.

    • [^] # Re: 100% static link

      Posté par  (Mastodon) . Évalué à 3.

      Oui bien sûr, l'option-static de gcc sert à ça. Ou tu veux dire autre chose ?

      En théorie, la théorie et la pratique c'est pareil. En pratique c'est pas vrai.

  • # Pourquoi des exceptions?

    Posté par  (site web personnel) . Évalué à 2.

    Le langage supporte les "sum types": pourquoi avoir mis des exceptions au lieu du Maybe de Haskell ou Option de Rust?

    • [^] # Re: Pourquoi des exceptions?

      Posté par  . Évalué à 3.

      Peut être simplement parce qu'il y a des gens (comme moi) qui aiment bien le mécanisme des exceptions.

      (mais je suis preneur de littérature pas trop complexe sur le sujet)

      • [^] # Re: Pourquoi des exceptions?

        Posté par  (site web personnel) . Évalué à 6.

        Ocaml a les 2. Et franchement, les exceptions c'est chiant. un type option/Sum t'oblige à gérer bien les cas, une exception fait semblant d'être simple, mais c'est difficile d'être sûr de tout gérer correctement.

        "La première sécurité est la liberté"

        • [^] # Re: Pourquoi des exceptions?

          Posté par  . Évalué à 3.

          Moui mais Walter Bright (le concepteur de D) te répondrait: qui va vérifier les messages de log?
          Auquel j'ajouterai qui va vérifier les débordements entier?

          Au lieu d'utiliser 1 + 2, tu vas faire utiliser une option?

          • [^] # Re: Pourquoi des exceptions?

            Posté par  (site web personnel) . Évalué à 3.

            Quel rapport avec les log ? Parce que c'est le comportement d'un code java typique ?

            Les débordements d'entier sont rarement un problème :
            - c'est une arithmétique de modulo qui ont des propriétés sympa
            - les CPU n'ont pas de support pour le débordement, mais la propriété sympa
            - C'est un problème de dimensionnement, un bug et pas un cas d'erreur.

            "La première sécurité est la liberté"

            • [^] # Re: Pourquoi des exceptions?

              Posté par  . Évalué à 2.

              Quel rapport avec les log ?

              Le rapport c'est que dans du code industriel, tu as beaucoup de log, donc quasiment toutes tes fonctions ont potentiellement des erreurs liés à ça.
              Avec des exceptions, si tu as un problèmes de log ça n'est pas ignoré sans "surcout visuel" lié a la propagation des valeurs de retour..

              Pour les débordements entier: l'arithmétique de modulo n'est quasiment jamais la sémantique que tu veux (les exceptions étant principalement la gestion des timers)

              "- C'est un problème de dimensionnement, un bug et pas un cas d'erreur."
              Hum, c'est un peu n'importe quoi cette phrase!
              Ne pas poster tard le soir?

              • [^] # Re: Pourquoi des exceptions?

                Posté par  (site web personnel) . Évalué à 5.

                Je n'ai rien compris à ce que tu veux dire avec les log. En quoi le fait qu'il y en ai beaucoup ou pas à avoir avec la gestion d'exception ou l'usage de retour de fonction (type option ou autre).

                Si tu as un code qui fait un dépassement d'entier, c'est que tu a un bug, et ce n'est pas un cas d'erreur à gérer. Les exceptions ne sont pas faite pour rattraper des bugs, mais de gérer des erreurs rares normales dans la vie du logiciel. Si tu as un dépassement, c'est qu'il une erreur dans les gestion des entrées de ton opération entière. C'est une problème de code faux, pas de gestion d'erreur dans l'usage du logiciel.

                Il y a code connu en Ada qui gérait les exceptions entières, sans handler, cela a lancé l'autotest, ce qui a balancé des AAAA et des 5555 sur le bus. Et pouf la fusée, pour une variable dont tout le reste du code se foutait.

                "La première sécurité est la liberté"

                • [^] # Re: Pourquoi des exceptions?

                  Posté par  . Évalué à 2.

                  En quoi le fait qu'il y en ai beaucoup ou pas à avoir avec la gestion d'exception ou l'usage de retour de fonction

                  En théorie ça ne change rien, en pratique c'est important pour la lisibilité du code, si ton code nominal est noyé dans la gestion des code de retour..

                  Pour ton exemple: ce qui est valable pour une fusée ne vaut pas forcément pour le reste: se faire hacker a cause d'un débordement entier n'est pas un problème pour une fusée..

                  Et franchement faire la différence entre une erreur "file not found", "serveur unreachable" et un débordement entier, ça me parait TRES discutable.

          • [^] # Re: Pourquoi des exceptions?

            Posté par  (site web personnel) . Évalué à 1.

            Pour 1 + 2, le mieux c'est d'avoir le choix, comme avec Rust:

            • [^] # Re: Pourquoi des exceptions?

              Posté par  . Évalué à 3.

              si on utilise l'opérateur +, c'est qu'on s'en fiche du comportement de l'overflow, et le comportement va dépendre de la machine

              Uniquement en mode release ça: en mode debug (le mode par défaut) il y a une panique.

        • [^] # Re: Pourquoi des exceptions?

          Posté par  . Évalué à 2.

          Oui mais là c'est une question de goût de ta part !

          • [^] # Re: Pourquoi des exceptions?

            Posté par  (site web personnel) . Évalué à 4.

            En fait, cela dépend si tu privilégie de torcher un code ou de faire un truc qui ne plante jamais.

            "La première sécurité est la liberté"

            • [^] # Re: Pourquoi des exceptions?

              Posté par  . Évalué à 3.

              en même temps pour qu'un code ne plante jamais suffit juste que son temps de dev soit au moins égale à celui de l'abandon du projet ;)

              Il ne faut pas décorner les boeufs avant d'avoir semé le vent

      • [^] # Re: Pourquoi des exceptions?

        Posté par  (site web personnel) . Évalué à 4.

        J'aimerais bien un langage qui oblige à gérer toutes les erreurs possibles proprement. Bref pas d'unchecked exception, de panic ou autre.

        Le post ci-dessus est une grosse connerie, ne le lisez pas sérieusement.

        • [^] # Re: Pourquoi des exceptions?

          Posté par  . Évalué à 2.

          Et bien vas-y: remplace tout les + de ton code par Add(int, int) -> nodiscardOption
          et ajoute un attribut nodiscard devant toutes les fonctions.

          A mon avis ça va être illisible rapidement..

          • [^] # Re: Pourquoi des exceptions?

            Posté par  . Évalué à 4.

            D’où le besoin exprimé par devnewton d’avoir un langage adapté plutôt que d’écrire du code illisible.

            • [^] # Re: Pourquoi des exceptions?

              Posté par  . Évalué à 2. Dernière modification le 05 juillet 2019 à 15:05.

              Des propositions concrètes? Parce que là je ne vois pas..
              Soit tu as une propagation automatiques des erreurs, ça s'appelle des exceptions, soit tu gère toi même les cas d'erreurs et là s'il faut gérer vraiment toutes les erreurs c'est très lourd.

              • [^] # Re: Pourquoi des exceptions?

                Posté par  (site web personnel) . Évalué à 3. Dernière modification le 05 juillet 2019 à 15:37.

                99% du temps (chiffre Dave Newton Institute Of Coding Horror), on veut juste que le nombre soit borné ou qu'il devienne invalide.

                Donc je ferais un langage du style:

                mutable compteur := int<0, 100>(99);
                compteur += 2;
                log.info(compteur); // affiche 100
                mutable toto := number(42);
                toto /= 0;
                log.info(compteur); // affiche NaN

                Le post ci-dessus est une grosse connerie, ne le lisez pas sérieusement.

                • [^] # Re: Pourquoi des exceptions?

                  Posté par  . Évalué à 2.

                  L'utilisation des additions saturées est très rare (sauf en traitement de signal) par contre l'utilisation de NaN pour éviter les exceptions/panic??
                  C'est sûr ça rend le flux d'exécution + simple, mais bon courage pour trouver l'endroit ou il y a eu le problème..

                  • [^] # Re: Pourquoi des exceptions?

                    Posté par  (site web personnel) . Évalué à 3. Dernière modification le 05 juillet 2019 à 18:04.

                    Il y a plein de possibilités à réfléchir (si tu veux vraiment des exceptions, il faudrait au moins que le compilateur t'oblige à les gérer toutes), mais mon besoin est de faire des programmes qui ne plantent jamais salement.

                    Le post ci-dessus est une grosse connerie, ne le lisez pas sérieusement.

                    • [^] # Re: Pourquoi des exceptions?

                      Posté par  . Évalué à 2.

                      Ça existe en Java le fait de devoir gérer toutes les exceptions mais beaucoup n'aime pas (ça me paraît une bonne idée mais je n'ai pas suffisamment utilisé Java pour savoir si ça marche bien en pratique)

                      • [^] # Re: Pourquoi des exceptions?

                        Posté par  (site web personnel) . Évalué à 3.

                        Le compilateur t'oblige à gérer certaines exceptions, mais pas toutes.

                        https://docs.oracle.com/javase/tutorial/essential/exceptions/runtime.html

                        Go a fait exactement la même erreur avec les panics.

                        Le post ci-dessus est une grosse connerie, ne le lisez pas sérieusement.

                        • [^] # Re: Pourquoi des exceptions?

                          Posté par  (site web personnel, Mastodon) . Évalué à 1.

                          Et à l'usage, le concept de checked exceptions a tellement d'inconvénients pour si peu d'avantages qu'il est généralement considéré comme l'un des défauts de Java.

                          La connaissance libre : https://zestedesavoir.com

                          • [^] # Re: Pourquoi des exceptions?

                            Posté par  (site web personnel) . Évalué à 2.

                            Le problème c'est l'entre deux. Pour moi, soit tu es en train de faire un script jetable donc tu peux coder en mode yolo, soit tu fais un logiciel et là tu as envie de gérer tous les cas d'erreurs.

                            Le post ci-dessus est une grosse connerie, ne le lisez pas sérieusement.

                            • [^] # Re: Pourquoi des exceptions?

                              Posté par  (site web personnel, Mastodon) . Évalué à 2.

                              soit tu fais un logiciel et là tu as envie de gérer tous les cas d'erreurs.

                              Je vois deux soucis avec ça :

                              • D'une part, c'est pas spécialement vrai. « Gérer tous les cas d'erreur » est potentiellement tellement long que tu n'arriveras jamais au bout de ton développement. Ce que tu veux réellement, c'est « gérer tous les cas raisonnables d'erreurs », c'est une balance bénéfice/risque.
                              • Les checked exceptions sont un assez mauvais moyen d'arriver à ce résultat, principalement parce que très envahissant pour le résultat obtenu.

                              La connaissance libre : https://zestedesavoir.com

                              • [^] # Re: Pourquoi des exceptions?

                                Posté par  (site web personnel) . Évalué à 5.

                                J'aime bien la solution de Go qui t'oblige à dire que tu t'en fous d'une erreur avec un underscore. Quand on lit le code, on comprends bien l'intention de l'auteur.

                                plop, err := refroidirReacteur()
                                if nil != err {
                                evacuerIleDeFrance()
                                }
                                resultat, _ := ploperSurLaTribune()

                                Le post ci-dessus est une grosse connerie, ne le lisez pas sérieusement.

        • [^] # Re: Pourquoi des exceptions?

          Posté par  (site web personnel) . Évalué à 4.

          Dans le langage Esterel, à l'époque, il fallait utiliser des fonctions de troncature si on utilisait un "+" sur 2 registres 8 bits, le résultat était sur 9 bits, cela ne compilait pas, il fallait donc tronquer ou arrondir.

          "La première sécurité est la liberté"

  • # il manque une ligne

    Posté par  . Évalué à 4.

    Mais nous pouvons même faire encore mieux avec un dernier appel à la commande strip…

    On ne voit pas la commande en question dans le journal, est-ce que c'est un oubli ?

    • [^] # Re: il manque une ligne

      Posté par  . Évalué à 1.

      Il s'agit de la commande usuelle strip qui prend en paramètre le fichier binaire. Elle est fournie en standard sur toutes les distributions. Elle te permet de diminuer la taille de ton exécutable en effaçant les symboles liés à la compilation.

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.