protofunc()

JS-Ladeperformance: Müßen wir die yslow-Regeln überdenken?

Tags: Performance, deutsch, javascript

Es gibt wohl auf der Welt keinen Frontend Entwickler, der nicht die wichtigsten Performance Regeln des Exceptional Performance teams von yahoo kennt.

Der wichtigste Teil dieser Regeln wurde 2006 erstellt und nicht nur Entwickler haben dazugelernt. Auch die Browser-Hersteller haben ihre Hausaufgaben gemacht und Performanceoptimierungen für die größten Ladebremsen durchgeführt. Einige dieser Browser-Optimierungen müßen zu einer Änderung der Regeln für Entwickler führen. Die bline Befolgung der Performance-Regeln führt zu einer ungerechtfertigten Bevorzugung des IE6 und des IE7 zu Lasten moderner Browser.

Put your JavaScript at the bottom

Die blinde Befolgung dieser Regel war mir schon immer ein Dorn im Auge. Wer sich die Ratio dieser Regel durchliest, könnte denken, daß Scripte, welche am Ende der Seite eingebunden sind, nicht mehr blockend laden. In Wirklichkeit blocken konventionell eingebundene Scripte immer, gleichgültig an welcher Stelle sie eingebunden wurden. Lediglich weil danach keine weiteren Resourcen kommen sollten, fällt dies nicht so gravierend auf. An der Gesamtladezeit einer Seite ändert sich hierdurch jedoch nichts. Lediglich die subjektive Ladezeit wird herabgesetzt, da der white page effekt dramatisch herabgesetzt wird, was wiederum immer zu Problemen von Flash of Unbehaviored Content und zumindest teilweise zu Flash of Unstyled Content führt.

Wer das blockende Laden lösen möchte, kann sein JS getrost im head dynamisch mit JS nachladen. Hierdurch wird das blockende Verhalten des JS ausgeschaltet, der Browser kann seine Downloads parallelisieren und auch die white page Zeit wird herabgesetzt. Da die JS-Dateien aber wesentlich früher geladen werden, treten die verschiedenen Arten von FOUCs seltener bis gar nicht auf.

Minimize HTTP Requests

Minimize HTTP Requests gehört wohl zu der goldenen Regel für die Frontend-Performance. Im Fall von JS wird empfohlen, alle Script-Dateien in eine einzelne Datei zu kombinieren. Diese Optimierung durch den Entwickler nimmt dem Browser jede Möglichkeit selbst Performance Optimierungen durchzuführen und ist in allen moderneren Browsern (inklusive IE8!) eine wahre Bremse.

Um diese Behauptung zu stützen, hier ein paar einfache Performancetests:

Ein erster Test mit cuzillion

Cuzillion ist ein nettes Tool um schnell mal ein paar Performance Tests durchzführen. Da die Mindestladezeit bei einer Sekunde liegt und keine CSS-Bilder simuliert werden können, sind die Ergebnisse jedoch immer mit Vorsicht zu genießen. Unser Cuzillion-Testaufbau, umfaßt immer 3 Stylesheets, 3 Bilder sowie ein iframe, welche je 1 Sekunde Ladezeit benötigen. Daneben wird JS von 2 Domains in unterschiedlicher Anzahl eingebunden, welche insgesamt 9 Sekunden betragen:

Wie schnell Laden nun die verschiedenen Browser die Seite, wenn wir diese JS-Dateien einerseits auf 2 Dateien und andererseits auf 8 Dateien zusammen fassen:

Bei 2 Dateien:
IE9: 10 | FF4: 10 | IE6: 15 | Chrome 11: 10

Bei 8 Dateien:
IE9: 5 | FF4: 4.6 | IE6: 16 | Chrome 11: 4.8

Wie wir sehen, wird die Seite durch Befolgung der Performanceregel im IE6 (und wohl auch im IE7) ein bißchen schneller, während dem die Seite in allen modernen Browsern deutlich langsamer wird.

Die Gründe für diese krassen Unterschiede sind relativ klar, moderne Browser haben mindestens 2 Performanceoptimierungen durchgeführt, welche durch das Zusammenfaßen von JS-Dateien nicht mehr ihre positiven Wirkungen entfalten können:

  • konventionell eingebundenes JS blockt nicht mehr
  • Die maximale Anzahl gleichzeitiger Requests zur gleichen domain wurden von 2 auf 4-8 angehoben

