Archivio per la categoria "PHP"



About Predis and benchmarks: why a pure-PHP Redis client anyway?

As some of you might know I'm the author and maintainer of Predis, a pure-PHP client library for Redis. When I started this project back in mid-2009 Redis was a very young yet promising NoSQL key-value store with only three client libraries available for PHP (one of which was a C-based extension that eventually led to phpredis) offering support only for the Redis commands implemented at that time, and plagued by many bugs. Fast forward to date, and both Predis and phpredis are now the two most used, stable and up-to-date clients in PHP-land among the bunch available. Although it's mostly a one-man project, I'm very proud of the work behind its development and thankful for the contributions received from the community.

So why am I writing this blog post? To cut it straight to the point, I think there's sometimes a little misconception about the very reason for which Predis exists nowadays and Aleksey's recent blog post is offering me a good opportunity to clarify things a bit. Don't get me wrong he made various valid points, but what has actually caught my attention and prompted me to write this post is the very introduction:

As some of you may know, I’m crazy about speed. So when I saw that people were happily using Predis as their choice of PHP client for Redis, I was a bit confused.

Why use a client written in PHP for something that should be ‘fast’ like Redis?

That kind of defeats the purpose - unless you don’t really care about response times and scalability. 

Predis was initially developed to fill the lack for a decent client library but turned into a more comprehensive solution allowing for maximum extensibility. In a sense, you may think of this library not only as a mere client for Redis but as a sort of framework (pass me the buzzword) with various building blocks that developers can leverage to create abstractions for features based upon Redis and expose them through a consistent interface. In my opinion a great example of such extensibility is the abstraction used to support Lua scripts exposing them as if they were common Redis operations such as GET or SET (and it works when using client-side sharding or master/slave replication too). Some parts were also reused to build a fully-asynchronous version of the client!

Obviously there's a price to pay for everything and, in our case, that price is a penalty in raw speed compared to a C-based extension due to the undeniable greater overhead of a pure-PHP implementation. You can mitigate part of this overhead by using PHP >= 5.4 (faster at method dispatching), an opcode cache (you should do that anyway on production servers) or even plugging in phpiredis for faster protocol (de)serialization, but in the end you are optimizing your stack on a single-server basis. You can have 5, 10, 100 servers hitting Redis for distributed operations: that's where Redis shines the best and that's why it's considered blazing fast. In such distributed scenarios, when one or more Redis instances are running on remote hosts, you will soon notice that most of the difference in speed is lost due to network round-trip times so you will be left for the most part with the inherent overhead of compiling the source code (or loading the cached opcodes) of Predis on each request. We've always been clear on what to expect out of Predis in this case and this is the reason why we have a FAQ about performances shipped along with the library. It's a matter of trade offs: you sacrifice a low overhead for the sake of flexibility.

But then again, is it possible to use Predis when you also need decent overall performances or scalability? Depending on your definition of "decent performances" in the context of your application and provided a correct setup and architecture, yes. I personally know of a few high-load web sites using Predis, each one with a different reason behind their choice. Sure, if you don't need fancy features and you only have one server performing requests against Redis running on the localhost then you would probably prefer to stick with phpredis thanks to its lower overhead, but that doesn't mean being able to scale. Is Predis better than phpredis, or the other way around? Simply put, they are two solutions to the same problem and both have their different strong and weak points. In the end you are the one to decide based on your needs but, more importantly and as Aleksey concluded, don't base your decisions on assumptions. And, I'd just like to add, not even on general benchmarks.

This\is\lame\r\n ... è un namespace, almeno in PHP 5.3

Finalmente la lunga saga dell'introduzione del supporto per i namespace in PHP 5.3 è giunta, forse, al termine nelle ultime ore. Finalmente perché la discussione è andata avanti per diversi mesi, con lunghi thread su php.internals aperti in maniera pressoché perpetua ma spesso abbastanza inconcludenti. Vi anticipo fin da subito il finale della storia, ma vi avviso che secondo me non è dei migliori: è stato deciso di sostituire :: con il carattere \ per identificare la separazione dei namespace.

Il problema principale della scelta di :: come separatore per i namespace, secondo i core developer di PHP, risiede nel fatto che in alcuni casi può crearsi un'ambiguità non risolvibile dall'interprete poiché lo stesso :: viene usato anche per accedere ai metodi di classe statici. Ecco un esempio pratico:

1 // -------------------------------------- 2 // primo caso: Classe::methodoStatico() 3 4 class Foo { 5 public static function bar() { 6 echo "Foo::bar()"; 7 } 8 } 9 10 Foo::bar(); 11 12 // -------------------------------------- 13 // secondo caso: Namespace::funzione() 14 15 namespace Foo; 16 17 function bar() { 18 echo "Foo::bar()"; 19 } 20 21 Foo::bar();

