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)
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)
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:
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)
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!

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 : 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) {
/**/
}
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 : 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>";
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();
}
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!
Kommentera