Meddelande

Minska
No announcement yet.

Ökad förståelse för prestanda (PHP)

Minska
X
  • Filter
  • Klockan
  • Show
Clear All
new posts

  • SPiN
    started a topic Ökad förståelse för prestanda (PHP)

    Ökad förståelse för prestanda (PHP)

    Då fortsätter jag mitt tråd-maraton med lite tips kring profilering och cachning! Hoppas det är till nytta för någon.

    Ofta ser man frågor om vad som är minst prestandakrävande, frågor om hur man ska gå tillväga för att ens webbplats ska vara så responsiv som möjligt. Ibland går det alldeles för långt då man ser personer som försöker optimera minsta lilla detalj. Självklart är prestanda en väldigt viktig del i en webbplats, som besökare väntar man inte längre än några sekunder innan man stänger ner tabben/fönstret om ingenting händer. Det får dock inte gå till överdrift. Därför finns det en hel del verktyg ute på marknaden för att profilera sin kod - man försöker hitta var någonstans flaskhalsar sitter, flaskhalsar som gör att sidan känns seg. Det finns många olika verktyg för det här, några kostar pengar och några är gratis och har öppen källkod. Jag kommer att visa ett litet exempel nedanför som använder sig av Benchmark, ett paket till PEAR. Det är antagligen inte det bästa eller mest effektiva sättet att profilera sin kod, men den gör ett hyfsat jobb och är gratis att använda.

    Vi börjar med ett PHP-dokument som hanterar information från en databas: (Info.php)
    PHP-kod:
    <?php
    function __compare($first$second) { // Funktion för att sortera efter pris
        
    if($first['price'] == $second['price'])
            return 
    0;

        return (
    $first['price'] < $second['price']) ? -1;
    }

    try {
        
    $pdh = new PDO("mysql:host=VÄRD;dbname=DATABAS""ANVÄNDARE""LÖSENORD");
        
    $data $pdh->query("SELECT c.categoryname, p.productname, p.price
                    FROM Categories c 
                    INNER JOIN Products p ON p.category=c.id"
    )->fetchAll(PDO::FETCH_ASSOC);

        
    usort($data"__compare");
        
    ?>
        <html>
        <body>
        <h1>Kategorier & produkter</h1>

        <table>
        <?php
        
    foreach($data as $row) {
            echo 
    "<tr>";
            echo 
    "<td>Kategori: " $row['categoryname'] . "</td>";
            echo 
    "<td>Produkt: " $row['productname'] . "</td>";
            echo 
    "<td>Pris: " $row['price'] . "</td>";
            echo 
    "</tr>";
        }
        
    ?>
        </table>

        </body>
        </html>
        <?php
    } catch(Exception $e) {
        
    /**/
    }
    Man kan ju faktiskt gissa sig till vad som är flaskhalsen i den här koden, men vi väljer ändå att nyttja Benchmark för att profilera den. Nackdelen med Benchmark är att man måste in i koden för sina dokument och ändra - den är inte särskilt dynamisk, tyvärr. Vi börjar med att se hur lång tid hela sidan tar att ladda för PHP: (Info-Benchmarked.php)
    PHP-kod:
    <?php
    require_once("Benchmark/Timer.php");
    $timer = new Benchmark_Timer();
    $timer->start();

    function 
    __compare($first$second) { // Funktion för att sortera efter pris
        
    if($first['price'] == $second['price'])
            return 
    0;

        return (
    $first['price'] < $second['price']) ? -1;
    }

    $timer->setMarker("Märke 1");

    try {
        
    $pdh = new PDO("mysql:host=VÄRD;dbname=DATABAS""ANVÄNDARE""LÖSENORD");
        
    $data $pdh->query("SELECT c.categoryname, p.productname, p.price
                    FROM Categories c 
                    INNER JOIN Products p ON p.category=c.id"
    )->fetchAll(PDO::FETCH_ASSOC);

        
    $timer->setMarker("Märke 2");

        
    usort($data"__compare");
        
    ?>
        <html>
        <body>
        <h1>Kategorier & produkter</h1>

        <table>
        <?php
        $timer
    ->setMarker("Märke 3");
        foreach(
    $data as $row) {
            echo 
    "<tr>";
            echo 
    "<td>Kategori: " $row['categoryname'] . "</td>";
            echo 
    "<td>Produkt: " $row['productname'] . "</td>";
            echo 
    "<td>Pris: " $row['price'] . "</td>";
            echo 
    "</tr>";
        }
        
    ?>
        </table>

        </body>
        </html>
        <?php
        $timer
    ->setMarker("Märke 4");
    } catch(
    Exception $e) {
        
    /**/
    }

    $timer->stop();

    echo 
    "<pre>" $timer->display() . "</pre>";
    Klassen Benchmark_Timer har en metod setMarker() som låter en sätta ut stolpar i koden, där man märker ut var avsnitt börjar och slutar - mycket användbart! När vi kör sidan bör vi få ut en tabell i slutet av dokumentet (från funktionen display()), i stil med:
    Kod:
    Start		1228769468.26427300	-		0.00%
    Märke 1		1228769468.26431300	0.000040	4.54%
    Märke 2		1228769468.26498300	0.000670	75.96%
    Märke 3		1228769468.26508800	0.000105	11.90%
    Märke 4		1228769468.26513800	0.000050	5.67%
    Stop		1228769468.26515500	0.000017	1.93%
    total				-	0.000882	100.00%

    Då ser vi att det är databaskopplingen som tar upp mest tid, och sorteringen av informationen tar lite tid också. Nu är ju just det här exemplet inte speciellt långsamt (jag har bara 6 poster i tabellerna) - men principen är densamma. Nu har man möjlighet att förbättra prestandan utifrån de avsnitt som tar mest tid att köra - kanske sortera posterna i SQL-frågan direkt, som ett exempel.

    Däremot är det ju svårare att förbättra SQL-frågan i sig, och få den att köra snabbare. Man skulle kunna börja pilla med stored procedures möjligtvis, men i det här avsnittet tänkte jag istället komma in på PHP's caching-möjligheter. Istället för att köra SQL-frågan och bygga upp sidan varje gång en förfrågan görs, kan man spara ner informationen på disk och leverera till klienten statiskt. Statisk data kommer alltid att vara snabbare än vad dynamisk är.

    Det finns några olika caching-bibliotek på marknaden, t.ex. Cache_Lite som finns i PEAR. Jag tänkte inte använda något bibliotek utan istället visa grundtanken bakom - med hjälp av de utmärkta utdata buffert-funktionerna som finns (output buffering). Konceptet bygger på att man vid första gången sidan besöks, skapar en cache-fil utifrån den information som ska visas för klienten. Sen hämtar man cache-filen under en förutbestämd tid istället för att köra hela PHP-dokumentet.

    Vi fortsätter på ursprungskoden, och lägger nu till vår cache-funktionalitet: (Info-Cached.php)
    PHP-kod:
    <?php
    $CACHE_FILE 
    "/tmp/page.cache";    // Filnamn för för cache-filen
    $CACHE_TIME 600;            // Hur länge ska sidan vara cachad (i sekunder)?

    if(file_exists($CACHE_FILE) && time() < (filemtime($CACHE_FILE) + $CACHE_TIME)) {
        
    $content file_get_contents($CACHE_FILE);
        echo 
    $content;
    } else {
        
    ob_start();

        try {
            
    $pdh = new PDO("mysql:host=VÄRD;dbname=DATABAS""ANVÄNDARE""LÖSENORD");
            
    $data $pdh->query("SELECT c.categoryname, p.productname, p.price
                        FROM Categories c 
                        INNER JOIN Products p ON p.category=c.id
                        ORDER BY p.price ASC"
    )->fetchAll(PDO::FETCH_ASSOC);
            
    ?>
            <html>
            <body>
            <h1>Kategorier & produkter</h1>

            <table>
            <?php
            
    foreach($data as $row) {
                echo 
    "<tr>";
                echo 
    "<td>Kategori: " $row['categoryname'] . "</td>";
                echo 
    "<td>Produkt: " $row['productname'] . "</td>";
                echo 
    "<td>Pris: " $row['price'] . "</td>";
                echo 
    "</tr>";
            }
            
    ?>
            </table>

            </body>
            </html>
            <?php
        
    } catch(Exception $e) {
            
    /**/
        
    }

        
    $content ob_get_contents();
        
    file_put_contents($CACHE_FILE$content);
        
    ob_end_flush();
    }
    Mitt exempel cachas i 10 minuter (60 sekunder per minut * 10 minuter = 600 sekunder) och jag valde att spara cache-filen i /tmp-katalogen. ob_*()-funktionerna sköter utdatan till klienten - och skickar ingenting till klienten förrän den får konkreta order om att göra det (ob_flush() och ob_end_flush()).

    Viktigt att tänka på: Se till att du har en skrivrättigheter på cache-filen. Placera helst cache-filen utanför webrooten. Var noga med vad för information du cachar - kanske du ska låta bli att cacha känslig information om användare? Överväg noga hur länge cachen ska gälla. Cacha inte användars instruktioner (user input). Kanske har du en sida med väldigt mycket aktivitet, då bör du inte cacha hela sidan. Man kan cacha vissa delar av en sida, och låta andra vara helt dynamiska. Olika delar kan ha olika giltighetstid för cachen. Om du serialiserar objekt kan du cacha enbart dom, om det finns prestandavinst på att göra så.

    Det blev ett ganska långt inlägg det här, men det finns hur mycket som helst att skriva vidare på egentligen.

    Cacha lugnt!

  • Travoni
    replied
    Mycket trevlig läsning.
    Tack!

    Leave a comment:


  • MickeA.com
    replied
    Nu har jag kört enligt ovanstående princip på ett XML flöde vi hostar. Det uppdateras var 5 minut på vår sida och varje gång någon gör ett anrop mot oss skapas en cachefil som lever i 5 minuter.

    Ser ut att funka bra hittills.

    Leave a comment:


  • SPiN
    replied
    dAEk skrev:
    Jag har två frågor:
    1. Finns det några före-vs-efter-nuffror att jämföra med?
    2. Hur bra skalar det att läsa från disk egentligen?
    I det här exemplet är jag lite halvt skeptisk till om en sp skulle göra någon märkbar skillnad eftersom databaserna är ganska bra på att cacha sina frågor, men vad vet jag?

    Jag knappar ingen PHP själv men tycker trots det att det här var ett trevligt initiativ.
    1. Med mitt exempel tjänar man ju inte så mycket på cache - i och med att jag har så lite data att hämta och manipulera. Men med cache påslaget får jag en totaltid på 0.000677 sekunder - vilket fortfarande var bättre än utan cache.
    2. Det skalar bra. Självklart finns det andra vägar att gå också, som t.ex. PHP's memcache-funktioner.

    En SP hade inte hjälp mycket i det här fallet nej, men det är principen jag är ute efter att förklara - man ska inte fokusera för mycket på min andra kod.
    Last edited by SPiN; 2009-04-06, 08:19.

    Leave a comment:


  • jme
    replied
    Ett annat alternativ för att profilera sin PHP-kod är xdebug. Med den behöver man inte göra tillägg i koden. Dock måste xdebug installeras och det är lite krångligare än det som SPiN använde. Resultatet kan sedan granskas med webgrind i webbläsaren (http://jokke.dk/media/2008-webgrind/webgrind_large.png).

    Cachning av objeckt på backend-sidan är viktigt men man ska inte glömma att man också kan spara en del tid på frontend-sidan genom att se till att css, javascript och bilder cachas samt att css- och js-filerna laddas i rätt ordning osv.

    En bok so mbehandlar just detta ämne är http://www.amazon.co.uk/High-Perform...8808248&sr=1-1.

    Även om det senast nämda inte har något med PP att göra så är det helt klart värt att läsa mer om, ifall man vill optimera sin kod.

    Leave a comment:


  • dAEk
    replied
    Jag har två frågor:
    1. Finns det några före-vs-efter-nuffror att jämföra med?
    2. Hur bra skalar det att läsa från disk egentligen?
    I det här exemplet är jag lite halvt skeptisk till om en sp skulle göra någon märkbar skillnad eftersom databaserna är ganska bra på att cacha sina frågor, men vad vet jag?

    Jag knappar ingen PHP själv men tycker trots det att det här var ett trevligt initiativ.

    Leave a comment:

Working...
X