L'ambiguità delle righe 10 e 21 risulta evidente, tanto che anche l'interprete in questo caso non saprebbe come comportarsi. Per questo motivo gli sviluppatori di PHP hanno pensato di aggirare il problema cambiando appunto in \ il separatore per i namespace.

Se doveste chiedermi cosa ne penso vi direi che, così di primo acchito e pur non avendo da offrire soluzioni in merito alla questione, per conto mio l'idea non è stata delle migliori. Senza alcun dubbio c'erano dei problemi, ma se da un lato è vero che così vengono risolte possibili ambiguità, con la scelta di \ vedo l'introduzione di bug subdoli dovuti alla mancata attenzione nell'uso della quotazione singola o, in caso contrario, un errato escaping.

$singleQuote = new ReflectionClass('foo\bar\className'); $doubleQuoteWithEscape = new ReflectionClass("foo\\bar\\$classNameInVar");

Ad ogni modo vi fornisco alcuni link la cui lettura può tornarvi utili per farvi un'opinione sulla decisione presa:

Ancora su lambda e closure in PHP

Anche se a pochi giorni di distanza, ho pensato di tornare a parlare di funzioni lambda e closure in PHP approfittando di un nuovo aggiornamento della RFC (con relativa patch che implementa quanto descritto) in cui sono state finalizzate alcune idee e sono stati introdotti alcuni nuovi concetti.

Prima di tutto è stata presa una decisione per quanto riguarda la sintassi dichiarativa, infatti precedentemente erano state formulate due possibilità. La prima prevedeva la cattura delle variabili esterne alla chiusura tramite una nuova keyword creata ad hoc, lexical:

