{"id":139,"date":"2010-10-21T19:31:49","date_gmt":"2010-10-21T17:31:49","guid":{"rendered":"https:\/\/d-mueller.de\/blog\/?p=139"},"modified":"2010-11-26T23:20:21","modified_gmt":"2010-11-26T22:20:21","slug":"web-performance-best-practices","status":"publish","type":"post","link":"https:\/\/d-mueller.de\/blog\/web-performance-best-practices\/","title":{"rendered":"Web-Performance: Best Practices"},"content":{"rendered":"<p>Was folgt ist eine Mischung aus der sehr guten (!) Artikelserie <a href=\"http:\/\/code.google.com\/intl\/de-DE\/speed\/articles\/\">&#8222;Make the web faster&#8220;<\/a> von google und einigen pers\u00f6nlichen Erg\u00e4nzungen und Erfahrungen. Erhebt keinerlei Anspruch auf Vollst\u00e4ndigkeit und mischt server- sowie clientseitige Tipps querbeet. Ich gehe hier nicht besonders ins Detail, nenne aber genug Stichw\u00f6rter, zu denen man sich dann schlaugooglen kann. Als Abschluss der Einleitung m\u00f6chte ich noch auf den <a href=\"http:\/\/stevesouders.com\/\">Gott<\/a> der Webperformance verweisen, ohne dessen B\u00fccher und Analysen wir sicher noch nicht da w\u00e4ren wo wir heute sind.<\/p>\n<ol>\n<li>Javascript so weit wie es geht asynchron laden bzw. ans Seitenende bef\u00f6rdern, da Browser JS-Dateien in der Regel nicht parallel laden. Langsam geht es allerdings in die richtige Richtung mit der neuen Browsergeneration (siehe <a href=\"http:\/\/davidwalsh.name\/html5-async\">hier<\/a> f\u00fcr mehr Details, Stichw\u00f6rter <i>async<\/i> und <i>defer<\/i>). Es folgt ein von ncz entliehenes Script, um Javascript asynchron nachzuladen &#8211; Details siehe <a href=\"http:\/\/www.nczonline.net\/blog\/2009\/07\/28\/the-best-way-to-load-external-javascript\/\">hier<\/a>.\n<pre data-enlighter-language=\"js\" class=\"EnlighterJSRAW\">\r\nfunction loadScript(url, callback){\r\n\r\n    var script = document.createElement(&quot;script&quot;)\r\n    script.type = &quot;text\/javascript&quot;;\r\n\r\n    if (script.readyState){  \/\/IE\r\n        script.onreadystatechange = function(){\r\n            if (script.readyState == &quot;loaded&quot; ||\r\n                    script.readyState == &quot;complete&quot;){\r\n                script.onreadystatechange = null;\r\n                callback();\r\n            }\r\n        };\r\n    } else {  \/\/Others\r\n        script.onload = function(){\r\n            callback();\r\n        };\r\n    }\r\n\r\n    script.src = url;\r\n    document.getElementsByTagName(&quot;head&quot;)[0].appendChild(script);\r\n}\r\n<\/pre>\n<\/li>\n<li>Da die Anzahl der maximal m\u00f6glichen Requests zu einem Server beschr\u00e4nkt sind, lohnt sich oft das verteilen von statischem Content auf Subdomains (=anderer Hostname, also mehr Downloads parallel). Allerdings sollte mans nicht \u00fcbertreiben, da beim Verteilen auf zuviele verschiedene Server die DNS-Lookup-Zeit mehr ins Gewicht f\u00e4llt als mehr parallele Downloads.<\/li>\n<li>In der Rangfolge der externen Dateien erst CSS und dann Javascript, da auf die Art und Weise mit den ersten Javascript-Files bereits begonnen werden kann, w\u00e4hrend CSS noch downloaded. Wer nach <a href=\"http:\/\/www.google.de\/images?hl=de&#038;q=waterfall+chart+javascript&#038;um=1&#038;ie=UTF-8&#038;source=og&#038;sa=N&#038;tab=wi\">Waterfall Charts<\/a> googlet, wird sehen was ich meine.<\/li>\n<li>Die Dimensionen von Bildern direkt in den img-Tag einbauen, da der Browser auf die Art und Weise beim Aufbau der Seitenstruktur direkt schon den Platz &#8222;einplanen&#8220; kann, bevor das Bild wirklich geladen wurde<\/li>\n<li>Cache-Header vern\u00fcnftig setzen (.htaccess). K\u00f6nnte etwa so aussehen &#8211; ist aber nat\u00fcrlich an die individuellen Bed\u00fcrfnisse anzupassen:\n<pre data-enlighter-language=\"enlighter\" class=\"EnlighterJSRAW\">&lt;IfModule mod_expires.c&gt;\r\n    ExpiresActive On\r\n    ExpiresDefault &quot;access plus 3 days&quot;\r\n\tExpiresByType image\/jpg &quot;access plus 2 years&quot;\r\n\tExpiresByType image\/jpeg &quot;access plus 2 years&quot;\r\n\tExpiresByType image\/gif &quot;access plus 2 years&quot;\r\n\tExpiresByType application\/javascript &quot;access plus 2 years&quot;\r\n\tExpiresByType text\/css &quot;modification plus 1 day&quot;\r\n&lt;\/IfModule&gt;<\/pre>\n<p>Dabei ist wenn immer m\u00f6glich der expires-header zu verwenden, da auf die Art und Weise ein http-request gespart wird und auch wenn die Antwort <i>304<\/i> ist, kostet das weitere Zeit.\n<\/li>\n<li>HTTP-Requests vermeiden. Imagesprites, Javascript und CSS in der Entwicklung getrennt, im Live-Betrieb minified und zu einer Datei zusammengepackt. Wenn auch schon 4 Jahre alt, <a href=\"http:\/\/rakaz.nl\/2006\/12\/make-your-pages-load-faster-by-combining-and-compressing-javascript-and-css-files.html\">hier<\/a> immernoch mein Lieblingsscript dazu. Die effektive URL f\u00fcr seine Scripts lautet dann etwa\n<pre data-enlighter-language=\"html\" class=\"EnlighterJSRAW\">\r\nhttp:\/\/www.creatype.nl\/javascript\/prototype.js,builder.js,effects.js,dragdrop.js,slider.js\r\n<\/pre>\n<p>Diese werden zusammengecruncht und als eine Datei physisch auf dem Server abgelegt.\n<\/li>\n<li>CSS Selektoren nicht unn\u00f6tig spezifizieren. Statt\n<pre data-enlighter-language=\"css\" class=\"EnlighterJSRAW\">body #wrapper #left div#menu<\/pre>\n<p> w\u00fcrde es auch <\/p>\n<pre data-enlighter-language=\"css\" class=\"EnlighterJSRAW\">#menu<\/pre>\n<p> tun, was dem Browser viel Arbeit beim CSS-parsen erspart. CSS Selektoren werden von rechts nach links abgearbeitet. Ich ertappe mich allerdings selbst oft dabei, einen Selektor zu \u00fcberspezifizieren, weil ich dadurch besser erkenne, in welchem Kontext er steht. Da dann lieber Kommentare verwenden. Au\u00dferdem sinkt der &#8222;reuse&#8220;-Faktor von \u00fcberspezifizierten Selektoren, was ja eigentlich den Kern von CSS ausmachen sollte. Siehe dazu <a href=\"http:\/\/www.stubbornella.org\/content\/2009\/03\/23\/object-oriented-css-video-on-ydn\/\">Object Oriented CSS<\/a><\/li>\n<li>Javascript-reflows vermeiden. Ein Reflow wird durch so ziemlich alles ausgel\u00f6st, was im DOM manipuliert wird. So ist es bspw. empfehlenswert, erst das HTML komplett als String zusammenzubauen, bevor man es ins DOM einf\u00fcgt da auf die Art und Weise die Anzahl der Reflows minimiert wird. Klassisches Beispiel:\n<pre data-enlighter-language=\"js\" class=\"EnlighterJSRAW\">\r\nvar categories = [&quot;Home&quot;,&quot;About me&quot;,&quot;Stuff&quot;,&quot;Other Stuff&quot;];\r\n\r\n$.each(categories,function(elem)\r\n{\r\n    $(&quot;ul&quot;).append(&quot;&lt;li&gt;&quot; + elem + &quot;&lt;\/li&gt;&quot;);\r\n});\r\n<\/pre>\n<p> ist vergleichen mit <\/p>\n<pre data-enlighter-language=\"js\" class=\"EnlighterJSRAW\">var categories = [&quot;Home&quot;,&quot;About me&quot;,&quot;Stuff&quot;,&quot;Other Stuff&quot;],\r\n    htmlstr = &quot;&quot;;\r\n    \r\n$.each(categories,function(elem)\r\n{\r\n    htmlstr += &quot;&lt;li&gt;&quot; + elem + &quot;&lt;\/li&gt;&quot;\r\n});\r\n\r\n$(&quot;ul&quot;).html(htmlstr);<\/pre>\n<p> um eine ganze Ecke unperformanter.\n<\/li>\n<li>Cookies am besten komplett vermeiden, da diese bei jeder neuen Seite erneut im Header \u00fcbertragen werden. Wenn irgendwie machbar, dann per Session-ID. Wenn unbedingt n\u00f6tig, dann zumindest nur eine ID und einen hash im Cookie speichern und den dann serverseitig validieren und die Daten so auslesen. Ist ohnehin wesentlich sicherer. Auf jeden Fall liegt die maximal vertretbare Gr\u00f6\u00dfe bei 1500 byte, was der durchschnittlichen MTU entspricht (Bytes pro \u00fcbertragenem Paket). Die Cookies immer so spezifisch wie m\u00f6glich setzen. Sollte der Cookie also nur auf einer Subdomain oder einer einzelnen Seite gebraucht werden, ist er auch nur f\u00fcr diese zu setzen.<\/li>\n<li>Komprimieren! Lieder deflate statt gzip, einfach ausprobieren.\n<pre data-enlighter-language=\"enlighter\" class=\"EnlighterJSRAW\">&lt;IfModule mod_deflate.c&gt;\r\nAddOutputFilterByType DEFLATE text\/html text\/plain text\/xml text\/css application\/x-javascript text\/javascript application\/javascript application\/json \r\n&lt;\/IfModule&gt;<\/pre>\n<\/li>\n<li>SQL-Statements in Loops vermeiden. Gerade bei Insert gern genommen:\n<pre data-enlighter-language=\"php\" class=\"EnlighterJSRAW\">\r\nforeach ($cars as $c) \r\n{\r\n  $query = &#039;INSERT INTO cars (cartype,horsepower) VALUES(&quot;&#039;.$c[&#039;type&#039;].&#039;&quot;,&quot;&#039;.$c[&#039;hp&#039;].&#039;&quot;)&#039;;\r\n  mysql_query($query);\r\n}\r\n<\/pre>\n<p>Wesentlich effektiver hingegen:<\/p>\n<pre data-enlighter-language=\"php\" class=\"EnlighterJSRAW\">\r\n$sql = array();\r\nforeach ($cars as $c) {\r\n    $sql[] = &#039;(&quot;&#039;.$c[&#039;type&#039;].&#039;&quot;,&quot;&#039;.$c[&#039;hp&#039;].&#039;&quot;)&#039;;\r\n}\r\n$query = &#039;INSERT INTO cars (cartype,horsepower) VALUES &#039;.implode(&#039;,&#039;, $sql);\r\nmysql_query($query);\r\n<\/pre>\n<p>Auch <a href=\"http:\/\/dev.mysql.com\/doc\/refman\/5.1\/en\/insert-delayed.html\">Insert delayed<\/a> bietet sich oft an.\n<\/li>\n<li><a href=\"http:\/\/code.google.com\/intl\/de-DE\/speed\/page-speed\/\">Google Page Speed<\/a> und <a href=\"http:\/\/developer.yahoo.com\/yslow\/help\/\">YSlow!<\/a> benutzen um die wirklichen bottlenecks zu finden (<i>premature optimization is the root of all evil<\/i> wie wir alle wissen).<\/li>\n<li>Zum rumspielen mit den HTTP-Headern empfiehlt sich folgendes Script:\n<pre data-enlighter-language=\"php\" class=\"EnlighterJSRAW\">&lt;?php\r\n$lastModified = filemtime(__FILE__);\r\n$etagFile = md5_file(__FILE__);\r\n$ifModifiedSince=(isset($_SERVER[&#039;HTTP_IF_MODIFIED_SINCE&#039;]) ? $_SERVER[&#039;HTTP_IF_MODIFIED_SINCE&#039;] : false);\r\n$etagHeader=trim($_SERVER[&#039;HTTP_IF_NONE_MATCH&#039;]);\r\n\r\nheader(&quot;Last-Modified: &quot;.gmdate(&quot;D, d M Y H:i:s&quot;, $lastModified).&quot; GMT&quot;);\r\nheader(&quot;Etag: $etagFile&quot;);\r\nheader(&#039;Cache-Control: public&#039;);\r\n\r\nif (@strtotime($_SERVER[&#039;HTTP_IF_MODIFIED_SINCE&#039;]) == $lastModified ||\r\n    $etagHeader == $etagFile) \r\n\t{\r\n\t\theader(&quot;HTTP\/1.1 304 Not Modified&quot;);\r\n\t}\r\n\r\necho &quot;Last modified: &quot;.$lastModified.&quot; (&quot;.gmdate(&quot;D, d M Y H:i:s&quot;,$lastModified. &quot;GMT&quot;).&quot;)&lt;br \/&gt;&quot;;\r\necho &quot;if-modified-since: &quot;.strtotime($ifModifiedSince).&quot; (&quot;.$ifModifiedSince.&quot;)&lt;br \/&gt;&quot;;\r\necho &quot;etag-File: &quot;.$etagFile.&quot;&lt;br \/&gt;&quot;;\r\necho &quot;etag-Header: &quot;.$etagHeader.&quot;&lt;br \/&gt;&quot;;\r\n<\/pre>\n<\/li>\n<\/ol>\n","protected":false},"excerpt":{"rendered":"<p>Was folgt ist eine Mischung aus der sehr guten (!) Artikelserie &#8222;Make the web faster&#8220; von google und einigen pers\u00f6nlichen Erg\u00e4nzungen und Erfahrungen. Erhebt keinerlei Anspruch auf Vollst\u00e4ndigkeit und mischt server- sowie clientseitige Tipps querbeet. Ich gehe hier nicht besonders &hellip; <a href=\"https:\/\/d-mueller.de\/blog\/web-performance-best-practices\/\">Weiterlesen <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4,12,9,10,6,8,3],"tags":[],"class_list":["post-139","post","type-post","status-publish","format-standard","hentry","category-php","category-javascript","category-datenbanken","category-performance","category-security","category-quicktips","category-webdev"],"_links":{"self":[{"href":"https:\/\/d-mueller.de\/blog\/wp-json\/wp\/v2\/posts\/139","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/d-mueller.de\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/d-mueller.de\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/d-mueller.de\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/d-mueller.de\/blog\/wp-json\/wp\/v2\/comments?post=139"}],"version-history":[{"count":0,"href":"https:\/\/d-mueller.de\/blog\/wp-json\/wp\/v2\/posts\/139\/revisions"}],"wp:attachment":[{"href":"https:\/\/d-mueller.de\/blog\/wp-json\/wp\/v2\/media?parent=139"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/d-mueller.de\/blog\/wp-json\/wp\/v2\/categories?post=139"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/d-mueller.de\/blog\/wp-json\/wp\/v2\/tags?post=139"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}