Betere performance voor pChart

Door ACM op dinsdag 20 oktober 2009 19:10 - Reacties (22)
Categorie: Pricewatch, Views: 4.278

Met het Pricewatch 3.0-project hebben we ook nieuwe grafiekjes geintroduceerd. Op onze schaal, zowel kwa data als aantallen grafieken, is de performance van zelfs dat soort details redelijk belangrijk.

Mijn eerste implementatie van een grafiekje met pChart-omgeving bleek 3 seconde nodig te hebben op onze testserver om een csv-bestand van 300 regels (met ieder een label en 3 datapunten) te vertalen naar een grafiekje. Nadere inspectie met xdebug gaf aan dat een groot deel van die tijd zat in de opslag van data in het pData-object.
Deze heeft een vrij inefficiente interne datastructuur, maar heeft ook nog eens exponentiele tijd nodig om datapunten toe te voegen. Effectief werd er voor elk nieuw datapunt (n+1) n maal geteld hoeveel elementen er al in de array zaten. Om 3x 300 datapunten toe te voegen ben je dan dus aardig wat count-operaties verder :X Als ik het goed uitrekenen zit dat op 3* 45150 count-operaties. Domweg het buiten de loop verplaatsen van de count-operatie leverde al een volle seconde tijdswinst op.
Het vervangen van de behoorlijk inefficiente loops door een simpele toevoeging van een array per datarij leverde nog wat winst op.

Verder stikt de code van loze dubbele if's, zoals dit soort dingen:

PHP:
1
2
3
4
5
6
7
8
if ( $DataDescription["Format"]["Y"] == "time" )
         $Value = $this->ToTime($Value);
if ( $DataDescription["Format"]["Y"] == "date" )
         $Value = $this->ToDate($Value);
if ( $DataDescription["Format"]["Y"] == "metric" )
         $Value = $this->ToMetric($Value);
if ( $DataDescription["Format"]["Y"] == "currency" )
         $Value = $this->ToCurrency($Value);


Als je weet dat die waarde "time" heeft, hoef je de andere if's niet meer uit te voeren... In dit geval had de code zelfs nog vervangen kunnen worden door een switch-case structuur, maar ik vond de toevoeging van else's voldoende.

Ook stikte de code van de duplicate floor-calls, waar in deze context een cast naar int sowieso al voldoende was. Voor elke alfatransparante die getekend moest worden, heb ik deze niet zo efficiente constructie vervangen:

PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
$Xi   = floor($X);
     $Yi   = floor($Y);
 
      if ( $Xi == $X && $Yi == $Y)
       {
// ...
       }
      else
       {
       $Alpha1 = (((1 - ($X - floor($X))) * (1 - ($Y - floor($Y))) * 100) / 100) * $Alpha;
        if ( $Alpha1 > $this->AntialiasQuality ) { $this->drawAlphaPixel($Xi,$Yi,$Alpha1,$R,$G,$B); }
 
       $Alpha2 = ((($X - floor($X)) * (1 - ($Y - floor($Y))) * 100) / 100) * $Alpha;
        if ( $Alpha2 > $this->AntialiasQuality ) { $this->drawAlphaPixel($Xi+1,$Yi,$Alpha2,$R,$G,$B); }
 
       $Alpha3 = (((1 - ($X - floor($X))) * ($Y - floor($Y)) * 100) / 100) * $Alpha;
        if ( $Alpha3 > $this->AntialiasQuality ) { $this->drawAlphaPixel($Xi,$Yi+1,$Alpha3,$R,$G,$B); }
 
       $Alpha4 = ((($X - floor($X)) * ($Y - floor($Y)) * 100) / 100) * $Alpha;
        if ( $Alpha4 > $this->AntialiasQuality ) { $this->drawAlphaPixel($Xi+1,$Yi+1,$Alpha4,$R,$G,$B); }
       }
     }

// Vervangen door:
     $Xi   = (int)$X;
     $Yi   = (int)$Y;
 
      if ( $Xi == $X && $Yi == $Y)
       {
// ..
       }
      else
       {
        $xdiff = ($X - $Xi);
        $ydiff = ($Y - $Yi);
       $Alpha1 = (((1 - $xdiff) * (1 - $ydiff) * 100) / 100) * $Alpha;
        if ( $Alpha1 > $this->AntialiasQuality ) { $this->drawAlphaPixel($Xi,$Yi,$Alpha1,$R,$G,$B); }
 
       $Alpha2 = (($xdiff * (1 - $ydiff) * 100) / 100) * $Alpha;
        if ( $Alpha2 > $this->AntialiasQuality ) { $this->drawAlphaPixel($Xi+1,$Yi,$Alpha2,$R,$G,$B); }
 
       $Alpha3 = (((1 - $xdiff) * $ydiff * 100) / 100) * $Alpha;
        if ( $Alpha3 > $this->AntialiasQuality ) { $this->drawAlphaPixel($Xi,$Yi+1,$Alpha3,$R,$G,$B); }
 
       $Alpha4 = (($xdiff * $ydiff * 100) / 100) * $Alpha;
        if ( $Alpha4 > $this->AntialiasQuality ) { $this->drawAlphaPixel($Xi+1,$Yi+1,$Alpha4,$R,$G,$B); }
       }
     }



Het eindresultaat van de wijzigingen vindt je in deze patch, let er daarbij op dat ik de .class-files hernoemd heb naar .class.php. Verder zou ie op pChart 1.27d moeten werken. Aangezien we .class als extentie al voor java's class files hebben gereserveerd maakte dat het editten van die files in eclipse met pdt zo lastig :)

Het eindresultaat was dat de grafiek op de vrij langzame testserver minder dan een seconde nodig had... Het kan vast nog steeds wel wat efficienter, maar de ergste knelpunten zijn er voor ons nu in ieder geval uit.