$lambda = function($arg1, $arg2) { lexical $reference, &$valueByRef; // ... };

La seconda, quella che è stata effettivamente scelta, prevede invece il riutilizzo della keyword già esistente use:

$lambda = function($arg1, $arg2) use ($reference, &$valueByRef) { // ... };

Qualcuno preferiva la prima opzione e personalmente ero dello stesso avviso, ma alla fine la scelta è stata dettata dall'intenzione di raggiungere un compromesso in maniera tale da ottenere le stesse funzionalità mantenendo la consistenza con l'attuale semantica di PHP e senza intaccare la retrocompatibilità.

La prima estensione nella RFC riguarda invece l'utilizzo della keyword static per stabilire se importare o meno $this all'interno di una chiusura definita all'interno del metodo di una classe. Non effettuare l'importazione di $this all'interno di una chiusura quando non necessario permette di ottenere un risparmio non solo in termini di velocità d'esecuzione, anche si tratta di differenze veramente minimali, ma anche e soprattutto di memoria, evitando che delle long-lived closure possano tenere in vita inutilmente le istanze delle classi in cui esse sono state create. La chiusura che cattura $this infatti impedisce che il refcount interno per l'oggetto che referenzia possa arrivare a 0 e ciò ne comporta l'esclusione dal processo automatico di raccolta delle risorse che viene effettuato dal garbage collector. Vediamo un esempio pratico con tanto di risultato:

class NRK { private $_whoAmI = __CLASS__; public function foo() { $method = __METHOD__; return function() use ($method) { return sprintf("created in %s (w/ \$this):\n%s", $method, print_r($this, true) ?: 'null' ); }; } public function bar() { $method = __METHOD__; return static function() use ($method) { return sprintf("created in %s (w/o \$this):\n%s", $method, print_r($this, true) ?: 'null' ); }; } } $nrk = new NRK(); $fun1 = $nrk->foo(); $fun2 = $nrk->bar(); echo $fun1(), "\n", $fun2(); /* created in NRK::foo (w/ $this): NRK Object ( [_whoAmI:NRK:private] => NRK ) created in NRK::bar (w/o $this): null */

In realtà trovo che sarebbe molto più comodo e pulito avere $this importata in automatico nel caso essa venga esplicitamente richiamata nel corpo della closure, per il compilatore non sarebbe difficile accorgersene ed agire di conseguenza, ad ogni modo non mi piace l'uso della keyword static dal momento che rende ancora più verbosa la definizione di una chiusura.

Proseguendo con le nuove funzionalità, nell'ottica di una maggiore integrazione con PHP sono state estese le classi ReflectionMethod e ReflectionFunction implementando il metodo getClosure() che permette di ottenere una funzione lambda generata dinamicamente partendo da una funzione o da un metodo di istanza/classe:

class NRK { public function instanceMeth() { return __METHOD__; } public static function staticMeth() { return __METHOD__; } public static function methWithArgs($a, $b) { return $a + $b; } } $instance = new NRK(); $class = new ReflectionClass('NRK'); $fun1 = $class->getMethod('instanceMeth')->getClosure($instance); $fun2 = $class->getMethod('staticMeth')->getClosure(); $fun3 = $class->getMethod('methWithArgs')->getClosure(); echo $fun1(), "\n", $fun2(), "\n", $fun3(5, 3); /* NRK::instanceMeth NRK::staticMeth 8 */

Per finire, un'altra novità ispirata dall'implementazione dell'oggetto Closure riguarda la generalizzazione del concetto di oggetto invocabile attraverso l'implementazione di un nuovo metodo magico, __invoke(). Un esempio vale più di mille parole:

class CallableObject { public function __invoke () { return 'Guess what?'; } } $callable = new CallableObject(); $notCallable = new stdClass(); echo '$callable is ', is_callable($callable) ? '' : 'not ', "a callable object\n"; echo '$notCallable is ', is_callable($notCallable) ? '' : 'not ', "a callable object\n"; echo $callable(); echo $notCallable(); /* $callable is a callable object $notCallable is not a callable object Guess what? Fatal error: Function name must be a string in [...] */

Tirando le somme lo stato dell'implementazione di lambda e closure in PHP sembrerebbe essere in uno stato particolarmente avanzato al punto tale che lo stesso Andi Gutmans su php.internals ha chiesto, soprattutto ai release manager, di pensare se sia effettivamente possibile includere tutto questo già in PHP 5.3. In effetti PHP 5.3 sarà già ricco di novità di per sè (namespace e collaterali, LSB, __callStatic, etc) quindi l'introduzione di un'altra nuova feature è da valutare bene, tuttavia vista la buona salute della proposta e dei lavori per implementarla sarebbe un peccato dover aspettare un altro anno o più. Intanto però c'è anche chi si chiede: ma alla fine le novità di PHP 6 saranno rappresentate solo dal supporto per Unicode?

Lambda e closure in PHP 5.3? Forse sì, forse no, ma intanto proviamo.

Negli ultimi giorni sulla mailing list di sviluppo di PHP si sta discutendo sulla possibilità di estendere il linguaggio in maniera tale da introdurre i concetti di funzioni lambda e di closure tanto cari a molti linguaggi dinamici come Ruby, Python, JavaScript, etc. In realtà si potrebbe quasi considerare come un ritorno di fiamma, già in passato ci furono discussioni simili anche se poco convinte e poco convincenti, tuttavia questa volta si intravede la possibilità di sfociare in una decisione furba nonostante non ci sia particolare interesse nell'implementazione da parte di alcuni dei core-dev (tanto per cambiare). Attualmente il tutto ruota intorno a una RFC che, oltre a mettere in evidenza alcuni scenari sull'utilizzo di funzioni lambda e chiusure e riportare i dettagli implementativi nell'engine di Zend, allega alcune patch con alcune piccole varianti sul tema da applicare ai sorgenti nei rami di sviluppo di PHP 5.3 e PHP 6.0 per poter toccare con mano quanto si sta discutendo.

$fun = function($name = 'guy') { echo "Hi $name!"; }; printf("Tipo: %s\n", get_class($fun)); // Type: Closure printf("Callable: %s\n", is_callable($fun)); // Callable: 1

Come possiamo notare, è stato implementato un nuovo tipo in ZE denominato Closure. Closure risulta essere invocabile, alla stessa stregua di funzioni e metodi:

echo $fun(); // Hi guy! echo $fun('NRK'); // Hi NRK!

Il primo pensiero va a questo punto a tutte quelle funzioni in PHP che accettano delle callback ma in cui comodità ed espressività vengono pesantemente minate dall'attuale modus operandi del linguaggio che prevede due opzioni:

  • definire una funzione tradizionale da usare come argomento callback ma sotto forma di stringa (il nome della funzione stessa), dando quindi origine a potenziali bugs galore.
  • creare una funzione a runtime usando create_function, opzione che oltre a risultare poco elegante alla vista rischia di introdurre un'altra buona dose di problemi in termini di manutenibilità del codice.

Ecco quindi un confronto tra come è possibile scrivere codice oggi e la possibilità di sviluppare la stessa soluzione attraverso l'utilizzo delle funzioni lambda:

$array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); /* senza funzioni lambda */ function reducer($a, $b) { return $a * $b; } $res = array_reduce($array, 'reducer', 1); // 3628800 /* senza funzioni lambda */ $res = array_reduce($array, create_function('$a, $b', 'return $a * $b;'), 1); // 3628800 /* con funzion lambda - versione "lunga" */ $reducer = function($a, $b) { return $a * $b; }; $res = array_reduce($array, $reducer, 1); // 3628800 /* con funzion lambda - versione "breve" */ $res = array_reduce($array, function($a, $b) { return $a * $b; }, 1); // 3628800

Il fatto che l'oggetto Closure sia a tutti gli effetti visibile in userland permette anche di sfruttare l'hinting dei tipi nel caso volessimo limitare a questo tipo di oggetto alcuni parametri delle nostre funzioni:

function iterate($array, Closure $fun) { foreach ($array as $element) $fun($element); } iterate($array, 'function'); // genera un Catchable Fatal Error, Argument 2 passed to // iterate() must be an instance of Closure, string given

Essendo Closure una vera e propria classe (che, per la cronaca, attualmente espone solo un metodo __toString() seppur vuoto) potrebbe implementare in un ipotetico futuro diverse funzionalità interessanti e chissà, potrebbe anche essere serializzabile. Ad ogni modo, dopo le funzioni lambda passiamo a vedere le closure:

$array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); $sum = 0; $fun = function($element) { lexical &$sum; // ... $sum += $element; }; array_walk($array, $fun); echo $sum; // 55

Possiamo notare l'introduzione della keyword lexical (c'è anche la variante use a seconda della patch che si decide di utilizzare) che ci permette di catturare i riferimenti alle variabili esterne al corpo della chiusura. L'introduzione di lexical è dovuta al fatto che con questa implementazione non viene catturato tutto l'ambiente lessicale in cui viene creata la chiusura, come accade invece in altri linguaggi, per motivi legati principalmente alle regole di scope di PHP. Usando lexical possiamo pertanto decidere quali variabili catturare, ma è da notare che attualmente per i tipi numerici o stringa, che per default in PHP sono passati per valore, è necessario creare manualmente un riferimento anteponendo l'ampersand alla variabile catturata, come da esempio. Tecnicamente la cattura per riferimento dovrebbe essere di default anche per questi tipi e lo stesso creatore della patch è del medesimo avviso, tanto più che il terribile global si comporta nello stesso modo creando sempre riferimenti.

