Journal Java : presque 9 000 requêtes par seconde avec 8 Mo de RAM

Posté par  (site web personnel, Mastodon) .
Étiquettes :
41
13
juin
2022

Sommaire

Sur les sites qui le permettent (et donc pas ici), mon avatar est l’avatar aléatoire disponible à cette adresse : https://avatar.spacefox.fr/avatar_renard.php (le proxy-téléchargeur de linuxfr.org m’interdisant de vous le montrer directement).

L’implémentation actuelle est faite avec trois lignes de PHP, ce qui m’ennuie un peu parce que c’est le seul outil qui a encore besoin de PHP sur mon serveur. Je me suis donc demandé : est-ce que je pourrais réimplémenter ça en Java ? Après tout, la partie dynamique est complètement triviale : c’est une URL qui réponds en HTTP et, quoi qu’on lui demande, renvoie un code retour HTTP 302 vers l’une des 21 URLs d’images de renard disponibles.

On colle le serveur dynamique derrière un Nginx pour faire proxy, HTTPS et serveur d’images, et ça roule.

Un peu de code

package fr.spacefox.avatar;

import com.sun.net.httpserver.HttpServer;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.Random;

public class AvatarHttpServer {

    private final Random random = new Random();
    private final int port;
    private final int imgCount;

    public AvatarHttpServer(final int port, final int imgCount) {
        this.port = port;
        this.imgCount = imgCount;
    }

    public void run() throws IOException {
        var server = HttpServer.create(new InetSocketAddress(port), 0);
        server.createContext("/", exchange -> {
            exchange.getResponseHeaders().add(
                    "Location",
                    "https://avatar.spacefox.fr/Renard-" + (random.nextInt(imgCount) + 1) + ".png");
            exchange.sendResponseHeaders(302, -1);
        });
        server.start();
    }

    public static void main(String[] args) throws IOException {
        new AvatarHttpServer(Integer.parseInt(args[0]), Integer.parseInt(args[1])).run();
    }
}

C’est du Java pur, sans aucune dépendance à aucune bibliothèque externe

33 lignes avec tout le boilerplate, c’est dix fois plus que l’implémentation actuelle. J’aurais pu gratter un peu en ne permettant pas la configuration du port ou du nombre d’images (la version actuelle ne le fait pas) mais ça ne change pas l’ordre de grandeur.

D’un côté, ça fait pas mal de blabla pour un fonctionnement aussi simple ; de l’autre, ça reste très raisonnable par rapport à ce qu’a pu être Java : pas besoin d’aller jouer manuellement avec des socket ou d’autres opérations acrobatiques.

Et surtout : c’est du Java pur, sans la moindre dépendance, qui va tourner directement sur n’importe quelle JVM (17 ou supérieure) – y compris les dérivées d’IBM J9. Le fichier .jar généré et exécutable (via java -jar) fait 2.02 ko.

Les performances

La question que je me suis posé ensuite, c’est : puisque le truc est tout petit, ça doit consommer presque rien en mémoire. D’accord, mais « presque rien » avec Java, c’est quoi ? Pour quelles performances ? Essayons donc.

Je définis le tas à 8 Mo avec -Xmx8m et limite les piles à 256 ko avec -Xss256k (par défaut c’est 1 Mo, clairement inutiles ici). Et je teste :

Débit en sortie

spacefox@shub-niggurath:~$ java -Xmx8m -Xss256k -jar AvatarServer-1.0-SNAPSHOT.jar 6666 21 &
[1] 654741
spacefox@shub-niggurath:~$ ab -n 100000 -c 10 http://localhost:6666/
This is ApacheBench, Version 2.3 <`{mathjax} Revision: 1879490 `>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient)
Completed 10000 requests
Completed 20000 requests
Completed 30000 requests
Completed 40000 requests
Completed 50000 requests
Completed 60000 requests
Completed 70000 requests
Completed 80000 requests
Completed 90000 requests
Completed 100000 requests
Finished 100000 requests


Server Software:
Server Hostname:        localhost
Server Port:            6666

Document Path:          /
Document Length:        0 bytes

