Wtyczki optymalizujące WordPressa

Naresz­cie udało mi się zostać up-to-date z wersjami Word­Pressa. Zazwy­czaj było tak, że zaraz po upgra­dzie na najnow­szą wersję wycho­dziła nowa. Z leni­stwa ocią­ga­łem się z wymianą. Wiązało się to ze zrobie­niem zrzutu bazy danych (ja raczej ekspor­to­wa­łem wszyst­kie wpisy i komen­ta­rze do pliku) i wszyst­kiego z serwera. Aktu­al­nie mam 3.0.1 i nadzieję, że za kilka dni nie wpro­wa­dzą nowej.

Ostat­nio ekspe­ry­men­to­wa­łem trochę z cache­owa­niem Word­Pressa za pomocą WP-Cache. Wtyczka gene­ruje pliki statyczne dla każdego posta. Zyskiem jest to, że za każdym razem inter­pre­ter PHP nie musi się mordo­wać z prze­twa­rza­niem kodu PHP przed wyświe­tle­niem. Baza danych również dostaje wolne.

Następ­nie powstała bardziej rozbu­do­wana wersja wtyczki — WP Super Cache. Ma te same zalety co podsta­wowa wersja. Oprócz tego mamy w niej możli­wość usta­wie­nia sobie np. prelo­adu cache, czasu odświe­ża­nia plików statycz­nych, inge­ren­cję w sekcję head czy kompre­sję gzipem.

Wszystko fajnie, ale boczne widgety gene­rują również sporo zapy­tań do bazy. Dla nich powstała specjalna wtyczka WP Widget Cache. Twórcy chwalą się, że zysku­jemy 70% więcej wydaj­no­ści. Obec­nie dora­dza się stoso­wa­nie tandemu WP Super Cache i WP Widget Cache. Osobi­ście jednak dałem spokój z obiema wtycz­kami i testuję coś zupeł­nie innego.

To co aktu­al­nie stosuję to W3 Total Cache. Jest to komplek­sowe rozwią­za­nie jeśli chodzi o opty­ma­li­za­cję, dlatego twórcy przed akty­wa­cją radzą wyrzu­cić wszyst­kie wtyczki opty­ma­li­zu­jące jakie mamy zain­sta­lo­wane (nie tylko deak­ty­wo­wać ale całkiem usunąć z dysku!) Po insta­la­cji poja­wia nam się nowa duża zakładka Perfor­mance i spore możli­wo­ści majstro­wa­nia przy takich rzeczach jak opty­ma­li­za­cja stron, zmniej­sza­nie obję­to­ści (HTML, JS, CSS), cache­owa­nie bazy danych, obiek­tów, nagłów­ków prze­glą­darki i obsługę Content Deli­very Network.

Na hory­zon­cie jest jesz­cze jedna wtyczka, której póki co nie rusza­łem — Hyper Cache. Po znudze­niu się W3TC zajmę się tą.


PHP i kolejki priorytetowe

Siedzę sobie w domu na urlo­pie i usku­tecz­niam to, co lubię najbar­dziej czyli progra­muję. Niektó­rzy lubią wyle­gi­wać się na plaży czeka­jąc aż czer­niak łaska­wie zawita na ich skórę, inni chodzą po zagra­nicz­nych targo­wi­skach i pilnują port­fela, a ja lubię siedzieć sobie wygod­nie przed kompu­te­rem cały dzień i dłubać.

Dzisiaj przed­mio­tem mojego dłuba­nia jest imple­men­ta­cja kole­jek prio­ry­te­to­wychPHP, czyli SplHeap. Cała zabawa polega na tym że doda­jąc nowe elementy do kolejki zawsze mamy je posor­to­wane. Po prostu wrzu­camy i tyle — samo się robi :-) Mało tego, podczas wycią­ga­nia jakie­goś elementu możemy sobie doda­wać nowe i one też zajmują swoje miej­sca w hierar­chii ważności.

Posta­wi­łem sobie za cel zrobić mecha­nizm, który będzie mi sorto­wał ciągi znaków. Ponadto ma dać się nim stero­wać, czy np. liczby mają mieć pierw­szeń­stwo przed lite­rami oraz czy wiel­kie litery mają mieć pierw­szeń­stwo przed małymi. Powstało coś o ładnej nazwie SpiechuPriorityQueueSpiechuStringComparator (skrom­ność przede wszystkim).

