J'ai donc écrit le code suivant :
closure = $closure;
}
public function accept()
{
return ($this->closure->__invoke($this) === true);
}
}
$letters = str_split('abcdefghijklmnopqrstuvwxyz');
$array = array_combine($letters, $letters) + range(1, 100000);
echo 'Iterator: ';
$start = microtime(true);
foreach (new closureFilterIterator(new arrayIterator($array), function(\iterator $iterator) { return is_string($iterator->key()); }) as $key => $value)
{
echo $value;
}
$time1 = (microtime(true) - $start);
echo ' [' . $time1 . ']' . "\n";
echo 'Foreach: ';
$start = microtime(true);
foreach ($array as $key => $value)
{
if (is_string($key) === true)
{
echo $value;
}
}
$time2 = (microtime(true) - $start);
echo ' [' . $time2 . ']' . "\n";
echo 'Delta: ' . ($time1 - $time2) . "\n";
echo 'Ratio: ' . ($time1 / $time2) . "\n";
?>
Rien de bien particulier, si ce n'est l'utilisation de la méthode magique __invoke() pour appeler la closure vu que PHP c'est de la merde qu'il n'est pas possible d'appeler une closure
de la manière normale si cette dernière est une propriété d'objet.
Pour votre information, j'ai entendu dire que ce point serait amélioré dans la prochaine version du langage, même si je n'ai pas l'ombre d'une confirmation de cela pour le moment.
Et les résultats, alors ?
Ils ont été sans appel.
L’exécution du code ci-dessus sur ma machine de développement en ligne de commande m'a donné les résultats suivant :
fch@diablo:~/tmp
8> php -nf closureFilterIterator.php
Iterator: abcdefghijklmnopqrstuvwxyz [0.39656400680542]
Foreach: abcdefghijklmnopqrstuvwxyz [0.055746793746948]
Delta: 0.34081721305847
Ratio: 7.113664843319
La méthode qui met en oeuvre filterIterator
est donc 7 fois plus lente que celle utilisant l'instruction foreach
et la fonction is_string()
.
Nous pourrions nous arrêter là, mais les plus attentifs auront remarqué l'utilisation du paramètre -n
, qui permet de ne pas charger en mémoire les modules additionnels de PHP, ceci afin d'éliminer l'influence d'extension tel qu'APC ou xdebug.
Si je n'utilise pas ce paramètre, j'obtiens les résultats suivant :
fch@diablo:~/tmp
9> php -f closureFilterIterator.php
Iterator: abcdefghijklmnopqrstuvwxyz [2.8110311031342]
Foreach: abcdefghijklmnopqrstuvwxyz [0.51954388618469]
Delta: 2.2914872169495
Ratio: 5.4105748867091
Oui, vous avez bien lu, bizarrement, non seulement les deux méthodes prennent plus de temps, mais le delta entre les deux est moins significatif...
J'avoue ne pas avoir pris le temps de chercher la ou les extensions responsables de ce phénomène, même si je pense très fortement que le coupable est xdebug.
Conclusion, mettre en oeuvre la SPL n'est pas forcément la meilleure de choses à faire en terme de performance, et charger en mémoire des modules PHP non indispensables au fonctionnement du code en production n'est pas non plus une bonne idée.
Mais bon, la SPL, cela permet d'écrire quand même du bien joli code...
Pour ceux qui veulent se la mesurer les fans de comparaisons, le fichier contenant le code est attaché à ce billet et voici quelques informations complémentaires :
fch@diablo:~/tmp
10> uname -a
FreeBSD diablo.local 7.1-RELEASE-p9 FreeBSD 7.1-RELEASE-p9 #9: Thu Dec 10 13:41:18 CET 2009 root@diablo.local:/usr/obj/usr/src/sys/DIABLO i386
fch@diablo:~/tmp
11> php --version
PHP 5.3.1 with Suhosin-Patch (cli) (built: Dec 10 2009 17:57:41)
Copyright (c) 1997-2009 The PHP Group
Zend Engine v2.3.0, Copyright (c) 1998-2009 Zend Technologies
with Xdebug v2.0.5, Copyright (c) 2002-2008, by Derick Rethans
with Suhosin v0.9.29, Copyright (c) 2007, by SektionEins GmbH
fch@diablo:~/tmp
12> php -m
[PHP Modules]
bcmath
bz2
calendar
Core
ctype
curl
date
dom
ereg
exif
expect
fileinfo
filter
ftp
gd
gettext
gmp
hash
iconv
imagick
imap
json
ldap
libxml
mbstring
mcrypt
mdbtools
memcache
mhash
mssql
mysql
mysqli
mysqlnd
odbc
openssl
pcntl
pcre
PDO
pdo_mysql
pdo_sqlite
pgsql
posix
pspell
readline
recode
Reflection
session
shmop
SimpleXML
snmp
soap
sockets
SPL
SQLite
standard
suhosin
svn
sysvmsg
sysvsem
sysvshm
tidy
tokenizer
wddx
xdebug
xml
xmlreader
xmlrpc
xmlwriter
xsl
yaz
zip
zlib
[Zend Modules]
Xdebug
J'ajouterais que contrairement aux apparences, ce billet n'est sponsorisé ni par l'Académie Française, ni par la DGLFLF.
9 réactions
1 De syndrael - 08/04/2010, 08:56
Ca fait longtemps que ça me dit de me mettre à filterIterator mais le souci est que le développement est plus intuitif avec un bon vieux foreach.. hélas..
Donc je suis toujours à la recherche d'un potentiel 'gain' de cette classe.. mais je ne désespère pas qu'un jour il me fasse gagner du temps ..et de la compréhension.
S.
2 De tight - 08/04/2010, 08:57
Je n'avais déjà pas spécialement le réflexe de regarder dans la SPL, je vais l'avoir encore moins !
PS: ta source n'est pas accessible (.php)
3 De mageekguy - 08/04/2010, 09:04
@tight : le fichier attaché est maintenant accessible, merci de m'avoir averti.
Et franchement, jette un oeil sur la SPL.
Dans ce cas précis, ce n'est peut être pas le moyen le plus efficace de faire le boulot, mais elle contient tout un tas de trucs très sympa.
4 De metagoto - 08/04/2010, 18:08
J'obtiens quelque chose de 3 fois plus rapide que le foreach. Mais c'est de la triche...
Au début j'ai essayé avec array_walk + une fonction callback standard mais aussi une lambda. C'est légèrement plus rapide avec la lambda. Mais array_walk reste plus lent que le simple foreach.
L'astuce c'est d'utiliser iterator_apply avec une lambda qui retourne false dès que le key n'est plus un string. L'itération stoppe (et plus la peine de parcourir les 100000 autres keys). Le tableau doit donc être pré-trié. Or si celui-ci est pré-trié, rien n'empêche de mettre un break dans le foreach. Ce dernier l'emporte donc à nouveau
5 De mageekguy - 08/04/2010, 19:19
@metagoto : Dans mon cas réel, le tableau n'est pas trié. J'ai bien pensé à faire un appel à la fonction shuffle() sur le tableau dans le code du test, mais cette dernière ne conserve pas les clefs. Et pour trier le tableau avant de récupérer les clefs qui nous intéresse, je suis presque sur qu'il n'y a pas de fonction native qui le permet. Et si c'est le cas, retour à la case iterator et/ou foreach.
Mais ca reste à confirmer, et je n'ai pas le temps de le faire maintenant.
Et bien joué pour iterator_apply(), je n'y avais pas pensé.
6 De metagoto - 08/04/2010, 20:19
J'ai "shufflé" le tableau avec shuffle_assoc, une fonction copiée rapidos dans les commentaires de shuffle() dans la doc php.
Le tri, je l'ai effectué avec asort() et le flag SORT_REGULAR, qui, à première vue, place bien les keys string avant les autres keys (en conservant l'associativité).
Mais ça reste hacky. Disons que si le tableau pouvait être trié coté db (ou quelque soit la source), alors iterator_apply pourrait "paraître" plus propre avec des perfs proches du foreach... surtout si la lambda fait des choses plus complexes qu'un simple test+echo.
7 De Armand - 08/04/2010, 21:10
Je n'ai pas de PHP sous la main pour tester, mais je me demande ce que donnerait un array_filter(array_flip($array), 'is_string');
Je testerai et posterai le résultat.
8 De mageekguy - 08/04/2010, 21:51
@Armand : Rien ne dit que les valeurs du tableau sont uniques.
9 De Laurentj - 09/04/2010, 16:24
tout très bon bouquin d'algo vous le dira : si vous voulez faire des traitements performants, trier d'abord, faites vos traitements ensuite. ça simplifie les algo, mais en plus dans la majorité des cas, ça donne des résultats plus performants.