la programmation asynchrone... et les pates
Post on 13-Dec-2014
897 Views
Preview:
DESCRIPTION
TRANSCRIPT
LA PROGRAMMATION ASYNCHRONE ET LES PÂTES
François Zaninotto - @francoisz - marmelab.com
PARLONS
CUISINE
Pour 4 personnes Préparation et cuisson: 20 minutes Ingrédients: - 500g de spaghetti - 6 tomates bien mûres - 1 oignon - 1 carotte - 2 gousses d'ail - 1 branche de céleri - huile d'olive - sauge, romarin, basilic frais - sel et poivre - amour
Spaghetti à la tomate de Giuseppina Plat principal - Facile - Bon marché
1. Faire bouillir une grande quantité d'eau (non salée) dans une casserole
2. Pendant ce temps, pelez les tomates et coupez-les grossièrement
3. Epluchez et émincez l'oignon, la carotte, l'ail et le céleri
4. Lorsque l'eau bout, salez-la, puis déposez les spaghettis en couronne
5. Faire chauffer l'huile d'olive dans une sauteuse. Mettez-y à brunir la deuxième gousse d'ail préalablement épluchée.
6. Retirez la gousse d'ail, puis versez le mirepoix (mélange de légumes) et faites revenir à feu vif
7. Ajoutez les tomates, les herbes, salez et poivrez copieusement. Faites chauffer à feu moyen pendant 5 minutes
8. Goûtez régulièrement les spaghettis. Lorsqu'ils sont cuits al dente, égouttez-les puis déposez-les dans un plat chaud.
9. Versez la sauce tomate immédiatement sur les pâtes fumantes. Ajoutez le basilic grossièrement découpé.
10.Servez avec du parmesan rapé et un bon Chianti Classico, et régalez-vous
Spaghetti à la tomate de Giuseppina Plat principal - Facile - Bon marché
MAINTENANT LE CHEF C’EST
PHP
// time is 0 $pastaPan = new Pan(); $water = new Water(); $pastaPan-‐>fill($water); $pastaPan-‐>warm($duration = 10); // now time is 10 $pastaPan-‐>fill(new Spaghetti()); $pastaPan-‐>warm($duration = 8); // now time is 18 $pastaPan-‐>remove($water); !$saucePan = new Pan(); $saucePan-‐>fill(new OliveOil()); $saucePan-‐>warm($duration = 2); // now time is 20 $saucePan-‐>fill(MirepoixFactory::create($withGarlic = true)); $saucePan-‐>warm($duration = 5); // now time is 25 $saucePan-‐>fill(TomatoFactory::create()); $saucepan-‐>warm($duration = 4); // now time is 29 !$plate = new Plate(); $plate-‐>addContentsOf($pastaPan); $plate-‐>addContentsOf($saucePan); $plate-‐>serve('Régalez-‐vous');
LES 3 GRANDS TABOUS DU DEVELOPPEUR
UTILISER eval()
INJECTER UN CONTENEUR D’INJECTION DE DÉPENDANCE
LES 3 GRANDS TABOUS DU DEVELOPPEUR
SERVIR DES PÂTES FROIDES
LES 3 GRANDS TABOUS DU DEVELOPPEUR
2/10 NOTE DES INVITÉS
EVENTLOOP À LA RESCOUSSE
class EventLoop { protected $tick = 0; protected $callbacksForTick = array(); ! public function executeLater($delay, $callback) { $this-‐>callbacksForTick[$this-‐>tick + $delay] []= $callback; } public function start() { while ($this-‐>callbacksForTick) { $this-‐>tick++; $this-‐>executeCallbacks(); } } public function executeCallbacks() { echo "tic-‐tac : " . $this-‐>tick . "\n"; if (!isset($this-‐>callbacksForTick[$this-‐>tick])) { return; // no callback to execute } foreach ($this-‐>callbacksForTick[$this-‐>tick] as $callback) { call_user_func($callback); } // clean up unset($this-‐>callbacksForTick[$this-‐>tick]); } }
LA CASSEROLE
ASYNCHRONE
class AsynchronousPan extends Pan { protected $eventLoop; public function __construct(EventLoop $eventLoop) { $this-‐>eventLoop = $eventLoop; } public function warm($duration, $callback) { $this-‐>eventLoop-‐>executeLater($duration, $callback); } }
$eventLoop = new EventLoop(); !
$pan = new AsynchronousPan($eventLoop); $pan-‐>warm(10, function() { echo "Régalez-‐vous\n"; }); !
echo "Ca chauffe !\n"; !
$eventLoop-‐>start();
DÉMONSTRATION
Ca chauffe ! tic-‐tac : 1 tic-‐tac : 2 tic-‐tac : 3 tic-‐tac : 4 tic-‐tac : 5 tic-‐tac : 6 tic-‐tac : 7 tic-‐tac : 8 tic-‐tac : 9 tic-‐tac : 10 Régalez-‐vous
LES SPAGHETTI
ASYNCHRONES
$eventLoop = new EventLoop(); !
$plate = new Plate(); !
// pasta $pastaPan = new AsynchronousPan($eventLoop); $water = new Water(); $pastaPan-‐>fill($water); echo "pastaPan: Allumage\n"; $pastaPan-‐>warm($duration = 10, function() use ($pastaPan, $plate, $water) { echo "pastaPan: L'eau bout\n"; $pastaPan-‐>fill(new Spaghetti()); echo "pastaPan: Lancement de la cuisson des spaghettis\n"; $pastaPan-‐>warm($duration = 8, function() use ($pastaPan, $plate, $water){ echo "pastaPan: Les spaghettis sont prêts\n"; $pastaPan-‐>remove($water); $plate-‐>addContentsOf($pastaPan); }); }); ../..
../.. // sauce $eventLoop-‐>executeLater($delay = 7, function() use ($plate, $eventLoop) { $saucePan = new AsynchronousPan($eventLoop); $saucePan-‐>fill(new OliveOil()); echo "saucePan: L'huile chauffe\n"; $saucePan-‐>warm($duration = 2, function() use($saucePan, $plate) { echo "saucePan: L'huile est chaude\n"; $saucePan-‐>fill(MirepoixFactory::create($withGarlic = true)); echo "saucePan: Lancement de la cuisson du mirepoix\n"; $saucePan-‐>warm($duration = 5, function() use($saucePan, $plate) { echo "saucePan: Le mirepoix est prêt pour la tomate\n"; $saucePan-‐>fill(TomatoFactory::create()); echo "saucePan: Lancement de la cuisson de la tomate\n"; $saucePan-‐>warm($duration = 4, function() use($saucePan, $plate) { echo "saucePan: La sauce est prête\n"; $plate-‐>addContentsOf($saucePan); }); }); }); }); !$eventLoop-‐>start(); $plate-‐>serve('Régalez-‐vous');
SÉQUENTIALITÉSy
nchro
ne
Asynchrone
pastaPan: Allumage tic-‐tac : 1 ... tic-‐tac : 7 saucePan: L'huile chauffe tic-‐tac : 8 tic-‐tac : 9 saucePan: L'huile est chaude saucePan: Lancement de la cuisson du mirepoix tic-‐tac : 10 pastaPan: L'eau bout pastaPan: Lancement de la cuisson des spaghettis tic-‐tac : 11 .... tic-‐tac : 14 saucePan: Le mirepoix est prêt pour la tomate saucePan: Lancement de la cuisson de la tomate tic-‐tac : 15 ... tic-‐tac : 18 pastaPan: Les spaghettis sont prêts saucePan: La sauce est prête Régalez-‐vous
SCRIPT SYNCHRONE
29 MINUTES
SCRIPT ASYNCHRONE
18 MINUTES
7/10 NOTE DES INVITÉS
class EventLoop { protected $tick = 0; protected $callbacksForTick = array(); ! public function executeLater($delay, $callback) { $this-‐>callbacksForTick[$this-‐>tick + $delay] []= $callback; } public function start() { while ($this-‐>callbacksForTick) { $this-‐>tick++; $this-‐>executeCallbacks(); } } public function executeCallbacks() { echo "tic-‐tac : " . $this-‐>tick . "\n"; if (!isset($this-‐>callbacksForTick[$this-‐>tick])) { return; // no callback to execute } foreach ($this-‐>callbacksForTick[$this-‐>tick] as $callback) { call_user_func($callback); } // clean up unset($this-‐>callbacksForTick[$this-‐>tick]); } }
// ... $eventLoop-‐>start(); !
// Never executed $plate-‐>serve('Régalez-‐vous');
RESYNCHRONISER L’ASYNCHRONE
class PlateOfSpaghettiWithSauce extends Plate { protected $hasSpaghetti = false; protected $hasSauce = false; public function addContentsOf(Pan $pan) { parent::addContentsOf($pan); if ($pan-‐>contains('Spaghetti')) { $this-‐>hasSpaghetti = true; } if ($pan-‐>contains('Tomato')) { $this-‐>hasSauce = true; } if ($this-‐>hasSpaghetti && $this-‐>hasSauce) { $this-‐>serve('Régalez-‐vous'); } } }
../.. // sauce $eventLoop-‐>executeLater($delay = 7, function() use ($plate, $eventLoop) { $saucePan = new AsynchronousPan($eventLoop); $saucePan-‐>fill(new OliveOil()); echo "saucePan: L'huile chauffe\n"; $saucePan-‐>warm($duration = 2, function() use($saucePan, $plate) { echo "saucePan: L'huile est chaude\n"; $saucePan-‐>fill(MirepoixFactory::create($withGarlic = true)); echo "saucePan: Lancement de la cuisson du mirepoix\n"; $saucePan-‐>warm($duration = 5, function() use($saucePan, $plate) { echo "saucePan: Le mirepoix est prêt pour la tomate\n"; $saucePan-‐>fill(TomatoFactory::create()); echo "saucePan: Lancement de la cuisson de la tomate\n"; $saucePan-‐>warm($duration = 4, function() use($saucePan, $plate) { echo "saucePan: La sauce est prête\n"; $plate-‐>addContentsOf($saucePan); }); }); }); }); !$eventLoop-‐>start(); $plate-‐>serve('Régalez-‐vous');
DÉMÊLER LE CODE
SPAGHETTI
$saucePan = new AsynchronousPan($eventLoop); $eventLoop-‐>executeLater($delay = 7, function() { call_user_func($warmSaucePan); }); $warmSaucePan = function() use ($saucePan) { $saucePan-‐>fill(new OliveOil()); echo "saucePan: L'huile chauffe\n"; $saucePan-‐>warm($duration = 2, $cookMirepoix); }; $cookMirepoix = function() use ($saucePan) { echo "saucePan: L'huile est chaude\n"; $saucePan-‐>fill(MirepoixFactory::create($withGarlic = true)); echo "saucePan: Lancement de la cuisson du mirepoix\n"; $saucePan-‐>warm($duration = 5, $cookTomato); }; $cookTomato = function() use ($saucePan) { echo "saucePan: Le mirepoix est prêt pour la tomate\n"; $saucePan-‐>fill(TomatoFactory::create()); echo "saucePan: Lancement de la cuisson de la tomate\n"; $saucePan-‐>warm($duration = 4, $serveSauce); }; $serveSauce = function() use ($saucePan, $plate) { echo "saucePan: La sauce est prête\n"; $plate-‐>addContentsOf($saucePan); };
$saucePan = new AsynchronousPan($eventLoop); $serveSauce = function() use ($saucePan, $plate) { echo "saucePan: La sauce est prête\n"; $plate-‐>addContentsOf($saucePan); }; $cookTomato = function() use ($saucePan, $serveSauce) { echo "saucePan: Le mirepoix est prêt pour la tomate\n"; $saucePan-‐>fill(TomatoFactory::create()); echo "saucePan: Lancement de la cuisson de la tomate\n"; $saucePan-‐>warm($duration = 4, $serveSauce); }; $cookMirepoix = function() use ($saucePan, $cookTomato) { echo "saucePan: L'huile est chaude\n"; $saucePan-‐>fill(MirepoixFactory::create($withGarlic = true)); echo "saucePan: Lancement de la cuisson du mirepoix\n"; $saucePan-‐>warm($duration = 5, $cookTomato); }; $warmSaucePan = function() use ($saucePan, $cookMirepoix) { $saucePan-‐>fill(new OliveOil()); echo "saucePan: L'huile chauffe\n"; $saucePan-‐>warm($duration = 2, $cookMirepoix); }; $eventLoop-‐>executeLater($delay = 7, function() use ($warmSaucePan) { call_user_func($warmSaucePan); });
$saucePan = new AsynchronousPan($eventLoop); $warmSaucePan = function($callback) use ($saucePan) { $saucePan-‐>fill(new OliveOil()); echo "saucePan: L'huile chauffe\n"; $saucePan-‐>warm($duration = 2, $callback); }; $cookMirepoix = function($callback) use ($saucePan) { echo "saucePan: L'huile est chaude\n"; $saucePan-‐>fill(MirepoixFactory::create($withGarlic = true)); echo "saucePan: Lancement de la cuisson du mirepoix\n"; $saucePan-‐>warm($duration = 5, $callback); }; $cookTomato = function($callback) use ($saucePan) { echo "saucePan: Le mirepoix est prêt pour la tomate\n"; $saucePan-‐>fill(TomatoFactory::create()); echo "saucePan: Lancement de la cuisson de la tomate\n"; $saucePan-‐>warm($duration = 4, $callback); }; $serveSauce = function() use ($saucePan, $plate) { echo "saucePan: La sauce est prête\n"; $plate-‐>addContentsOf($saucePan); }; $eventLoop-‐>executeLater($delay = 7, function() use ($plate, $eventLoop) { Async::waterfall( array($warmSaucePan, $cookMirepoix, $cookTomato), $serveSauce ); });
class Async { public static function waterfall($tasks, $callback = null) { $taskCallback = function () use (&$next) { call_user_func_array($next, func_get_args()); }; $done = function () use ($callback) { if ($callback) { call_user_func_array($callback, func_get_args()); } }; $next = function () use (&$tasks, $taskCallback, $done) { if (0 === count($tasks)) { call_user_func_array($done, func_get_args()); return; } ! $task = array_shift($tasks); $args = array_merge(func_get_args(), array($taskCallback)); call_user_func_array($task, $args); }; $next(); } }
Source: https://github.com/reactphp/async/blob/master/src/React/Async/Util.php#L81
EN ASYNCHRONE PAS DE RETURN
// prototype synchronous function $cook = function($ingredient) use ($saucePan) { $saucePan-‐>fill($ingredient); $saucePan-‐>warm(5); return 'chaud devant'; }; !
!
$message = $cook(MirepoixFactory::create()); echo $message, "\n";
// prototype asynchronous function $cook = function($ingredient, $callback) use ($saucePan) { $saucePan-‐>fill($ingredient); $saucePan-‐>warm(5, function() { $callback('chaud devant'); }); }; !
$cook(MirepoixFactory::create(), function($message) { echo $message, "\n"; });
EN ASYNCHRONE PAS DE TRY/CATCH
$cook = function($ingredient, $callback) use ($saucePan) { $saucePan-‐>fill($ingredient); $saucePan-‐>warm(5, function() { $isSummer = in_array(date('m'), array(6, 7, 8)): if ($saucePan-‐>contains('Tomato') && !$isSummer) { throw new OutOfBoundsException('On ne fait de la bonne sauce tomate qu\'en été'); } $callback('chaud devant !'); }); }; !
try { $cook(MirepoixFactory::create(), function($message) { echo $message, "\n"; }); } catch (OutOfBoundsException $e) { echo "Echec de la recette\n"; }
$cook = function($ingredient, $callback) use ($saucePan) { $saucePan-‐>fill($ingredient); $saucePan-‐>warm(5, function() { $isSummer = in_array(date('m'), array(6, 7, 8)): if ($saucePan-‐>contains('Tomato') && !$isSummer) { return callback(new OutOfBoundsException('On ne fait de la bonne sauce tomate qu\'en ete')); } $callback(null, 'chaud devant !'); }); }; !
$cook(MirepoixFactory::create(), function($error, $message) { if ($error instanceOf OutOfBoundsException) { echo "Echec de la recette\n"; return; } echo $message, "\n"; });
DU CODE RÉUTILISABLE
ROBUSTE UN PLAT
CHAUD
VICTOIRE !
10/10 NOTE DES INVITÉS
IL N’Y A TOUJOURS
QU’UN SEUL CHEF
PROGRAMMATION NON
PARALLELE
PAS DE TEMPS PERDU A ATTENDRE LA RÉPONSE D’UN AUTRE
Operation CPU cycles L1 .................. 3 L2 .................. 14 RAM ................. 250 Disk ................ 41,000,000 Network ............. 240,000,000
LATENCE I/O
L1 cache reference ......................... 0.5 ns L2 cache reference ........................... 7 ns Main memory reference ...................... 100 ns Compress 1K bytes with Zippy ............. 3,000 ns = 3 µs Send 2K bytes over 1 Gbps network ....... 20,000 ns = 20 µs SSD random read ........................ 150,000 ns = 150 µs Read 1 MB sequentially from memory ..... 250,000 ns = 250 µs Round trip within same datacenter ...... 500,000 ns = 0.5 ms Read 1 MB sequentially from SSD* ..... 1,000,000 ns = 1 ms Disk seek ........................... 10,000,000 ns = 10 ms Read 1 MB sequentially from disk .... 20,000,000 ns = 20 ms Send packet CA-‐>Netherlands-‐>CA .... 150,000,000 ns = 150 ms
Source: http://www.cs.cornell.edu/projects/ladis2009/talks/dean-keynote-ladis2009.pdf
I/O = ATTENTE ETABLISSEMENT CONNEXION SÉCURISÉE
RÉCEPTION REQUÊTE HTTP REQUÊTE BASE DE DONNÉES
LECTURE VALEUR DANS MEMCACHE LECTURE DE FICHIER SUR DISQUE
APPEL À UNE API REST ENVOI MESSAGE À UN AMQP
ENVOI RÉPONSE HTTP
90% DU TEMPS DE RÉPONSE
D’UNE REQUÊTE HTTP EST PASSÉ À ATTENDRE UNE I/O
GET /favicon.ico
Routing Lancement de l’ordre de chargement du fichier
Déplacement de la tête de lecture Transfert des données du disque en mémoire
Envoi de la réponse HTTPTraitement
Attente Fin de la requête
Construction de la réponse HTTP
Process serveur
UN SERVEUR WEB PASSE SON TEMPS A SE TOURNER
LES POUCES
POUR MIEUX UTILISER LE CPU
ON MULTIPLIE LES PROCESS LES THREADS
ET DONC LA CONSO MÉMOIRE
MaxClients 50
UN AUTRE MONDE
EST POSSIBLE
GET /favicon.ico
Traitement
Attente
Process serveur
I/O disque asynchrone
Transfert des données du disque en mémoire
Envoi de la réponse HTTP
I/O réseau asynchrone
LIBEL EVENT LOOP
LIBEIO ASYNCHRONOUS I/O
LIBUV MULTI-PLATFORM ABSTRACTION LAYER
GET /favicon.icoGET /js/jquery.jsGET /css/main.css
COMMENT FAIRE DES I/O ASYNCHRONES
EN PHP ?
Event-‐driven, non-‐blocking I/O with PHP.
PHP & PROCESS PERSISTENTS
PAS BON MÉNAGE
PHP N’A PAS DE FONCTIONS D’I/O DISQUE ASYNCHRONES
PECL/LIBIO
TANT QU’À INSTALLER UN BINAIRE
AUTANT EN PRENDRE UN
STABLE POPULAIRE
TANT QU’À REPOSER SUR UNE EVENT LOOP
AUTANT UTILISER UN LANGAGE
ÉVÈNEMENTIEL
TANT QU’À ABUSER DES FONCTIONS ANONYMES
AUTANT UTILISER UN LANGAGE
FONCTIONNEL
var fs = require('fs'); fs.unlink('/tmp/hello', function (err) { if (err) throw err; console.log('successfully deleted /tmp/hello'); }); // more code console.log('deletion script');
plate = new Plate(); pastaPan = new AsynchronousPan(eventLoop); water = new Water(); pastaPan.fill(water); console.log('pastaPan: Starting to boil water'); pastaPan.warm(duration = 10, function() { console.log('pastaPan: Water is boiling'); pastaPan.fill(new Spaghetti()); console.log('pastaPan: Starting to boil spaghetti'); pastaPan.warm(duration = 8, function() { console.log('pastaPan: Spaghetti is ready'); pastaPan.remove(water); plate.addContentsOf(pastaPan); }); });
$plate = new Plate(); $pastaPan = new AsynchronousPan($eventLoop); $water = new Water(); $pastaPan-‐>fill($water); echo "pastaPan: Starting to boil water\n"; $pastaPan-‐>warm($duration = 10, function() use ($pastaPan, $plate, $water) { echo "pastaPan: Water is boiling\n"; $pastaPan-‐>fill(new Spaghetti()); echo "pastaPan: Starting to boil spaghetti\n"; $pastaPan-‐>warm($duration = 8, function() use ($pastaPan, $plate, $water) { echo "pastaPan: Spaghetti is ready\n"; $pastaPan-‐>remove($water); $plate-‐>addContentsOf($pastaPan); }); });
I/O ASYNCHRONES UN SEUL CHEF
DES CALLBACKS PLUS DE CONCURRENCE
MOINS DE CONSOL MÉMOIRE PAS POSSIBLE EN PHP, NATIF EN NODE.JS
PAS DE RETURN, PAS DE TRY/CATCH
DES PÂTES CHAUDES
MERCI
François Zaninotto - @francoisz - marmelab.com github.com/fzaninotto - github.com/marmelab
top related