Najpierw kod klasy kolejki:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class SpiechuPriorityQueue extends SplHeap {
 
   private $stringComparator;
 
   public function __construct($ignoreCase = false, $numbersAtFront = true) {
      $this->stringComparator = new SpiechuStringComparator('PL',$ignoreCase,$numbersAtFront);
}
 
   public function insert($value,$priority = 0) {
      echo 'Dodaję: ' . $value . '<br/>';
      parent::insert($value);
}
 
   protected function compare($str1,$str2) {
      return $this->stringComparator->compare($str1, $str2);
}
 
   public function extract() {
      $extracted = parent::extract();
      echo 'Wyciągam: ' . $extracted . '<br/>';
      return $extracted;
}
}

Mamy tutaj kilka tajem­ni­czych zagrań:

  1. na etapie tworze­nia obiektu kolejki możemy usta­lić para­me­try sortowania,
  2. całą robotę odwala za nas SpiechuStringComparator,
  3. nie da się usta­lać ręcz­nie prio­ry­te­tów w meto­dzie insert (ręczne mody­fi­ka­cje popsu­łyby nasz mecha­nizm sortujący),
  4. wyma­gane jest prze­sło­nię­cie metody compare, którą SplHeap sam sobie wywo­łuje tworząc hierar­chię elemen­tów w środku

Zapewne pojawi się pyta­nie jak tego używać. Dosyć prosto:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?
$queue = new SpiechuPriorityQueue();
 
$queue->insert('ABC');
$queue->insert('CBA');
$queue->extract();
$queue->insert('ABW');
$queue->insert('NKOTB');
$queue->extract();
$queue->insert('1Pierwszy');
$queue->extract();
$queue->insert('2Drugi');
$queue->insert('3Ostatni');
$queue->extract();
$queue->insert('abc');
$queue->insert('abCadabra');
 
echo '---pozostałe---<br>';
 
while($queue->valid()) {
   $queue->extract();
}
?>

Widać powy­żej, że prze­pla­tamy doda­wa­nie i wycią­ga­nie elemen­tów. Na koniec spraw­dzamy czy coś jest w kolejce i wyciągamy.

Co zwróci nam powyż­sze wywo­ła­nie kodu? Domyśl­nie kolejka nie igno­ruje małych i dużych liter oraz stawia liczby jako ważniej­sze od liter. Otrzymamy:

Dodaję: ABC
Dodaję: CBA
Wycią­gam: ABC
Dodaję: ABW
Dodaję: NKOTB
Wycią­gam: ABW
Dodaję: 1Pierwszy
Wycią­gam: 1Pierwszy
Dodaję: 2Drugi
Dodaję: 3Ostatni
Wycią­gam: 2Drugi
Dodaję: abc
Dodaję: abCa­da­bra
—pozo­stałe—
Wycią­gam: 3Ostatni
Wycią­gam: abCa­da­bra
Wycią­gam: abc
Wycią­gam: CBA
Wycią­gam: NKOTB

Jeżeli zigno­ru­jemy wiel­kość liter, a liczby usta­wimy jako mniej ważne od liter to otrzymamy:

Dodaję: ABC
Dodaję: CBA
Wycią­gam: ABC
Dodaję: ABW
Dodaję: NKOTB
Wycią­gam: ABW
Dodaję: 1Pierwszy
Wycią­gam: CBA
Dodaję: 2Drugi
Dodaję: 3Ostatni
Wycią­gam: NKOTB
Dodaję: abc
Dodaję: abCa­da­bra
—pozo­stałe—
Wycią­gam: abc
Wycią­gam: abCa­da­bra
Wycią­gam: 1Pierwszy
Wycią­gam: 2Drugi
Wycią­gam: 3Ostatni

Fajne, co?

Mecha­nizm sorto­wa­nia zrobi­łem od zera sam. Dostępny jest tutaj. Może­cie sobie robić z nim co chce­cie (pod warun­kiem napi­sa­nia pozy­tyw­nego komen­ta­rza pod wpisem i ewen­tu­al­nym klik­nię­ciem na „Lubię to” :-) ). Komen­to­wa­łem po angiel­sku (trochę zwięk­szę tym audy­to­rium, a Wam zapewne nie zrobi różnicy).


Multithreading in web browser (Web Workers + jQuery)