Concurrency Level:      10
Time taken for tests:   11.355 seconds
Complete requests:      100000
Failed requests:        0
Non-2xx responses:      100000
Total transferred:      16157218 bytes
HTML transferred:       0 bytes
Requests per second:    8806.68 [#/sec] (mean)
Time per request:       1.136 [ms] (mean)
Time per request:       0.114 [ms] (mean, across all concurrent requests)
Transfer rate:          1389.56 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.2      0      11
Processing:     0    1   0.8      1      66
Waiting:        0    1   0.8      1      65
Total:          0    1   0.8      1      66

Percentage of the requests served within a certain time (ms)
  50%      1
  66%      1
  75%      1
  80%      1
  90%      2
  95%      2
  98%      4
  99%      4
 100%     66 (longest request)

8806.68 requêtes par seconde en moyenne en local. C’est pas mal, surtout que le code complètement mono-thread et que le serveur sur lequel je lance ça tourne sur des Intel(R) Xeon(R) CPU E5-2690 v3 @ 2.60GHz, donc des processeurs assez lents – je suis plutôt à 17 000 sur mon ordinateur.

Je dis « c’est pas mal » mais je devrais plutôt écrire « c’est énorme » : le même test, sur le même serveur, sur un fichier statique hébergé sur cette même machine, via nginx et HTTPS, plafonne plutôt vers 450 requêtes par seconde. Ceci implique que le facteur limitant en conditions réelles sera le proxy (et tout ce qu’il doit gérer) plus que ce programme.

Mais surtout, les performances sont limitées par le CPU et par le passage du garbage collector : le simple fait d’assigner 16 Mo de mémoire au tas fait monter les performances à près de 10 000 requêtes par seconde – au-delà, l’amélioration n’est plus significative.

Le choix du garbage collector est important : ici le test est fait avec G1GC, celui par défaut dans l’OpenJDK 17. Mais si je passe à ZGC, un garbage collector alternatif qui a ses avantages mais n’est clairement pas conçu pour ce genre de charge, les performance sont diminuées de moitié.

La vérité sur la consommation mémoire

Vérifier la consommation mémoire réelle d’une application n’est pas quelque chose de trivial. Pour commencer, beaucoup d’outils affichent la mémoire demandée et pas celle réellement utilisée, ce qui fausse les chiffres à la hausse.

Or, le tas susmentionné (et généralement la première valeur que l’on modifie dans les programmes Java avec la directive -Xmx) n’est pas la seule zone mémoire utilisée par la JVM, il y en a d’autres.

Avec les outils d’introspection Java (dont je ne vous colle pas la sortie, de toutes façon illisible), j’estime la consommation réelle de cet outil à entre 30 et 40 Mo de mémoire – qui ne bouge pas, même après avoir servi des dizaines de millions de requêtes.

Alors, Java c’est lourd et lent et verbeux ?

Ben… oui et non.

Le simple fait d’utiliser une JVM impose de se réserver quelques dizaines de Mo et plusieurs threads invisibles (notamment à cause du Garbage Collector). Si ça pouvait être énorme à l’époque où les ordinateurs avaient quelques centaines de Mo de RAM au total, ça n’est plus un vrai problème aujourd’hui, surtout que le code métier prends très vite beaucoup plus de mémoire que cette consommation de base. De plus, beaucoup de programmes Java dans la nature consomment trop par rapport à ce qu’ils pourraient consommer pour deux raisons principales : d’une part la confusion entre garbage collector et mémoire magique qui crée beaucoup de fuites de mémoire , d’autre part à cause de réglages désastreux par défaut. Par exemple, j’ai encore croisé cette semaine un outil qui conseille de paramétrer 2 Go de tas là où 250 Mo suffisent largement…

Les performances sur ce genre d’exercice sont suffisantes pour que le processeur ne soit jamais un problème. En fait, cette assertion est généralisable : les performances des compilateurs just in time intégrés aux JVM (OpenJDK et dérivées, J9 et dérivées) sont excellentes, tant qu’on ne tape pas dans les programmes extrêmement calculatoires. Les goulets d’étranglement d’un programme Java correctement réalisé sont rarement au niveau du CPU (dans mon test c’est le cas, mais le test n’est pas représentatif d’un usage réel : le proxy ou la connexion satureront avant le CPU en usage réel).

Enfin, Java a fait d’énormes efforts en verbosité, même s’il reste des axes d’amélioration (j’aurais pu éviter le constructeur au prix d’un import – qui sont toujours masqués – et d’une annotation avec Lombok). Kotlin fait mieux de ce côté, mais impose de distribuer les classes de runtime, ce qui n’est clairement pas intéressant dans ce cas.

De plus, tout ça c’est avec un programme écrit en 10 minutes qui ne nécessite aucune dépendance (autre qu’une JVM).

Quoi ? Vous saviez déjà tout ça ? Pourtant, c’est encore des critiques qu’on entends très régulièrement.

Aparté sur l’outil de benchmark utilisé

ab n’est pas fait pour tester des « page » aussi triviales, et sera probablement le facteur limitant lors du test de toute implémentation très optimisée, ce qui conduira à des résultats faux. Cf ici pour plus de détails.

Et dans votre langage préféré ?

Je serais curieux de savoir ce que peut donner l’exercice dans votre langage préféré. N’hésitez pas à le réaliser et à partager vos résultats !


Ce journal, publié sous licence CC BY 4.0, est une adaptation de ce billet publié précédemment sur Zeste de Savoir. Vous trouverez des tests d’implémentation en Go, Rust, Ada et mix C/C++ dans les commentaires.

  • # Pourquoi une redirection?

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

    Pourquoi une redirection vers une autre URL?
    C'est sur qu'avec une redirection 302, tu limites une partie de la mise en cache.

    Et, je me demande même si avec Nginx (ou Apache), tu ne pourrais pas complètement te passer de java ou php, surtout qu'il s'agit de livrer une ressources aléatoirement. Y compris si tu voulais aussi ajouter une redirection 302.

    Pourquoi bloquer la publicité et les traqueurs : https://greboca.com/Pourquoi-bloquer-la-publicite-et-les-traqueurs.html

    • [^] # Re: Pourquoi une redirection?

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

      Une redirection parce que c’est le plus simple, ça évite toute forme de traitement.

      La connaissance libre : https://zestedesavoir.com

      • [^] # Re: Pourquoi une redirection?

        Posté par  . Évalué à 4.

        Y’a pas besoin de traitement, tu peux balancer des headers +/- statiques (en fonction des contraintes que tu te fixes) et le fichier random à la suite (y’a juste la taille des data à prendre en compte dans le head).

        En HTTP(S) on a tendance à oublier qu’il n’y a pas nécessairement de lien fort entre la ressource et le fichier disque (quand la ressource est un fichier). Les headers servent à bien signaler que la ressource est mouvante.

        C’est ce que je fais sur un hébergement gratuit par exemple : tout est stocké en html.gz et un .htaccess qui va bien ajoute les headers qui vont bien. Pas de compression à la volée, mais uniquement au déploiement du site (ce qui ceci dit en passant devrait être la règle…).

        Mort aux cons !

    • [^] # Re: Pourquoi une redirection?

      Posté par  . Évalué à -3.

      Avec apache et une RewriteMap rnd:, ça le fait sans souci et surtout sans l'usine à gaz java.

      • [^] # Re: Pourquoi une redirection?

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

        Pour commencer, j’utilise Nginx et pas Apache.

        Ensuite ça n’est pas la question. Elle n’est pas de « chercher la solution la plus simple pour répondre au besoin » mais précisément de « vérifier si Java est vraiment une usine à gaz pour ce genre de cas ».

        La connaissance libre : https://zestedesavoir.com

        • [^] # Re: Pourquoi une redirection?

          Posté par  . Évalué à 3. Dernière modification le 23 juin 2022 à 01:36.

          Il doit y avoir moyen de faire ça avec haproxy (sans lua!) aussi. Et au doigt mouillé, ça doit exploser toutes les autres solutions évoquées ici au niveau perf (mémoire, taille du binaire et cpu). Pas sûr que cela réponde à ta question… vu que pour moi java est clairement une usine à gaz pour ce genre de ^W^W^W^W dans tous les cas ;-)

  • # HTTPS

    Posté par  . Évalué à 10.

    le même test, sur le même serveur, sur un fichier statique hébergé sur cette même machine, via nginx et HTTPS, plafonne plutôt vers 450 requêtes

    Essaye en http, sans TLS. Sur mon serveur, c'est le jour et la nuit. L'établissement d'une connexion TLS, avec la négociation d'une clé de chiffrement symétrique via un protocole de chiffrement asymétrique, c'est vraiment très lourd.

    • [^] # Re: HTTPS

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

      Oui, cette phrase est surtout là pour montrer que HTTPS est consommateur – contrairement à ce qu’on peut lire parfois sur le sujet.

      Il n’y a pas de tests sur le serveur public en HTTP parce qu’il est configuré pour forcer la redirection vers HTTPS.

      La connaissance libre : https://zestedesavoir.com

  • # C'est beaucoup ?

    Posté par  . Évalué à 3.

    En mode python tout bete, je fais un:

    python3 -m http.server 9000

    qui va me retourner la liste des fichiers sur le répertoire courant. Je lance le bench, j'obtiens 1000 requêtes par secondes. Sans rien chercher, avec un langage lent comme le monde, sur un laptop bien mais pas ouf, qui va chercher des trucs sur le disque et serialiser un vrai contenu (une petite page web).

    Pour être honnête, ca me parait pas si élevé que ca, a première vue.

    • [^] # Re: C'est beaucoup ?

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

      Il faudrait regarder la consommation CPU et mémoire du serveur Python.

      Sur les implémentations mono-thread, Java est à la louche 1.5x à 2x plus lent que les implémentations compilées. Par contre, les implémentations peuvent être multi-thread par défaut, ce qui fait très vite monter les performances mesurées si on ne fait pas gaffe.

      La connaissance libre : https://zestedesavoir.com

      • [^] # Re: C'est beaucoup ?

        Posté par  . Évalué à 6. Dernière modification le 13 juin 2022 à 13:52.

        Ok, j'ai repris le truc, pour m'amuser un peu. Le serveur maintenant affiche son pid, et sert un fichier html tout bete:

        import http.server # Our http server handler for http requests                                                                                                                                                                                
        import os                                                                                                                                                                                                                                     
        import socketserver # Establish the TCP Socket connections                                                                                                                                                                                    
        
        PORT = 9000                                                                                                                                                                                                                                   
        
        print(os.getpid())                                                                                                                                                                                                                            
        
        class MyHttpRequestHandler(http.server.SimpleHTTPRequestHandler):                                                                                                                                                                             
            def do_GET(self):                                                                                                                                                                                                                         
                self.path = 'index.html'                                                                                                                                                                                                              
                return http.server.SimpleHTTPRequestHandler.do_GET(self)                                                                                                                                                                              
        
        Handler = MyHttpRequestHandler                                                                                                                                                                                                                
        
        with socketserver.TCPServer(("", PORT), Handler) as httpd:                                                                                                                                                                                    
            print("Http Server Serving at port", PORT)                                                                                                                                                                                                
            httpd.serve_forever()                                                                                                                                                                                                                     
        

        et le index.html:

        <html>
        <head>
        <title>I am awesome!</title>
        </head>
        <body>
        <p>Congratulations! The HTTP Server is working!</p>
        </body>
        </html>
        J'obtiens 6000 requetes par secondes. J'imagine que le bottleneck est sur la lecture du fichier, mais a ce moment la il ne me parait plus si simple d'utiliser http.server sans servir un fichier html.

        top m'indique le CPU reste en dessous de 100%, j'imagine qu'on est en monothread, la ram, mesuree avec ps -o rss= <pid>, reste constante a 16 MB.

    • [^] # Re: C'est beaucoup ?

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

      Et en https, ça donne quoi ?

      Il faudrait tester exactement le même programme et sur la même machine. Que donne le programme Java sur ta machine ?

    • [^] # Re: C'est beaucoup ?

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

      Avec un truc en bottle vite fait, je monte à 3400 req/s pour fournir le fichier directement, sans redirection, alors que je suis à 2800 req/s pour la redirection.
      Le serveur par défaut est mono-thread, mais utiliser des trucs multi-thread ou asynchrones, ne change pas grand chose vu la simplicité des traitements.

      #!/usr/bin/python3
      from bottle import route, run, response
      import glob
      import random
      
      
      
      # preloading files
      def readfile(path):
          try:
              with open(path, 'rb') as f:
                  a = f.read()
                  return (a, len(a))
          except Exception:
              return (None, 0)
      
      
      files = [
          (c, l)
          for path in glob.glob('*.png')
          for (c, l) in [readfile(path)]
          if l
      ]
      
      
      @route('/')
      def img():
          img, size = random.choice(files)
          response.set_header('Content-Type', 'image/png')
          response.add_header('Content-Length', size)
          return img
      
      
      run(host='localhost', port=8080)

      Et la version courte :

      #!/usr/bin/python3
      from bottle import route, run, redirect
      import glob
      import random
      
      
      files = glob.glob('*.png')
      
      
      @route('/')
      def img():
          redirect(random.choice(files))
      
      
      run(host='localhost', port=8081)

      Après plusieurs benchmark ab comme dans le journal sur chaque serveur, la RAM utilisée est de 21Mo pour chacun (25,3Mo en VSZ, 20,8Mo en RSS).

      Je suis surtout surpris du fait que la redirection soit plus lente.
      Peut-être qu'en balançant manuellement les headers ça irait plus vite que le Bottle.redirect().
      Pourtant je ne fais plus aucune IO disque après le démarrage, dans les deux cas, ça devrait être relativement équivalent.

      Bref, après, c'est difficile de comparer les chiffres bruts alors qu'on n'est pas sur la même machine.

      • Yth.
      • [^] # Re: C'est beaucoup ?

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

        À noter que la version qui précharge n'a pas besoin d'un serveur de fichiers statiques à côté, c'est complètement autonome, et peut tourner en frontal ou directement derrière un haproxy, sans nginx/apache/etc.

        Si j'utilise par exemple waitress comme serveur python (serveur multi-thread) on reste à 21Mo en RSS mais on passe à 313Mo en VSZ !
        Et c'est plus lent (2800/2400 req/s)

        Avec gunicorn (pre-fork) on va plus vite, 3500/3000 req/s et la RAM monte : on a deux processus, un master et un worker, qui font 25Mo+22Mo en RSS (et 30+30 en VSZ), mais comme on le voit, il prefork un seul worker.
        Si on met worker=10, par exemple, on est à 14000/10000 req/s, mais comme prévu, on a 10 workers, donc 25+220Mo de RAM, ça fait pas mal, mais ça "scale" :)

        • Yth, bon si j'arrêtais de procrastiner hein ?

        PS : pour changer de serveur c'est assez simple run(host='localhost', port=8080, server='gunicorn', workers=10)

  • # Avec un peu de créativité, tu peux même tout faire avec nginx

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

    Il suffit juste de faire un proxy avec X serveurs différents en backend, et chaque backend est un vhost dans le même nginx qui va toujours renvoyer une seul image (par exemple, via une directive qui déduit l'image depuis le vhost, ou simplement, en codant en dur dans la déclaration du vhost).

    L'algo de round robin va en prendre un au pif via https://nginx.org/en/docs/http/ngx_http_upstream_module.html#random , et voila.

    Ou tu peux faire ça directement en lua dans nginx, mais ç'est tricher.

    Ensuite, ça risque d'être verbeux, je sais pas si nginx a le support pour des templates de vhost, ou si devoir faire ça via un script shell (ou ansible, ou puppet ou autre) est acceptable.

  • # En groovy (sans optimisations)

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

    Dans Main.groovy

    import com.sun.net.httpserver.HttpServer
    import groovy.transform.CompileStatic
    
    @CompileStatic
    static void run(int port, int imgCount) {
      final random = new Random()
      final server = HttpServer.create(new InetSocketAddress(port), 0)
      server.createContext("/", exchange -> {
        exchange.getResponseHeaders().add(
                "Location",
                "https://avatar.spacefox.fr/Renard-" + (random.nextInt(imgCount) + 1) + ".png")
        exchange.sendResponseHeaders(302, -1)
      })
      server.start()
    }
    
    final port = Integer.parseInt(args[0])
    final imgCount = Integer.parseInt(args[1])
    run(port, imgCount)

    Pour le démarrer, j'utilise Groovy 4.0.3 :

    export JAVA_HOME=~/.jdks/corretto-17.0.3/
    $GROOVY_INSTALL/bin/groovy src/main/groovy/Main.groovy 6666 21

    Requests per second: 39136.05 #/sec

    j't'éclate ton serveur… Mais merci quand même.

    • [^] # Re: En groovy (sans optimisations)

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

      Pour info, après un second round (j’exécute le tout dans Intellij, avec pleins de choses ouvertes, comme Steam):

      Requests per second:    44935.38 [#/sec] (mean)
    • [^] # Re: En groovy (sans optimisations)

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

      Tu saurais dire quel processeur tu as, et l'occupation CPU que tu obtiens pendant le test ? Ça m'étonne de voir une telle différence alors que les deux programmes tournent sur des JVM.

      La connaissance libre : https://zestedesavoir.com

      • [^] # Re: En groovy (sans optimisations)

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

        J'ai un CPU très rapide par rapport à son enveloppe thermique (105 watts) :

        $ cat /proc/cpuinfo
        . . .
        processor       : 31
        vendor_id       : AuthenticAMD
        cpu family      : 25
        model           : 33
        model name      : AMD Ryzen 9 5950X 16-Core Processor
        stepping        : 2
        microcode       : 0xa201205
        cpu MHz         : 3276.090
        . . .

        Clairement le CPU utilise plus d'un core. Si j'ai le temps, j'essaierais de voir si c'est le GC qui utilisent toutes les autres threads… Es-tu sûr que ce soit mono-threadé hors GC ? Je n'ai pas bien compris l'explication.

        Sur la documentation de Class HttpServer ce n'est pas monothreadé de ce que je comprends…

      • [^] # Re: En groovy (sans optimisations)

        Posté par  . Évalué à 2.

        C'est pire. groovy ici ne fais que lancer du code de la bibliothèque standard de java.
        À part la gestion de String vs GString je vois pas ce qui donnerais de différence.

        https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll

        • [^] # Re: En groovy (sans optimisations)

          Posté par  (site web personnel) . Évalué à 3. Dernière modification le 13 juin 2022 à 14:37.

          En quoi est-ce pire ?

          N'as t-on pas le droit d'utiliser le langage que l'on veut sur la JVM ?

          À part complexifier l'utilisation de GraalVM, je vois pas d’inconvénients…

          La pertinence de ce test vient du fait que ce n'est pas le même parseur que celui de Java, même si la syntaxe est compatible, tu n'as pas besoin de compiler (même si tu peux le faire), et contrairement aux idées reçues, ce n'est pas tellement plus lent pour une application de type serveur. L'exemple ne se prête pas aux démonstration pédantique de syntaxe.

          Et tu as pas mal d'apport potentiel lié à Groovy… Comme les DSL.

          • [^] # Re: En groovy (sans optimisations)

            Posté par  . Évalué à 5. Dernière modification le 13 juin 2022 à 14:51.

            "C'est plus surprenant encore" (c'est une "pire" surprise)

            Et ce n'est pas surprenant parce que je m'attendais à ce que ce soit plus lent, mais parce que je m'attendais à ce que ce soit identique. La majorité du code écris que ce soit en java ou en groovy n'est exécuté qu'une seule fois au démarrage.

            Pour le reste ce qui est en java d'un coté et groovy de l'autre c'est la récupération d'un nombre aléatoire (mais ça fait appel à la même classe java) et la concaténation d'une chaîne de caractère (ce qui est réputé lent en java). D'où mon questionnement sur String vs GString (j'utilise très peu groovy je ne connais pas particulièrement les GString).

            Pour moi c'est aussi surprenant que de dire que si tu lance ab depuis bash, zsh ou fish tu obtiens des performance différentes.

            Et tu as pas mal d'apport potentiel lié à Groovy… Comme les DSL.

            Je n'ai pas eu l'occasion d'en faire suffisamment pour apprendre vraiment à m'en servir (je m'en sert essentiellement pour des DSL et j'en ai intégré un dans notre logiciel, mais c'est assez anecdotique), mais c'est un langage qui m'attire plutôt beaucoup et j'aimerais bien à minima prendre le temps de regarder le MOP et qu'est-ce que l'on peut faire avec (non pas pour en mettre partout mais pour découvrir ce qu'on peut faire avec).

            https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll

            • [^] # Re: En groovy (sans optimisations)

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

              Oui, globalement d'accord, le résultat dépend grandement de la Jvm et donc prévisible. Seule la section :

              exchange -> {
                   exchange.getResponseHeaders().add(
                        "Location",
                        "https://avatar.spacefox.fr/Renard-" + (random.nextInt(imgCount) + 1) + ".png");
                        exchange.sendResponseHeaders(302, -1);
                   }

              s'exécute à chaque requête. Difficile d'optimiser le code, on peut jouer seulement sur les paramètres de la JVM. Cela dit, je ne sais pas si le contexte est multi-threadé ou non. J'ai testé pour le fun avec Graalvm, aucune différence.

              • [^] # Graal VM

                Posté par  . Évalué à 0.

                L'intérêt que pourrait présenter Graal VM en compilation native pour ce type de code est le temps de démarrage du processus et l'économie du JIT lors des premières exécutions qui sont donc immédiatement optimales.
                Et aussi l'empreinte mémoire devrait être faible qu'avec une exécution en JVM/Hotspot car le tas/heap n'est pas la seule zone mémoire allouée par la JVM, il faut y regarder plus en détail et vérifier les allocations natives pour le metaspace (.class, code produit par le jit, zone de traitement du gc…)

  • # PHP ?

    Posté par  . Évalué à 6.

    L’implémentation actuelle est faite avec trois lignes de PHP, ce qui m’ennuie un peu parce que c’est le seul outil qui a encore besoin de PHP sur mon serveur.

    Mais donc si j'ai bien compris tu multiplies le nombre de lignes par 3 de PHP vers Java ? (métrique qui ne veut rien dire oui mais bon, le journal en parle…)

    Je serais curieux de savoir ce que peut donner l’exercice dans votre langage préféré.

    Tu as lancé le benchmark sur ta version PHP ? Pour voir d'où l'on part.

    • [^] # Re: PHP ?

      Posté par  . Évalué à 2.

      Je trouve que cela vaudrait vraiment le coup. Car PHP sera compilé en byte-code et entièrement en RAM et à mon avis il ne consommera pas plus que Java sur ce cas pour les même perfs (à minima).

      PHP est moins bon que Java dans la gestion des tableau (ou classes) et dans les boucles. Là tu n'en a pas…

    • [^] # Re: PHP ?

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

      Je n’ai pas lancé le benchmark PHP, parce que j’ai complètement la flemme d’installer PHP juste pour ça sur le nouveau serveur, et de configurer un nouvel endpoint qui réponde en HTTP(non S) juste pour ça. Je sais juste que la version actuelle est « très largement suffisante en conditions réelles ».

      D’ailleurs, j’ai dit une connerie, elle ne fait pas 3 lignes mais 2 – il n’y a bas de balise fermante :

      <?php
          header('Location: Renard-'.mt_rand(1,21).'.png');

      Et c’est lancé avec un proxy socket Unix vers PHP-FPM.

      La métrique du nombre de lignes était là parce que l’une des critiques très souvent faites à Java est que c’est un langage verbeux. Ça s’entends bien sûr en tant que « lignes correctement formatées », pas des one-liners idiots.

      La connaissance libre : https://zestedesavoir.com

  • # native-image

    Posté par  . Évalué à 4.

    Je serais curieux de savoir ce que peut donner l’exercice dans votre langage préféré.

    Mon langage préféré c'est java donc bon… J'ai pas de doute qu'utiliser netty ou une surcouche peut permettre d'aller bien plus loin.

    Ton exemple m'a donné envi d'essayer native image de graalvm.

    % tree
    .
    └── fr
        └── spacefox
            └── avatar
                └── AvatarHttpServer.java
    
    3 directories, 1 file
    % javac --release 17 fr/spacefox/avatar/AvatarHttpServer.java
    % native-image fr.spacefox.avatar.AvatarHttpServer

    Comme souvent dis c'est long à compiler, mais on obtient un binaire final :

    % ldd fr.spacefox.avatar.avatarhttpserver
        linux-vdso.so.1 (0x00007ffd4657c000)
        libgtk3-nocsd.so.0 => /usr/lib/x86_64-linux-gnu/libgtk3-nocsd.so.0 (0x00007f0a02043000)
        libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f0a02020000)
        libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f0a0201a000)
        libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007f0a01ffe000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f0a01e0c000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f0a0322c000)
    % ls -sh fr.spacefox.avatar.avatarhttpserver
    16M fr.spacefox.avatar.avatarhttpserver

    Avec ta version (lancé avec java18 avec les même paramètres que toi) j'obtiens 13.5k hit/s avec le binaire j'obtiens autour des 15k hit/s. L'option -verser ne change pas grand chose au résultat.

    33 lignes avec tout le boilerplate, c’est dix fois plus que l’implémentation actuelle. J’aurais pu gratter un peu en ne permettant pas la configuration du port ou du nombre d’images (la version actuelle ne le fait pas) mais ça ne change pas l’ordre de grandeur.

    Tu compare quoi à quoi ? J'imagine que tu ne lance pas le serveur web de PHP, mais que tu utilise php-fpm ou quelque chose du genre, non ? Il consomme combien de mémoire ?

    https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll

    • [^] # Re: native-image

      Posté par  (site web personnel, Mastodon) . Évalué à 4. Dernière modification le 13 juin 2022 à 14:19.

      L'option -server (s'il s'agit bien d'elle) ne sert plus à rien sur les JVM HotSpot et dérivées 64 bits, la JVM "server" est la seule qui existe encore.

      Et j'ai la flemme d'installer PHP sur le nouveau serveur et d'autoriser une connexion en HTTP simple pour faire le test.

      La connaissance libre : https://zestedesavoir.com

      • [^] # Re: native-image

        Posté par  . Évalué à 3.

        L'option -server (s'il s'agit bien d'elle)

        Ma dyslexie a encore frappé (oui oui je suis diagnostiqué).

        L'option -server (s'il s'agit bien d'elle) ne sert plus à rien sur les JVM HotSpot et dérivées 64 bits, la JVM "server" est la seule qui existe encore.

        Ah oui j'avais vu mais complètement oublié ! Merci du rappel.

        https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll

        • [^] # Re: native-image

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

          Pas de quoi, on la retrouve encore très souvent (y compris dans des prétendus « papiers de recherche sur l’état de l’art de la performance de la JVM », qui perdent d’un coup pas mal de crédibilité).

          La connaissance libre : https://zestedesavoir.com

  • # Rust et C

    Posté par  . Évalué à -1.

    Pour un programme aussi simple et statique, coder en C++ ou Rust me parait aussi simple et beaucoup plus efficace (en tout cas par rapport à Java).

    • [^] # Re: Rust et C

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

      Cf les commentaires de l'article sur Zeste de Savoir : tout dépend de ce que tu appelles beaucoup plus efficace. En consommation RAM, plutôt oui. En débit en sortie ou en complexité de code, pas tant que ça (si, y'a une implémentation en C/C++ qui est beaucoup plus rapide mais beaucoup plus complexe).

      La connaissance libre : https://zestedesavoir.com

      • [^] # Re: Rust et C

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

        La JVM va maximiser le "Throughput" (ou débit), au détriment de la charge CPU et mémoire. Elle va tout faire pour aller le plus vite possible, quitte à recompiler le code à la volé (charge CPU supplémentaire), ou ne pas désallouer lorsque la mémoire n'est plus utilisée.

        C'est complexe à faire avec d'autres langages qui vont chercher à maximiser la vitesse, sans doute en utilisant moins de ressources.

  • # Très intéressant

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

    Merci pour cette expérience. Le résultat est intéressant, car la performance n'est pas un objectif du serveur http fournit avec le jdk.

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

  • # Java pur ?

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

    Je n'ai pas suivi de quand date l'ajout d'un serveur HTTP dans les JVM, mais si tu dois importer une classe de com.sun c'est que ce n'est pas dans la norme non ?

    • [^] # Re: Java pur ?

      Posté par  . Évalué à 5.

      Il date de java 1.6 et il est décrit dans la javadoc donc il fait bien parti de la bibliothèque standard

      https://docs.oracle.com/en/java/javase/18/docs/api/jdk.httpserver/com/sun/net/httpserver/HttpServer.html

      Tu peut trouver son code dans OpenJDK

      https://github.com/openjdk/jdk/blob/master/src/jdk.httpserver/share/classes/com/sun/net/httpserver/HttpServer.java

      https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll

    • [^] # Re: Java pur ?

      Posté par  . Évalué à 4.

      C'est là depuis au moins la JVM 1.6 (donc y'a quelques années). Mais si je suis bien ce que dit SpaceFox, c'est pas que ça fait partie de la norme, mais que c'est inclus dans la JVM de base, donc pas de dépendance supplémentaire.

    • [^] # Re: Java pur ?

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

      Je confirme, la classe date de Java 6 mais est peu utilisée. D’une part parce qu’elle est d’assez bas niveau et donc peu pratique pour des projets un peu complexe, d’autre part parce que c’est une API en com.sun.* que beaucoup de gens (y compris des outils automatiques !) confondent avec une API en sun.*, ces dernières ne devant pas être utilisées parce que privées (et supprimées des dernières versions de Java). Les API en com.sun.* sont pourtant standard (on les retrouve dans toutes les implémentations de Java, mêmes celles historiquement IBM) et sont simplement déconseillées dans quelques usages précis.

      J’ai fait aussi un test rapide avec des outils plus « industriels » pour ce genre d’application (un microserveur minimaliste), comme vert.x. On a un peu moins de code un peu plus clair, et des performances sensiblement identiques (même la consommation RAM, ce qui m’a surpris). C’est aussi un facteur qui explique le manque d’intérêt pour HttpServer.

      La connaissance libre : https://zestedesavoir.com

    • [^] # Re: Java pur ?

      Posté par  . Évalué à 4.

      À noter par contre que depuis java 11 il y a un client http https://docs.oracle.com/en/java/javase/18/docs/api/java.net.http/java/net/http/HttpClient.html (qui lui est dans java.net.http.* qui est probablement l'endroit où devrait être le serveur).

      https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll

  • # Rustlang

    Posté par  (site web personnel) . Évalué à 7. Dernière modification le 13 juin 2022 à 20:54.

    En rust;

    $ cargo init renard

    Dans le fichier Cargo.toml sous [dependencies] ajouter :
    actix-web = "4"
    rand= "0.8.5"

    Dans le src/main.rs :

    use actix_web::{get, App, HttpResponse, HttpServer, Responder};
    use rand::Rng;
    
    
    #[get("/")]
    async fn hello() -> impl Responder {
        let mut rng = rand::thread_rng();
        HttpResponse::TemporaryRedirect()
         .append_header(("Location", format!("https://avatar.spacefox.fr/Renard-{}.png",rng.gen_range(1..21))))
         .finish()
    }
    
    #[actix_web::main]
    async fn main() -> std::io::Result<()> {
    
        HttpServer::new(|| {
            App::new()
                .service(hello)            
        })
        .bind(("127.0.0.1", 8080))?
        .run()
        .await
    }
    

    avec le test :
    $ cargo build
    $ cargo run
    $ ab -n 100000 -c 10 http://localhost:8080/

    Requests per second: 12180.63 #/sec

    Et en version release :
    $ cargo build --release
    $ cargo run --release
    $ ab -n 100000 -c 10 http://localhost:8080/

    Requests per second: 23764.99 #/sec

    Percentage of the requests served within a certain time (ms)
    50% 0
    66% 0
    75% 0
    80% 0
    90% 0
    95% 1
    98% 1
    99% 1
    100% 3 (longest request)

    ps aux :
    VSZ : 338Ko RSS: 4.2Ko
    cpu : i7-8565U CPU @ 1.80GHz

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

  • # Record

    Posté par  . Évalué à 3.

    Hello. On peut éliminer le constructeur en définissant ton Server comme étant un record?

  • # Record

    Posté par  . Évalué à -2.

    Hello. On peut éliminer le constructeur en définissant ton Server comme étant un record?

  • # Version Rust

    Posté par  . Évalué à 4.

    Voici ma version Rust (je ne suis pas un expert). On a moins de librairies disponible dans le "core", il m'a donc semblé juste d'utiliser des dépendances.

    #![deny(warnings)]
    
    use std::convert::Infallible;
    use rand::Rng;
    use hyper::service::{make_service_fn, service_fn};
    use hyper::{Body, Method, Request, Response, Server, StatusCode};
    
    
    async fn random_fox(req: Request<Body>) -> Result<Response<Body>, Infallible> {
        match (req.method(), req.uri().path()) {
            (&Method::GET, "/") => {
                let mut randomGenerator = rand::thread_rng();
                let imgCount=20;
                let rndInt=randomGenerator.gen_range(1..imgCount);
                let rndStr=rndInt.to_string();
                let url = "https://avatar.spacefox.fr/Renard-".to_owned()+&rndStr+ ".png";
                let set_location = Response::builder()
                    .header(hyper::header::LOCATION, url)
                    .status(StatusCode::MOVED_PERMANENTLY)
                    .body(Body::from("")).unwrap();
                Ok(set_location)
            },
            // Return the 404 Not Found for other routes.
            _ => {
                let mut not_found = Response::default();
                *not_found.status_mut() = StatusCode::NOT_FOUND;
                Ok(not_found)
            }
        }
    }
    
    #[tokio::main]
    pub async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
        let port = std::env::args().nth(1).expect("no port given")
            .parse::<u16>().unwrap();
        let _imgCount:u16 = std::env::args().nth(2).expect("no imgCount given")
            .parse::<u16>().unwrap();
    
        pretty_env_logger::init();
        let make_svc = make_service_fn(|_conn| {
            async { Ok::<_, Infallible>(service_fn(|req| random_fox(req))) }
        });
        let addr = ([127, 0, 0, 1], port).into();
        let server = Server::bind(&addr).serve(make_svc);
        println!("Listening on http://{}", addr);
        server.await?;
        Ok(())
    }

    Et le Cargo.toml
    ```ini
    [package]
    name = "random_url_server"
    version = "0.1.0"
    edition = "2021"

    [dependencies]
    hyper = { version = "0.14", features = ["full"] }
    tokio = { version = "1", features = ["full"] }
    rand = { version = "*"}
    pretty_env_logger = "0.4"
    ```

    • [^] # Re: Version Rust

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

      Tu as pu comparer la vitesse avec ma version ?

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

      • [^] # Re: Version Rust

        Posté par  . Évalué à 1.

        Non, je ne sais pas trop comment m'y prendre

        • [^] # Re: Version Rust

          Posté par  . Évalué à 2.

          alberic@minicoda6:~/scripts-utils/diffuse $ ab -n 100000 -c 10 http://localhost:3000/
          This is ApacheBench, Version 2.3 <Revision: 1843412>
          Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
          Licensed to The Apache Software Foundation, http://www.apache.org/

          Benchmarking localhost (be patient)
          Completed 10000 requests
          Completed 20000 requests
          Completed 30000 requests
          Completed 40000 requests
          Completed 50000 requests
          Completed 60000 requests
          Completed 70000 requests
          Completed 80000 requests
          Completed 90000 requests
          Completed 100000 requests
          Finished 100000 requests

          Server Software:

          Server Hostname: localhost
          Server Port: 3000

          Document Path: /
          Document Length: 0 bytes

          Concurrency Level: 10
          Time taken for tests: 7.984 seconds
          Complete requests: 100000
          Failed requests: 0
          Non-2xx responses: 100000
          Total transferred: 14152909 bytes
          HTML transferred: 0 bytes
          Requests per second: 12525.70 #/sec
          Time per request: 0.798 ms
          Time per request: 0.080 ms
          Transfer rate: 1731.20 [Kbytes/sec] received

          Connection Times (ms)
          min mean[+/-sd] median max
          Connect: 0 0 0.1 0 3
          Processing: 0 0 0.3 0 12
          Waiting: 0 0 0.3 0 11
          Total: 0 1 0.3 1 12

          Percentage of the requests served within a certain time (ms)
          50% 1
          66% 1
          75% 1
          80% 1
          90% 1
          95% 1
          98% 2
          99% 2
          100% 12 (longest request)

          • [^] # Re: Version Rust

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

            et avec mon code ? il faut comparer avec la même machine, c'est plus simple pour comprendre.

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

            • [^] # Re: Version Rust

              Posté par  . Évalué à 2. Dernière modification le 14 juin 2022 à 16:35.

              Cela fais longtemps que je n'ai pas pratiqué Java.

              J'ai fais mon makefile:

              AvatarHttpServer.class: AvatarHttpServer.java
                  javac AvatarHttpServer.java
              
              AvatarServer-1.0-SNAPSHOT.jar: AvatarHttpServer.class
                  jar cfM AvatarServer-1.0-SNAPSHOT.jar AvatarHttpServer.class
              
              all: AvatarServer-1.0-SNAPSHOT.jar
              
              run: AvatarServer-1.0-SNAPSHOT.jar
                  java -Xmx8m -Xss256k -jar ./AvatarServer-1.0-SNAPSHOT.jar 3000 21
              
              clean:
                  rm -f ./AvatarServer-1.0-SNAPSHOT.jar ./AvatarHttpServer.class

              Mais "make run" me dit :

              java -Xmx8m -Xss256k -jar ./AvatarServer-1.0-SNAPSHOT.jar 3000 21
              Error: Invalid or corrupt jarfile ./AvatarServer-1.0-SNAPSHOT.jar
              
              
              • [^] # Re: Version Rust

                Posté par  . Évalué à 2.

              • [^] # Re: Version Rust

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

                Il existe donc réellement des personnes qui utilisent des makefiles avec Java ?

                Personnellement je ne me rappelle plus la dernière fois ou j’ai utilisé autre chose que Maven, Gradle ou très rarement Ant. Et oui, ce couplage à des toolings lourdingues peut clairement être listé dans les inconvénients de Java.

                La connaissance libre : https://zestedesavoir.com

                • [^] # Re: Version Rust

                  Posté par  . Évalué à 2.

                  Il existe donc réellement des personnes qui utilisent des makefiles avec Java ?

                  Il existe surtout des personnes qui n'utilisent pas Java ;) J'aime bien le Makefile pour automatiser certaines compilation en tests. Je suis de la vielle école.

                  Mais bon, j'ai réussi…

                  This is ApacheBench, Version 2.3 <$Revision: 1843412 $>
                  Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
                  Licensed to The Apache Software Foundation, http://www.apache.org/
                  
                  Benchmarking localhost (be patient)
                  Completed 10000 requests
                  Completed 20000 requests
                  Completed 30000 requests
                  Completed 40000 requests
                  Completed 50000 requests
                  Completed 60000 requests
                  Completed 70000 requests
                  Completed 80000 requests
                  Completed 90000 requests
                  Completed 100000 requests
                  Finished 100000 requests
                  
                  
                  Server Software:        
                  Server Hostname:        localhost
                  Server Port:            3000
                  
                  Document Path:          /
                  Document Length:        0 bytes
                  
                  Concurrency Level:      10
                  Time taken for tests:   13.204 seconds
                  Complete requests:      100000
                  Failed requests:        0
                  Non-2xx responses:      100000
                  Total transferred:      16157208 bytes
                  HTML transferred:       0 bytes
                  Requests per second:    7573.62 [#/sec] (mean)
                  Time per request:       1.320 [ms] (mean)
                  Time per request:       0.132 [ms] (mean, across all concurrent requests)
                  Transfer rate:          1195.01 [Kbytes/sec] received
                  
                  Connection Times (ms)
                                min  mean[+/-sd] median   max
                  Connect:        0    0   0.2      0      11
                  Processing:     0    1   1.0      1     105
                  Waiting:        0    1   0.9      1     103
                  Total:          0    1   1.0      1     106
                  
                  Percentage of the requests served within a certain time (ms)
                    50%      1
                    66%      1
                    75%      1
                    80%      2
                    90%      2
                    95%      3
                    98%      4
                    99%      5
                   100%    106 (longest request)
                  

                  Je suis donc pasé de 12 000 en Rust à 7 573 en Java ;)

                  • [^] # Re: Version Rust

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

                    Et ton code rust vs le mien ?

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

                    • [^] # Re: Version Rust

                      Posté par  . Évalué à 1.

                      Je n'ai pas testé, en fait, je suis un débutant en Rust (plus habitué au PHP et C). J'ai pris ça en exercice et après j'ai vu le tiens. Je pense que le tiens est plus léger au niveau plugin au moins.

                • [^] # Re: Version Rust

                  Posté par  . Évalué à 2.

                  Et oui, ce couplage à des toolings lourdingues peut clairement être listé dans les inconvénients de Java.

                  Comme je le montrais plus haut :

                  % tree
                  .
                  └── fr
                      └── spacefox
                          └── avatar
                              └── AvatarHttpServer.java
                  
                  3 directories, 1 file
                  % javac --release 17 fr/spacefox/avatar/AvatarHttpServer.java
                  # native image c'était pour compiler le bytecode en binaire elf sinon
                  % java fr.spacefox.avatar.AvatarHttpServer 8080 21

                  Mais si vraiment ça parait être un couplage à un toolings lourdingues de devoir appeler un compilateur :

                  % rm fr/spacefox/avatar/AvatarHttpServer.class
                  % tree
                  .
                  └── fr
                      └── spacefox
                          └── avatar
                              └── AvatarHttpServer.java
                  
                  3 directories, 1 file
                  % java fr/spacefox/avatar/AvatarHttpServer.java 8080 21 # oui oui ".java"

                  Après c'est peu utilisé, mais c'est parce qu'ils ne sont pas si lourdingues que ça par rapport à ce qu'ils apportent ;)

                  https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll

                  • [^] # Re: Version Rust

                    Posté par  (site web personnel, Mastodon) . Évalué à 2. Dernière modification le 14 juin 2022 à 17:37.

                    Non mais ça c’est un cas particulier pour une seule classe, dans la vie c’est un usage ultra-minoritaire de Java, et ça n’est absolument pas de ce cas dont je parle…

                    La connaissance libre : https://zestedesavoir.com

                    • [^] # Re: Version Rust

                      Posté par  . Évalué à 1. Dernière modification le 15 juin 2022 à 00:08.

                      Je suis pas d'accord. On parle de quelqu'un qui a du mal à essayer ton exemple parce qu'il tente d'utiliser ce qu'il sait de sa toolchain habituelle dans une qui n'a rien à voir.

                      Utiliser make pour compiler des fichiers sources java un à un, c'est faire rentrer un rond dans un carré et c'est pareil si au lieu de java, on parlait de go, rust, julia et tout un tas d'autres1.

                      Les cas simples sont devenu triviaux en java2.

                      Quand tu veut faire de la gestion de dépendance, tu entre dans une classe de problèmes qui n'ont rien à voir et make ne fera rien pour toi. On peut sincèrement critiquer maven, mais il joue clairement dans la cours des pas mauvais élèves quand il s'agit de gérer des dépendances3.

                      maven 4 est prévu pour tenter de s'améliorer aussi.


                      1. et ce n'est pas une critique de la personne. C'est tout à fait logique de se baser sur ce que l'on connaît. J'essayais de montrer qu'il était possible de faire sans s'embêter 

                      2. en l’occurrence par exemple, construire un jar n'apporte rien. C'est, j'imagine, simplement une habitude de ta part. 

                      3. et il a construit un écosystème ce qui est une base pour que toutes les alternatives à maven ont put se faire une place (gradle, sbt, etc) et des petits nouveaux comme jbang qui peut peut être intéresser ceux qui n'apprécient vraiment pas les autres 

                      https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll

    • [^] # Re: Version Rust

      Posté par  . Évalué à 2.

      Pour info, j'ai teste ton code chez moi, avec ab et wrk :

      $ ab -k -n 100000 -c 100 http://localhost:3000/ |grep "Requests per second:"
      Requests per second:    135280.84 [#/sec] (mean)
      
      $ wrk -t12 -c400 -d10s http://localhost:3000/
      4364506 requests in 10.07s
      

      avec un Intel(R) Core(TM) i7-10850H CPU @ 2.70GHz

  • # ab = mauvais

    Posté par  . Évalué à 2.

    le a de ab, ça signifie apache, cet outil est très peu efficace pour faire des benchmarks.

    Pour faire des benchmarks, il faut utiliser wrk, qui vient de chez Nginx:
    https://github.com/wg/wrk

    Sinon, si vous voulez voir des benchmarks HTTP qui ont réellement de la gueule, alors allez chez TechEmpower:
    https://www.techempower.com/benchmarks/

    • [^] # Re: ab = mauvais

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

      le a de ab, ça signifie apache, cet outil est très peu efficace pour faire des benchmarks.

      Et tu peux expliquer pourquoi.

      • [^] # Re: ab = mauvais

        Posté par  . Évalué à 5.

        ab sature très vite, alors qu'avec wrk c'est assez facile de faire tomber l'application en face.

        L'architecture interne de wrk est bien meilleure, il y a quelques posts sur le Web qui l'explique:
        https://engineering.appfolio.com/appfolio-engineering/2019/4/21/wrk-it-my-experiences-load-testing-with-an-interesting-new-tool

        Le benchmark que j'ai cité plus haut utilise aussi wrk:
        https://blog-en.richardimaoka.net/techempower-on-aws

        Mais, le mieux, c'est de tester wrk, avec les bons paramètres, cette application Java sera très certainement par terre, contrairement avec ab.

        Bref, arrêtez d'utiliser ab, ça ne prouve en rien que votre application tient la charge.

        • [^] # Re: ab = mauvais

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

          Pour la petite histoire, j'ai utilisé ab parce que c'est ce que j'avais sous la main. Comme l'observation du comportement de l'application testée me montrait que l'application était bien limitée par le CPU avec cet outil, je n'ai pas eu besoin d'aller plus loin.

          Mais effectivement pour des langages plus performants (en particulier dès que l'application testée est multithreadée) ab ne suffit plus, comme mentionné dans le journal.

          Ça montre aussi que lancer des tests sans surveiller le comportement de l'application ne sert à rien, puisque ça ne permet pas d'en déduire quoi que ce soit quand aux résultats observés.

          Enfin, le simple terme "tenir la charge" n'a pas de sens en soi. Par exemple ici, il faudrait vraiment un programme très créatif pour qu'il soit incapable de tenir la charge réelle qui va lui être demandée, même en imaginant l'héberger sur une brouette comme un premier Rapsberry Pi. En ce sens, un programme comme ab peut très bien prouver que les performances mesurées sont "très largement suffisantes", même si faussées parce qu'on atteint les limites de l'outil de mesure.

          La connaissance libre : https://zestedesavoir.com

        • [^] # Re: ab = mauvais

          Posté par  . Évalué à 7.

          Mais, le mieux, c'est de tester wrk, avec les bons paramètres, cette application Java sera très certainement par terre, contrairement avec ab.

          J'ai compilé wrk et j'ai essayé j'obtiens les même perf qu'avec ab. La différence entre les 2 outils est en dessous de la marge d'erreur. J'ai tenté de faire varier le nombre de threads et le nombre de connexion. Je fais effectivement tomber le service en augmentant le nombre de connexions, mais de même avec ab.

          Bref, arrêtez d'utiliser ab, ça ne prouve en rien que votre application tient la charge.

          wrk non plus, hein ? Les 2 sont des outils triviaux pour se faire une idée sans se prendre la tête. Des outils de se type tu en a pleins comme vegeta par exemple qui lui vise plus à imposer un débit défini.

          https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll

  • # une version en haskell

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

    {-# LANGUAGE OverloadedStrings #-}
    import           Control.Concurrent.STM
    import qualified Data.ByteString.Char8 as C
    import           Fmt
    import           Network.Wai 
    import           Network.HTTP.Types
    import           Network.Wai.Handler.Warp 
    import           System.Environment
    import           System.Random.Stateful
    import           Text.Read 
    
    app :: RandomGen g => Int -> TGenM g -> Application
    app nImgs rngVar _ respond = do
      i <- atomically $ applyTGen (uniformR (1, nImgs)) rngVar
      let url = fmt $ "https://avatar.spacefox.fr/Renard-" +| i |+ ".png"
      respond $ responseLBS status302 [("Location", url)] ""
    
    main :: IO ()
    main = do
      args <- fmap readMaybe <$> getArgs
      rngVar <- getStdGen >>= newTGenMIO
      case args of
        [Just port, Just nImgs] -> do
          C.putStrLn $ fmt $ "listening on port " +| port |+ ""
          run port (app nImgs rngVar)
        _ -> C.putStrLn "usage: <port> <nImgs>"

    compilation/exécution:

    $ ghc -O2 -rtsopts serve.hs
    $ ./serve 8080 21
    

    benchmark (AMD Ryzen 7 PRO 4750U, 1.7GHz) :

    $ ab -n 100000 -c 10 http://localhost:8080/
    This is ApacheBench, Version 2.3 <$Revision: 1879490 $>
    Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
    Licensed to The Apache Software Foundation, http://www.apache.org/
    
    Benchmarking localhost (be patient)
    Completed 10000 requests
    Completed 20000 requests
    Completed 30000 requests
    Completed 40000 requests
    Completed 50000 requests
    Completed 60000 requests
    Completed 70000 requests
    Completed 80000 requests
    Completed 90000 requests
    Completed 100000 requests
    Finished 100000 requests
    
    
    Server Software:        Warp/3.3.20
    Server Hostname:        localhost
    Server Port:            8080
    
    Document Path:          /
    Document Length:        0 bytes
    
    Concurrency Level:      10
    Time taken for tests:   5.373 seconds
    Complete requests:      100000
    Failed requests:        0
    Non-2xx responses:      100000
    Total transferred:      13157347 bytes
    HTML transferred:       0 bytes
    Requests per second:    18612.30 [#/sec] (mean)
    Time per request:       0.537 [ms] (mean)
    Time per request:       0.054 [ms] (mean, across all concurrent requests)
    Transfer rate:          2391.49 [Kbytes/sec] received
    
    Connection Times (ms)
                  min  mean[+/-sd] median   max
    Connect:        0    0   0.0      0       1
    Processing:     0    0   0.1      0       6
    Waiting:        0    0   0.1      0       5
    Total:          0    1   0.1      1       6
    
    Percentage of the requests served within a certain time (ms)
      50%      1
      66%      1
      75%      1
      80%      1
      90%      1
      95%      1
      98%      1
      99%      1
     100%      6 (longest request)
    
  • # Et si le plus simple était de se passer de code ?

    Posté par  . Évalué à 2.

    En lisant, j'ai eu l'instinct qu'une chose aussi simple serait sûrement faisable directement par le serveur web : banco avec ngx_http_random_index_module sur nginx. Peut-être la solution la plus simple ?

Suivre le flux des commentaires

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