Ich habe ja Ende April für dieses Blog HTTP/3 aktiviert. Ich hatte damals schon ein paar Performancetests gemacht, aber ich wollt die noch einmal ein bisschen ordentlicher machen. Das habe ich jetzt gemacht.
Vorweg: der mit Ubuntu ausgelieferte curl-Build hat die HTTP/3-Option nicht aktiviert. Ich hatte gehofft, dass das mit Ubuntu 26.04 anders werden würde, doch dem war nicht so. Also habe ich die Tests von einem Raspberry Pi 3 aus laufen lassen. Dieser Pi ist per Wifi an mein Netzwerk angeschlossen.
Messreihe 1: Mit offen gehaltener Verbindung
Ich benutzer wieder hyperfine um die Messungen zu machen, denn die Messwerte schwanken oft sehr stark. Ich lade mit curl eine JPEG-Datei aus dem Kochprojekt von diesem Blog. Die Hyperfine-Option -w 1 bedeutet, dass erst einmal ein „Warmup“ durchgeführt wird, bevor die Zeit gemessen wird. Das hilft, damit der Server die Datei schon geladen hat, außerdem hält sich curl selber Verbindungen offen. Also starten wir mal:
$ hyperfine -w 1 "curl --http1.1 https://blog.strangerthanusual.de/file/kochprojekt_9_07.jpg > /dev/null"
Benchmark 1: curl --http1.1 https://blog.strangerthanusual.de/file/kochprojekt_9_07.jpg > /dev/null
Time (mean ± σ): 337.0 ms ± 14.5 ms [User: 123.0 ms, System: 20.3 ms]
Range (min … max): 318.6 ms … 359.5 ms 10 runs
Explizit auf HTTP/1.1 gesetzt, im Mittel 337 ms. Als nächstes HTTP/2:
$ hyperfine -w 1 "curl --http2-prior-knowledge https://blog.strangerthanusual.de/file/kochprojekt_9_07.jpg > /dev/null"
Benchmark 1: curl --http2-prior-knowledge https://blog.strangerthanusual.de/file/kochprojekt_9_07.jpg > /dev/null
Time (mean ± σ): 354.2 ms ± 22.3 ms [User: 124.4 ms, System: 19.9 ms]
Range (min … max): 331.4 ms … 404.2 ms 10 runs
Hmm… 354 ms und höhere Varianz. Sollte das nicht eigentlich schneller gehen? Naja, probieren wir mal HTTP/3:
$ hyperfine -w 1 "curl --http3-only https://blog.strangerthanusual.de/file/kochprojekt_9_07.jpg > /dev/null"
Benchmark 1: curl --http3-only https://blog.strangerthanusual.de/file/kochprojekt_9_07.jpg > /dev/null
Time (mean ± σ): 383.4 ms ± 20.7 ms [User: 142.1 ms, System: 23.0 ms]
Range (min … max): 364.1 ms … 425.6 ms 10 runs
Noch langsamer. Aber warum?
Warum sind diese Werte mit Vorsicht zu genießen?
Ich habe hier nur einen Testfall gehabt: Eine Datei geladen, sequenziell, mit derselben Verbindung. Hier gibt es mehrere Probleme:
- HTTP/3 hat weniger Round-Trips beim Verbindungsaufbau. Wir nutzen hier aber nur eine Verbindung, die wir vorher initialisieren.
- HTTP/3 und HTTP/2 können über dieselbe Verbindung parallel Anfragen stellen. Das machen wir hier aber nicht, sondern stellen alle Nachfragen nacheinander
- Die Vorteile von HTTP/3 sind am größten bei schlechtem Netz. Ich habe aber recht stabiles Netz hier, trotz Wifi.
Punkt 2 hier zu testen ist mir gerade zu doof. Aber vielleicht kann man ja jedes Mal eine neue Verbindung nehmen? Wie sieht es dann aus?
Messreihe 2: Ohne Verbindungscache
Disclaimer: Es kann sehr gut sein, dass ich falsch verstanden habe, wie curl Verbindungscaches behandelt, aber --no-keepalive hat keinen Nennenswerten Unterschied gemacht. Also vielleicht probiere ich doch einmal das Parallelladen.
Messreihe 3: Mehrere Dateien anfragen
Wenn man eine Website aufruft, werden ja üblicherweise mehrere Dateien geladen. HTML, CSS, Bilder, Javascript, … ich habe das mal unvollständig simuliert:
$ hyperfine -w 1 "curl --parallel --no-keepalive --http1.1 'https://blog.strangerthanusual.de/blogposts/92' 'https://blog.strangerthanusual.de/assets/style.css?cache=3440db11c282e87069ad25da8143b4a4a7729799639d35efe4781159757275fc' 'https://blog.strangerthanusual.de/file/kochprojekt_9_01.jpg' 'https://blog.strangerthanusual.de/file/kochprojekt_9_02.jpg' 'https://blog.strangerthanusual.de/assets/favicon.svg?cache=ad19a16b8769e48ca81229ccdf62e9f59c51e1e4d1130bfa6c59d05cda9a0dd9' > /dev/null"
Benchmark 1: curl --parallel --no-keepalive --http1.1 'https://blog.strangerthanusual.de/blogposts/92' 'https://blog.strangerthanusual.de/assets/style.css?cache=3440db11c282e87069ad25da8143b4a4a7729799639d35efe4781159757275fc' 'https://blog.strangerthanusual.de/file/kochprojekt_9_01.jpg' 'https://blog.strangerthanusual.de/file/kochprojekt_9_02.jpg' 'https://blog.strangerthanusual.de/assets/favicon.svg?cache=ad19a16b8769e48ca81229ccdf62e9f59c51e1e4d1130bfa6c59d05cda9a0dd9' > /dev/null
Time (mean ± σ): 660.9 ms ± 11.6 ms [User: 431.8 ms, System: 44.0 ms]
Range (min … max): 649.8 ms … 681.8 ms 10 runs
660 ms also. Für HTTP/2 sind es hingegen nur 385 ms und für HTTP/3 sind es 437 ms. Wie man erwarten konnte schneidet HTTP/1.1 hier deutlich schlechter ab als HTTP/2 und 3, aber HTTP/3 ist auch deutlich schlechter als 2. Warum? Ich weiß es nicht, ich kann nur Mutmaßungen anstellen.
Mögliche Gründe, warum HTTP/3 hier langsamer ist
- die HTTP/3-Implementierung in curl (client) ist nicht so gut optimiert wie die für HTTP/2
- die HTTP/3-Implementierung in nginx (server) ist nicht so gut optimiert wie die für HTTP/2
- ich habe irgendwo beim Konfigurieren von nginx oder bei den Parametern von curl etwas falsch gemacht. Zweiteres ist unwahrscheinlich, weil Browser mir das gleiche Bild liefern
- Für diesen Use-Case ist HTTP/2 einfach besser und HTTP/3 glänzt an anderen Stellen
Die Liste ist nicht erschöpfend, ich gibt sicher noch weitere Möglichkeiten. Wie gesagt, ich weiß es nicht. Ich werde die Sache einfach weiterhin beobachten.
Save a Roundtrip
Was ich neulich auch gefunden haben savearoundtrip.com. Dort wird beschrieben, dass es auch noch eine DNS-Konfguration machen kann um dem Client noch schneller mitzuteilen, dass der Server HTTP/3 unterstützt. Werde ich für meinen Server vielleicht irgendwann mal scharf schalten, aber vorerst noch nicht. Für meine kleinen Experimente oben hat das eh keinen Unteschied gemacht, weil ich hier curl dazu gezwungen habe, bestimmte HTTP-Versionen zu nehmen.