This post is a kind of expe­ri­ment. It’s not a strict trans­la­tion of my previous post writ­ten in polish. I’m just curious how „uncle Google” will behave and if he send me some visi­tors :-) Notice that english is my second langu­age, so you will have to bear some my langu­age mista­kes. In case this project succe­edes, I will write some more english stuff.

I’m going to write about one of HTML5 new featu­res — Web Workers. These are inten­ded to provide some heavy compu­ta­tions in the back­gro­und and — what is most impor­tant — web brow­ser rema­ins respon­sive. It means that you won’t expe­rience any slowe­ness. When Web Worker ends its job, it noti­ces brow­ser about the outcome. The point is to use user’s CPU which is rarely full used in oppo­site to server’s.

By the way, multi­th­re­ading in brow­ser is not a big deal, Face­book uses it to provide you instant messa­ging or wall refre­shing without relo­ading the whole page.

Offi­cial draft of Web Workers API informs that workers have many restric­tions, such as no access to DOM or (in our case) jQuery object. You may think of it as a closed envi­ron­ment. Howe­ver you are able to commu­ni­cate in both ways via JSON and post­Mes­sage() function.

Take a look at our goal. We can control spaw­ning of new Workers, it state update inte­rvals and the moment of termi­na­tion. To ensure that our brow­ser supports Web Workers, we’ll use nice library specia­li­zed in chec­king a lot of modern HTML featu­res — Moder­nizr. In both cases user reads alert message if he can use workers or not.

Basicly we need two files: worker and main html file. Below is Java­Script code of timeworker.js:

var workerNo;

onmessage = function(e) {
    workerNo = e.data.workerNo;
    changeInterval(e.data.timer);
}

function changeInterval(interval) {
    setInterval("gimmeTime()",interval);
}

function gimmeTime() {
    postMessage({
        time:new Date().toUTCString(),
        workerNo:workerNo
    });
}
  1. Field wor­kerNo conta­ins worker id — inte­ger 1 or greater.
  2. onmes­sage is message recie­ved from the brow­ser. Notice that we can handle recie­ved data via e.data, not e alone!
  3. chan­ge­In­te­rval is func­tion which self calls gimme­Time() func­tion with given para­me­ter inte­rval (in microsecs).
  4. gim­me­Time sends brow­ser JSON message with its id and current time via post­Mes­sage() function.

Now it’s time for workersdemoeng.html (I’m listing only JS code):

var workers = new Array();
var counter = 0;
var bindedWorkers = '';

if (Modernizr.webworkers) {
    alert("Your browser supports Web Workers technology, you're free to test it!");
}
else {
    alert('Your browser does NOT support Web Workers technology. Install yourself a decent browser (FF, Chrome, Opera[?])!');
}

function addNotification(workerno) {
    var msg = $('<p>').appendTo('#notif');
    msg.text('Worker '+workerno+' updated state');
    msg.fadeOut(1500);
}

function fireUpWorker() {
    ++counter;
    var workerDiv = $('<div>').attr('id','workerdiv-'+counter).appendTo('#counters');
    var par = $('<p>').attr('id','worker-'+counter).appendTo(workerDiv);
    $('<input />').attr({
        type:'submit',
        value:'terminate',
        onClick:'terminateWorker('+counter+');return false;'
    }).appendTo(workerDiv);
    workers[counter] = new Worker('timeworker.js');
    var timer = $('#intrv').val();
    workers[counter].postMessage({timer:timer,workerNo:counter});
    bindWorkers();
}

function terminateWorker(workerNo) {
    workers[workerNo].terminate();
    $('#worker-'+workerNo).text('terminated!');
    $('#workerdiv-'+workerNo).fadeOut(5000);
}

function bindWorkers() {
    bindedWorkers += "workers["+counter+"].onmessage = function(event) {";
    bindedWorkers += "$('#worker-"+counter+"').text('Worker '+event.data.workerNo+' date : '+event.data.time);";
    bindedWorkers += "addNotification(event.data.workerNo);";
    bindedWorkers += "};";
    eval(bindedWorkers);
}
  1. We have 3 global fields: array wor­kers which holds all active workers, workers coun­ter and bin­de­dWor­kers which conta­ins ugly JS code to be execu­ted by eval() — the only way I know to force brow­ser to listen all workers, not only the last one created.
  2. add­No­ti­fi­ca­tion shows short 1,5 sec. noti­fi­ca­tion about worker state update.
  3. fireU­pWor­ker is the main func­tion. Firstly it creates a new div, and then para­graph and termi­nate button. Newly created worker rece­ives JSON message about its number and state update inte­rval. At the end, bin­dWor­kers() is executed.
  4. ter­mi­na­te­Wor­ker firstly kills certain wor­ker by worker.terminate(), and then hides div rela­ted to it.
  5. Finally the func­tion I’m least satis­fied of: bin­dWor­kers(). It handles what brow­ser should do with the rece­ived message from a worker. It works analo­gi­cally like the worker’s one: thro­ugh onmes­sage and e.data. To bin­de­dWor­kers string we’re appen­ding the code and execu­ting by eval(). The worst part is when we termi­nate some workers, this string conta­ins useless data (we could cut this out by some rege­xps, but I’m lazy ;-) ).