Ein weiterer nicht uninteressanter Test, ist das Laden von 8 JS Dateien, von denen 7 dynamisch eingebunden wurden:

8 Dateien 7 x DOM create und 1 konventionell:
IE9: 5 | FF4: 4.8 | IE6: 14 | Chrome: 5.5

Es zeigt sich, daß moderne Browser vom dynamischen Einbinden gar keinen Nutzen mehr ziehen können. Wirklich überraschend ist, daß der IE6 mit seinen beiden mageren Downloadslots die hohe Anzahl der Dateien durch Parallelisierung wettmachen kann.

Praxisnaher Performancetest

Als Grundlage für einen praxisnäheren Test diente ein Prototyp mit einer HTML Seite mit 60kb, unterschiedlicher Anazhl von JS-Dateien mit insgesamt 300kb, 4 x Stylesheet-Dateien (125kb), 25 x CSS-Bildern (140kb) und 5 x Inhaltsbildern (160kb). Für die weiteren Tests habe ich auf die alten Browser verzichtet und ausschließlich IE9 sowie FF4 verwendet, da beide mehr oder weniger gleich schnell sind, faße ich beide zusammen.

Auch habe ich mit verschiedenen Verbindungen getestet, da die Ergebnisse jedoch nicht relevant sind und nichts an der generellen Beobachtung ändern, bleiben die langsamen Verbindungsdaten unerwähnt.

Alle JS Dateien wurden grundsätzlich konventionell eingebunden.

Ladezeiten bei unterschiedlicher Anzahl von Dateien (Anzahl Dateien / DOM-Ready-Zeit / onload-Zeit):

  • 1 Dateien / 1100ms / 1600ms
  • 4 Dateien / 850ms / 1350ms
  • 6 Dateien / 800ms / 1300ms
  • 8 Dateien / 800ms / 1300ms
  • 12 Dateien / 800ms / 1300ms

Interessant an dem Ergebnis ist, daß sowohl IE9 als auch FF4 mit 6 Dateien ihr Performanceoptimum erreicht haben, aber andererseits durch Verdoppelung auf 12 Dateien keine Verlangsamung eintritt.

Wie aussagekräftig sind die Tests?

Um hier möglicher Kritik aus dem Weg zu gehen. Diese Ergebnisse beruhen auf ein paar kleinen Tests, welche mit Sicherheit nicht den Grundlagen der Statistik und des wissenschaftlichen Arbeitens folgen. Aber sie zeigen einen deutlichen Trend, der viele zum Umdenken/Neudenken befördern sollte.

Ähnliche Tests mit ähnlichen Ergebnissen wurden im Bereich der performanten Stylesheet-Einbindung bereits vor ein paar Jahren von Dirk Jesse gemacht, welcher eine Aufsplittung in 4 CSS-Dateien empfiehlt. (Ich habe aber wirklich echt nicht abgeschrieben).

Was bedeutet das für die Praxis?

Die Bedeutung von “Minimize http-requests” ist mit Sicherheit in der heutigen Browserlandschaft gesunken, hat aber immer noch Berechtigung. Der Entwickler muß sich hier darüber im Klaren sein, daß diese Regel der Parallelisierung von Downloads entgegensteht und einen angemessenen Ausgleich schaffen.

Dieser Ausgleich muß sich an den jeweiligen Größen der eingebundenen Dateien orientieren.

Das Verringern von http-requests einerseits dient vor allem der Reduzierung der Dauer für das Senden des Request-Headers sowie der Wartezeit bis der eigentliche Download beginnt, währenddem das “Auftrennen” von Dateien andererseits zu einer Reduzierung der eigentlichen Downloadzeiten durch Parallelisierung führt. Kurz: Ist eine Datei kleiner als 5kb sollte sie mit anderen zusammengefaßt werden, ist eine Datei dagegen größer 50kb sollte sie gesplittet werden.

Written March 1, 2011 by
alexander farkas