In linea di massima l'introduzione di funzioni lambda e closure in PHP sarebbe interessante e in certe situazioni renderebbe la scrittura dei programmi un po' meno arrabattata con la possibilità di ottenere codice molto più facilmente generalizzabile e più verificabile/manutenibile. Provare è abbastanza facile: basta avere un ambiente configurato per compilare PHP, scaricare i sorgenti di PHP 5.3 o 6.0 dal repository CVS (sì, usano ancora CVS...), applicare una delle patch disponibili, compilare e installare.

Pre e post hook a funzioni o metodi in PHP

Curiosando qua e là in PECL ho scoperto una nuova estensione per PHP abbastanza interessante, funcall, che permette di aggiungere degli hook a funzioni o metodi prima o dopo la loro esecuzione, invocandoli in automatico. Per esempio:

function funzione($foo, $bar) { echo "Foo: $foo, Bar: $bar"; } function pre_funzione($args) { // $args -> argomenti passati alla funzione (come array) } function post_funzione($args, $result, $time) { // $args -> argomenti passati alla funzione (come array) // $result -> contiene il valore ritornato della funzione // $time -> tempo impiegato nel processare la funzione } fc_add_pre('funzione','pre_funzione'); fc_add_post('funzione','post_funzione'); funzione('lol', 'wut');

In questo caso pre_funzione e post_funzione verranno invocate automaticamente chiamando normalmente funzione e, ovviamente, possono essere attaccate più di una pre / post callback con la possibilità di ottenerne l'elenco a runtime sotto forma di array attraverso la funzione fc_list. La prima cosa che può venire in mente a questo punto è il pattern design by contract e in effetti questa estensione potrebbe tornare utile in questo senso, anche se ammetto che è tutto da verificare (leggasi: voglio provare bene prima di esserne sicuro). Rimane comunque da verificare la stabilità di questa estensione, dal momento che parrebbe recente, e quanto impatti in termini prestazionali un suo eventuale utilizzo, ad ogni modo ho già un caso in cui potrebbe tornarmi utile. Giusto per andare leggermente off-topic: in Ruby 2.0 questa funzionalità sarà supportata nativamente mentre ad oggi è disponibile solo attraverso l'utilizzo di moduli come DesignByContract.