I have a big requ­est for the JS geeks: how to run onmes­sage code in a loop and without eval?

P.S.: It seems that I have found a bug in Chrome brow­ser: an attempt to add onclick attri­bute by jQuery didn’t worked. You should use onClick instead :-)


Wielowątkowość w przeglądarce (Web Workers + jQuery)

Przed nami ciekawe czasy w tech­no­lo­gii WWW. Powoli wcho­dzi HTML5 wpro­wa­dza­jąc całko­wi­cie nową jakość. Póki co strony w HTML5 mają charak­ter bardziej poka­zowy niż użyteczny. Ja jako maniak tech­no­lo­gii musia­łem zacząć się z tym wszyst­kim bawić. Na pierw­szy ogień idzie tech­no­lo­gia Web Workers [dalej WW].

WW to próba wpro­wa­dze­nia wielo­wąt­ko­wo­ści w prze­glą­darce WWW. W zało­że­niu zada­nia, które wyma­gają dużej mocy obli­cze­nio­wej wrzu­cane są do takiego workera. Gdy sobie skoń­czy liczyć, powia­da­mia prze­glą­darkę, a ta wyświe­tla wynik. Chodzi o to, że użyt­kow­nik strony nieko­niecz­nie ma ochotę cier­pieć zwol­nie­nie pracy prze­glą­darki w czasie kiedy proce­sor jest obcią­żony (ładnie się to nazywa respon­syw­ność przeglądarki).

Zresztą to nie jest jakaś super nowość. Z wątkami w tle spoty­ka­cie się na co dzień prze­glą­da­jąc Face­bo­oka (powia­do­mie­nia kto jest na czacie, nowe posty na tablicy bez prze­ła­do­wy­wa­nia itd.) Oficjalne API WW ma spore obostrze­nia. Worker to jakby zamknięte środo­wi­sko. Nie mamy dostępu do np. obiek­tów strony macie­rzy­stej (DOM, jQuery). Więcej pole­cam poczy­tać u Marco­osa.

Dzisiaj zrobimy coś, czego nie widzia­łem nigdzie w Inter­ne­cie póki co. Spró­bu­jemy dyna­micz­nie gene­ro­wać nowe wątki, kaso­wać je, a nawet regu­lo­wać często­tli­wość ich odpo­wie­dzi. Wszystko to będzie oczy­wi­ście ładnie okra­szone biblio­teką jQuery. Siedzia­łem nad tym pół niedzieli, więc uważajcie!

Najpierw rzuć­cie okiem na efekt finalny, a poni­żej wyja­śnie­nia co ciekaw­szych rzeczy.

Pierw­sze co robimy to spraw­dzamy czy prze­glą­darka użyt­kow­nika w ogóle obsłu­guje WW. Korzy­stamy z biblio­teki Moder­nizr i wyświe­tlamy alert informujący.

Druga sprawa to kod samego workera (timeworker.js):

var workerNo;

onmessage = function(e) {
    workerNo = e.data.workerNo;
    changeInterval(e.data.timer);
}

function changeInterval(interval) {
    setInterval("gimmeTime()",interval);
}

function gimmeTime() {
    postMessage({
        time:new Date().toUTCString(),
        workerNo:workerNo
    });
}
  1. Pole workerNo zawiera iden­ty­fi­ka­tor workera — liczbę od 1 w górę.
  2. onmes­sage to komu­ni­kat odebrany od prze­glą­darki. Zwra­camy uwagę na to, że do odebra­nych danych dobie­ramy się poprzez e.data, a nie samo e!
  3. chan­ge­In­te­rval to funk­cja usta­wia­jąca samo­wy­wo­ła­nie okre­ślo­nej funk­cji cyklicz­nie co podany czas w ms.
  4. gimme­Time to funk­cja wysy­ła­jące prze­glą­darce JSON z aktu­al­nym czasem i swoim iden­ty­fi­ka­to­rem za pomocą funk­cji post­Mes­sage().