19 Comments »

  1. Zu einem ähnlichen Fazit wie Du kommt Philip Tellis in folgendem Blogbeitrag: http://calendar.perfplanet.com/2010/thoughts-on-performance/

    Ich denke auch, dass es nicht mehr pauschal korrekt ist, dass man die beste Performance mit den wenigsten Requests erreicht. Man muss einfach die angebotenen parallelen Verbindungen optimal auslasten. Das sind bei IE6/7 2, bei Safari 4, bei IE8/Firefox/Chrome 6 und bei Opera 8 parallele Verbindungen. Dennoch nützt die Eselsbrücke “so wenig HTTP-Requests wie möglich” mehr als dass sie schadet.

    Dass die JavaScript-Dateien, die konventionell im Quelltext eingebunden sind, heutige Browsern nicht mehr blockieren stimmt nur bezogen auf den vorgreifenden HTML-Parser und das Downloadverhalten. Moderne Browser lesen also den Quelltext während des Herunterladens eines Scriptes weiter und sie laden auch auf das Script folgende Resourcen schon einmal herunter. Blockieren also das Vorbereiten der weiteren Seite nicht.

    Was sie meines Wissens nach aber immer noch tun, ist das Rendering der Inhalte so lange zurückzuhalten bis das entsprechende Script zu Ende kompiliert und ausgeführt wurde. Man blockiert also mit JavaScript immer noch das Userinterface und sollte JavaScript deshalb nach wie vor erst spät einbinden, um eine gefühlt schnelle Darstellung zu erreichen, oder eben dynamisch im Kopf.

    Alternativ nutzt man die Attribute async und/oder defer – dann passiert das nicht. Oder man lazyloaded auf andere Art und Weise.

    Vielleicht auch interessant zu dem Thema: http://www.nczonline.net/blog/2011/02/14/separating-javascript-download-and-execution/

    Danke für Deinen Blogartikel und Grüße

    vom Schepp

    Comment by Schepp — March 1, 2011 @ 1:06 pm

  2. Hallo Alex,

    IMHO sind Deine Tests realitätsfern. Ich arbeite oft an Projekten, in denen historisch bedingt je 20-30 JavaScript- und CSS-Dateien eingebunden sind, nicht 4-8.

    Außerdem werden JavaScript-Dateien beim herkömmlichen, nicht-dynamischen Laden auch von modernen Browsern sequentiell geladen, nicht parallel. Darum blockieren sie den Download, darum gehören sie in den Footer.

    Weitere Kriterien, die Du hier nicht nennst, befassen sich mit der Effizienz der JavaScript-Funktionen selbst – natürlich ist eine Blockierung beim Laden durch das JavaScript auch abhängig davon, wie effizient mit Selektoren umgegangen wird, wie Schleifen durchlaufen werden etc.

    Dann gibt es noch das Problem mit CSS-Bildern, von denen oft mehr als 50 von der gleichen Domain eingebunden sind, wenn die Entwickler nicht das Konzept von CSS-Sprites kennen. Auch hier ist es sinnvoll, die Anzahl der HTTP-Requests zu reduzieren.

    Darum können wir die Frage, ob Web Performance-Regeln überdacht werden müssen, meines Erachtens getrost verneinen.

    Cheers,
    Martin

    Comment by Martin Kliehm — March 1, 2011 @ 1:23 pm

  3. @Martin Sie werden in modernen Browsern nur sequentiell ausgeführt, aber sehr wohl parallel geladen. Schau Dir den Network-Graph von Firebug an. Ich würde sie dennoch wie Du in den Fußbereich verlegen.

    Comment by Schepp — March 1, 2011 @ 1:34 pm

  4. @Martin

    boa, schreck mal nicht vor neuen Erkenntnissen zurück. Hast du den Artikel wirklich gelesen?

    Ich sage an keiner Stelle, daß das Laden von 20-30 JS-Dateien gut ist und ebenfalls sage ich an gar keiner Stelle, daß das Laden von 50 CSS-Bildern gut sei. Ich mache nur klar, daß das Laden von einer einzelnen großen JS-Datei, wie es lange gepredigt wurde/wird, schlecht ist. Wir reden hier von zwei unterschiedlichen Extremen, die beide schlecht für die Performance sind. Es geht um einen anständigen Ausgleich.

    Du behauptest, daß moderne Browser konventionell eingebundene JS-Dateien weiterhin sequentiell laden. Das ist nachweislich falsch und kann bereits durch einen Blick in Fiddler und andere Netzwerk-Tools (Firebug, Webinspector?) widerlegt werden.

    Für den IE8, welcher der 1. Browser war, der paralleles Laden unterstützt hat Steve Souders selbst am 17. März 2008 einen entsprechenden Test mit Erklärung gemacht:

    http://stevesouders.com/cuzillion/?ex=3&title=IE8+Parallel+Script+Loading

    Jetzt sind 3 Jahre vergangen, die aktuellen Versionen von FF, Chrome, Safari und Opera unterstützen ebenfalls paralleles laden. Alleine diese Aussage läßt bei mir Zweifel aufkommen, ob deine Kritik sich überhaupt inhaltlich mit meinem Artikel auseinandersetzt.

    Richtig ist, daß Parsen und Ausführung von JS auch seinen Teil mit bei trägt. Das ich dies inhaltlich nicht behandele, macht meine Aussagen und Tests jedoch nicht falsch.

    Bitte lies nochmal den allerletzten Absatz ganz gründlich durch. Dort steht ein deutliches Bekenntnis zum Reduzieren von http-Requests. Nur mit dem feinen Unterschied, daß man Techniken zur Parallelisierung von Downloads mitberücksichtigen muß.

    Comment by alexander farkas — March 1, 2011 @ 2:06 pm

  5. Die YSlow-Regeln entstammen tatsächlich einer Zeit, in der das asynchrone Laden von Scripten noch allgemein nicht bekannt war. Die »Put scripts at the bottom«-Regel ist heutzutage insofern nicht mehr relevant, dass man Scripte sinnvollerweise modularisiert und erst bei Bedarf asynchron lädt. Damit verteilt man die Scripte bewusst auf mehrere kleinere Requests, anstatt blind hunderte Kilobyte JavaScript in einer zusammengefassten Ressource zu laden. Klar, das relativiert auch die Regel, HTTP-Requests zu minimieren. Im Grunde gelten die Regeln also noch, man sollte sie jedoch ergänzen und relativieren.

    Comment by molily — March 1, 2011 @ 2:12 pm

  6. @molily
    Ich sehe wir verstehen uns :-)

    Comment by alexander farkas — March 1, 2011 @ 2:15 pm

  7. Das Laden der Javascripte am Fuße einer Website fand ich schon immer doof. Besonders wenn man CMS-Basiert arbeitet und manche Daten mitten im Content in gewisse Javascript-Funktionen injiziert werden müssen.

    Das minimieren der HTTP-Requests halte ich aber nach wie vor Sinnvoll. Hierbei sollte man nur beachten, dass man es auf seinen eigenen Code anwendet. Eine Bibliothek, wie z.B. jQuery würde man sinnvoller Weise vom GoogleCDN laden, da es erstens zig mal schneller ist als der eigene Server, der Browser es vielleicht sogar schon von einer anderen Website gecached hat und die andere Domain nicht das Limit gleichzeitiger Requests des Browsers beeinträchtigt. Schließlich ist Javascript nicht das einzige was ich von meinem Server in den Browser laden muss. Ich behaupte mal das Bilder hier den Kohl fett machen.

    Comment by micha149 — March 1, 2011 @ 2:47 pm

  8. @micha149
    Alexander geht es meiner Meinung nach NICHT um den Verzicht auf die Minimierung von Requests, das ist grundsätzlich eine gute Idee. Nur verbaut man sich mit der pauschalen Reduktion auf jeweils eine eine einzelne Datei die Vorteile des parallelen Ladens von JS oder auch CSS und optimiert damit eigentlich für eine aussterbende Browsergeneration.

    Comment by Dirk Jesse — March 1, 2011 @ 3:30 pm

  9. Hallo Alexander,

    danke für die Anregung, die ich soweit nachvollziehen kann. Mich würde interessieren, welche Testergebnisse sich auf einem Smartphone ergeben. Sowohl über WLan als auch GRPS.

    Nach meinem subjektiven Empfinden dauern die Requests dort deutlich länger und könnten zu einer anderen Empfehlung führen. Kannst du das nachreichen?

    Danke und liebe Grüße

    Bertram

    Comment by Bertram Simon — March 1, 2011 @ 7:02 pm

  10. @Bertram Das kannst Du selbst mit Hilfe der oben verlinkten Testaufbauten herausfinden, und zwar hier: http://www.blaze.io/mobile/

    Comment by Schepp — March 2, 2011 @ 9:21 am

  11. Hallo,

    danke für den sehr aufschlußreichen Artikel!

    Allerdings sollte man vorher immer die Zielgruppe betrachten, bevor man das Optimieren anfängt. Wenn da in den Server-Log-Files noch 50% User auftauchen, welche mit IE<=7 unterwegs sind, ist wohl die Verminderung von http-Requests und das Zusammenfassen von CSS/JS Dateien noch sehr effektiv.

    Gibt es eigentlich eine Übersicht, welcher Browser in welcher Version wieviele parallele Downloads unterstützt?

    Wäre für eine kurze Info dankbar,

    Viele Grüße, OLE

    Comment by Ole — March 2, 2011 @ 12:15 pm

  12. @Ole Jepp, gibt es hier: http://www.browserscope.org/results?o=xhr&v=top&category=network

    Comment by Schepp — March 2, 2011 @ 12:37 pm

  13. @derSchepp
    super, Danke dafür!

    Comment by Ole — March 2, 2011 @ 1:02 pm

  14. Danke für den Beitrag, der mir wieder zeigt wie kritisch man die einzelnen Tools trotzdem sehen sollte und es mehr bedarf als YSlow o.ä. anzuwerfen.

    Comment by Frank — March 2, 2011 @ 9:13 pm

  15. @Schepp (Dein 1. Kommentar)

    danke, daß du hier erstmal die Moderation übernommen hast. Zu deinem Kommentar: Da ich wenig Zeit habe sehr kurz: Absolut richtig.

    Die Beschreibung, wie JS in modernen Browsern (inkl. IE8) nicht mehr blockt und dennoch blockt ist ebenfalls absolut richtig.

    Zu Put your JS at bottom. Eigentlich sollte der Artikel vornehmlich um die Requestanzahl bei JS-Dateien gehen. Ich stehe mit dieser Regel schlichtweg von Anfang an auf Kriegsfuß. Daher wollte ich das kurz streifen. Zu dieser Regel:

    Hier geht es im wesentlichen um das Downloadverhalten, welches man erziehlen möchte. Es gibt folgende Varianten:

    - Sofort im head dynamisch einbinden -> Vorteil: gut für Behebung zu langer white page Zeit bei geringer FOUC-Wahrscheinlichkeit | Nachteil: Blockiert Downloadslot für Inahltsbilder

    - konventionell am Ende einbinden -> Vorteil: extrem gut für Behebung der white page Zeit | Nachteil: FOUC ist direkte Folge | DOMContentReady-Event wird ebenfalls geblockt, schlecht für alte Browser wenn mehrere Scripte geladen werden

    - verzögert im head dynamisch einbinden -> Vorteil: extrem gut für Behebung der white page Zeit | Nachteil: FOUC ist direkte Folge

    Wie man sieht kann vom head aus alles perfekt gesteuert werden (async bzw. async + defer – Verhalten), sobald man auch nur ein Script am Ende konventionell einbindet, geht diese Steuerung verloren, da dies negative Eigenschaften auf das DOMContentReady-Event hat, da dies geblockt wird. Die Frage lautet also warum/wann sollte ich ein Script unten einbinden. Die Antwort hier ist einfach. Wenn man ausschließlich ein Lightbox-Script verwendet. Sobald man etwas mehr machen möchte, wird das ganze zu einem Klotz am Bein.

    Comment by alexander farkas — March 8, 2011 @ 11:05 pm

  16. I found no other place to post this – so I’m posting here.

    As of jQuery 1.6, your plugin “backgroundPosition” (http://www.protofunc.com/scripts/jquery/backgroundPosition/) is broken. Just a heads up.

    Comment by Kristaps — May 4, 2011 @ 1:06 pm

  17. Hallo Alexander, habe gerade gelesen, dass Kommentar 16 auch keine andere Möglichkeit gefunden hat dich zu kontaktieren. Wollte auch bescheid sagen, dass mit meine jQuery Update auf 1.6 dein Plugin nicht mehr funktioniert.

    Ich bekomme die Meldung ” Uncaught TypeError: Cannot read property ‘backgroundPosition’ of undefined ” in meiner Console.

    Comment by Mathi — May 7, 2011 @ 10:33 am

  18. Same problem for me. :/

    Comment by Flip — May 10, 2011 @ 3:32 pm

  19. About issues with backgroundPosition plugin. There is an official bugtracker for this plugin @ http://plugins.jquery.com/project/backgroundPosition-Effect . Simply log in and create a new issue.

    About jQuery 1.6.0 support.

    I will definitley add jQuery 1.6.1 support, but currently I don’t see need to test and update my plugins. jQuery has changed a lot since 1.6.0 release and 1.6.0 is more a beta than a final. I wouldn’t use in production.

    Comment by alexander farkas — May 10, 2011 @ 8:42 pm

RSS feed for comments on this post | TrackBack URL

Leave a comment