Teraz czas na kod workersdemo.html (podaję sam kod Java­Script bo szkoda miejsca).

var workers = new Array();
var licznik = 0;
var bindedWorkers = '';

if (Modernizr.webworkers) {
    alert('Twoja przeglądarka wspiera Web Workers, możesz śmiało testować!');
}
else {
    alert('Twoja przeglądarka NIE wspiera Web Workers, demo nie będzie działało. Zainstaluj sobie prawdziwą przeglądarkę (FF, Chrome, Opera[?])!');
}

function addNotification(workerno) {
    var msg = $('<p>').appendTo('#notif');
    msg.text('Worker '+workerno+' updated state');
    msg.fadeOut(1500);
}

function fireUpWorker() {
    ++licznik;
    var workerDiv = $('<div>').attr('id','workerdiv-'+licznik).appendTo('#liczniki');
    var par = $('<p>').attr('id','worker-'+licznik).appendTo(workerDiv);
    $('<input />').attr({
        type:'submit',
        value:'terminate',
        onClick:'terminateWorker('+licznik+');return false;'
    }).appendTo(workerDiv);
    workers[licznik] = new Worker('timeworker.js');
    var timer = $('#intrv').val();
    workers[licznik].postMessage({timer:timer,workerNo:licznik});
    bindWorkers();
}

function terminateWorker(workerNo) {
    workers[workerNo].terminate();
    $('#worker-'+workerNo).text('terminated!');
    $('#workerdiv-'+workerNo).fadeOut(5000);
}

function bindWorkers() {
    bindedWorkers += "workers["+licznik+"].onmessage = function(event) {";
    bindedWorkers += "$('#worker-"+licznik+"').text('Worker '+event.data.workerNo+' date : '+event.data.time);";
    bindedWorkers += "addNotification(event.data.workerNo);";
    bindedWorkers += "};";
    eval(bindedWorkers);
}
  1. Mamy 3 pola globalne: tablicę workers trzy­ma­jącą wszyst­kich aktyw­nych worke­rów, licz­nik worke­rów i binde­dWor­kers zawie­ra­jący brzydki kod Java­Script do wyko­na­nia poprzez eval() — jedyny znany mi sposób na zmusze­nie prze­glą­darki żeby słuchała wszyst­kich aktyw­nych worke­rów, a nie tylko ostat­niego dodanego.
  2. addNo­ti­fi­ca­tion wyrzuca użyt­kow­ni­kowi komu­ni­kat o odebra­niu wiado­mo­ści od workera, znika po 1,5 sek.
  3. fireU­pWor­ker (nie chciało mi się wysi­lać na lepszą nazwę ;-) ) to główna funk­cja. Najpierw tworzy nowe elementy HTML: div, w nim para­graf na komu­ni­kat z workera i przy­cisk zabi­ja­jący. Nowo stwo­rzo­nemu worke­rowi wysy­łam komu­ni­kat w JSON co jaki czas ma się odświe­żać i jaki ma numer. Na koniec wywo­łuję funk­cję bindWor­kers().
  4. termi­na­te­Wor­ker najpierw zabija okre­ślo­nego workera poprzez worker.terminate(), a następ­nie ukrywa zwią­za­nego z nim diva.
  5. Na koniec funk­cja, z której jestem najmniej zado­wo­lony: bindWor­kers(). Zajmuje się usta­wia­niem co prze­glą­darka ma robić w przy­padku otrzy­ma­nia komu­ni­katu od workera. Działa to analo­gicz­nie jak poprzed­nio. Poprzez onmes­sagee.data. Do pola binde­dWor­kers doda­jemy kod w postaci ciągu i urucha­miamy evalem.

Tutaj mam prośbę dla znaw­ców. Doda­wa­nie onmes­sage w pętli dla poszcze­gól­nych worke­rów osobno nie dzia­łało. Jak zała­twić problem bez używa­nia evala?

P.S.: Znala­złem chyba buga w Chrome: Próby doda­nia atry­butu onclick poprzez jQuery nic nie dają. Należy używać onClick :-)