Le livre Multithreading for Visual Effects
Par Narann le dimanche, 24 août 2014, 19:13 - Mes coups de coeur - Lien permanent
En code, rien ne vaut un bon bouquin. Vous entendrez souvent ce conseil de la part de développeurs expérimentés.
Aujourd'hui je vous propose un billet sur le livre Multithreading for Visual Effects.
Ce livre a été publié après la session de cours Multithreading and VFX au Siggraph 2013 où un certain nombre de studios présentaient comment ils avaient utilisés le multithreading dans leur outils. Les papers étaient suffisamment intéressant pour qu'ils décident de les rassembler en un livre.
Dans la mesure ou ce type de livre fait parti du marche de niche que sont les VFX, parlons en! :enerve:
Chapitre 1: Introduction et vue d'ensemble (par James Reinders)
Le prologue est une ode au multithreading (Sans blagues :siffle: ). James Reinders est ingénieur pour Intel et on peut dire qu'il connait son boulot!
J'ai pas mal apprécié comment les multiples facettes du multithreading sont présentées (multi tache vs multi thread, vectorisation, etc...) ainsi que les explications techniques sur comment chacune d'elle fonctionne, avec leurs avantages et inconvénients.
Je me dois de parler de la manière dont TBB est présenté. TBB est généralement considéré comme une (la?) librairie de choix pour des logiciels multithreadés (Presque tous les logiciels du livre l'utilisent). Et de ce que j'ai pu lire de la documentation, c'est mérité. Pour être tout à fait honnête, TBB me donne réellement envie de maitriser les templates C++ et je comprends parfaitement qu'on puisse se tourner vers TBB rien que pour ça.
Mais je trouve dommage que James n'ai pas passé autant de temps et d'amour à présenter les alternatives. Je sais bien, c'est un bonhomme d'Intel mais clairement, TBB reçoit tellement d'éloge dans ce chapitre (et tout au long du livre) qu'il en occulte complètement les autres librairies (qui sont "présentées" mais pas de manière aussi clair que TBB).
Bref, vous comprenez assez vite le message: TBB cay bon, mangez-en! :banaeyouhou:
Chapitre 2: Houdini: Multithreader un logiciel existant (par Jeff Lait)
Bon nombre des informations qu'on peut trouver ici et la, y compris dans les livres (celui ci inclus), sont très académiques. Elles expliquent comment vous devriez écrire du code.
Ce chapitre est très intéressant car il prend l'approche inverse: Comment convertir du vieux code en un code scalable (qui se parallélise efficacement). Jeff présente différents problèmes concrets, la ou les les méthodes qu'ils ont choisis pour les résoudre, ce qu'ils ont perdus au passage et ce qu'il ont pu sauver.
Ce que j'adore dans ce chapitre est à quel point il est anti-académique. Certains code d’exemples m'ont vraiment fait réagir: "Je pourrais faire ça... Ça serait bizarre mais ça marcherait plutôt pas mal!". Le meilleur exemple est la façon dont Houdini gère la latence de création des threads (car créer un thread est assez long) quand la quantité de données a calculer ne vaux pas le temps de création des threads. Accrochez vous: If data_count < 5000 : compute in the main thread! :IFuckTheWorld:
Vous ne trouverez jamais ce genre de "solutions" dans un livre académique mais vous ne trouverez jamais plus efficace que ça sans avoir à modifier le code à milles endroits! :D
Chaque exemple est intéressant (bien que souvent très spécifique). Les lires entraine votre cerveaux sur pourquoi vous devriez faire les choses de tel ou tel façon et comment réagir quand vous voyez un truc qui semble louche.
Je dois avouer que j'avais lu le paper original de Side Effect après le Siggraph 2013. J'ai d'ailleurs remarqué qu'un petit travail de réorganisation a été fait et certains exemples m'ont paru mieux expliqué. Je me rappel avoir été un peu perdu avec certains des exemples du paper original mais je n'ai eu aucun soucis à comprendre ceux du livre. J'en conclu donc qu'une bonne relecture a été faite (ou que je suis plus capable de lire du code qu'avant ce qui est fort probable).
J'ai été super frustré par la fin du chapitre qui arrive trop vite... On en veut encore! :grenadelauncher: Plus d'exemples, plus de problèmes tordu (mais concrets) à résoudre!
Si vous souhaitez en savoir plus sur comment Houdini fonctionne sous le capot, j'avais traduis un post très intéressant que je vous invite à lire.
Chapitre 3: Le système d’exécution de Presto: Architecturer pour le Multithreading (par George ElKoura)
L'interactivité est le mot clef de ce chapitre.
La première partie n'est pas particulièrement intéressante. George explique les ambitions et les contraintes techniques. C'est chouette pour la partie animation (temps de latence, interactivité, etc...), mais il passe définitivement trop de temps sur des choses que tu connais souvent déjà sans vraiment expliquer les implications spécifiques à Presto ("La vectorisation du code est importante car blabla", "Il faut organiser ces données comme ça sinon blabla"). Le coté très général des conseils fait presque redondance avec le premier chapitre. Il aurait peut être été préférable de développer tout ces points dans le premier chapitre (plutôt que de brosser TBB dans tous les sens :trollface: ) et laisser chacun des autres chapitre se focaliser sur l’implémentation.
Cela dit, la second partie est vraiment superbe! Vous y trouverez pleins de petites choses qu'il faut avoir en tête si vous souhaitez développer pour des logiciel d'animation. La gestion des threads dans Presto (tout particulièrement la manière dont le graph d'evaluation est compilé puis exécuté) m'a semblé vraiment moderne, bien pensé et concret. Par concret j'entends qu'on est pas dans des choses génériques mais vraiment spécifiques. La gestion du temps (cache des géométries), la gestion de l'interface utilisateur avec une anticipation des valeurs modifié pour mieux préparer les "batchs" de thread (si un utilisateur modifie les contrôleurs d'un personnage, on peut vraisemblablement penser qu'il ne modifiera pas le cache des autres, inutile d’arrêter leurs threads car un thread coute cher à lancer rappelez vous). Chaque détail permettant d'éviter des ralentissements et d'augmenter le "rendement" des graphs est présenté.
Tout ceci est très clairement expliqué même si ça manque un peu de détails à mon gout (j'aime bien avoir des explications de cas un peu tordu).
Ils finissent avec un petit exemple de code (2 pages) sur comment multithreader un déformer géométrique simple. C'est un exemple intéressant car il présente un cas assez général mais qu'on peu rencontrer souvent en prod quand on est rigger. Vous vous en doutez, la solution est tout aussi "général".
Si vous souhaitez avoir un survole de comment Presto fonctionne sous le capot il y a de fortes chances que vous appreciez ce chapitre. Le mot "survol" est le bon terme car George ne se focalise que sur l'aspect multithreading (c'est le titre du livre donc difficile de le lui reprocher...) mais il parle souvent de choses implicites qu'il est difficile de cerner si on n'est pas habitué a Presto (j'imagine). A la fin du chapitre, on ne sais pas vraiment a quoi ressemble un rig dans Presto (relation entre les nodes et la UI, les nodes les plus bizarres/spéciaux/gros, etc...). Cette "abstraction" de certains détails techniques est souvent frustrante. :injures:
J'ai également ressenti une sorte de "tout va pour le mieux dans le meilleur des mondes". Comme si il n'y avait jamais eu de complication, aucun souci spécifique au multithreading (qui est pourtant un techno assez jeune en terme de paradigme), aucun "on a pensé faire ça car blabla mais on c'est rabattu sur ça car blabla". Sincèrement, en développement il y a toujours des moments ou la vision "abstraite" des concepts (vision nécessaire à l’architecture général d'un logiciel, surtout dans les premières années) se mange les cas non prévus de la réalité dans les dents. Des moments ou un juste milieu, un moindre mal est nécessaire. C'est d'autant plus flagrant quand on compare avec le chapitre suivant.
Chapitre 4: LibEE: Évaluation en Parallèle des Rigs des Personnages (par Martin Watt)
Celui là m'a littéralement surpris! Il se lit comme une histoire!
J’avoue que les premières page ne m'ont pas particulièrement fait sauté au plafond . LibEE est présenté comme un graph de dépendance (Dependency Graph ou DG) multithreadé... On pourrais presque lire entre les lignes "Le DG de Maya est totalement à l'Ouest alors on l'a réécrit". Le problème c'est qu'un graph de dépendance "a la Maya" ne rime pas forcément avec performance, surtout dans le cas d'un rig. Le fait que chaque node soit exécuté indépendamment puis passe son résultat au node suivant via des attributs, le tout badigeonné d'une dépendance inter-attributs (ou certains attributs d'entrés peuvent modifier certains des attributs de sortie mais pas tous) et vous obtenez un graph qui passe plus de temps a évaluer les dépendances pour savoir quel node exécuter qu’exécuter réellement les calculs des nodes (avouez que c'est con quand le node ne fait au final qu'une simple addition :siffle: ). Et c'est vrai pour Maya comme pour n'importe quel autre implémentation qui suit cette approche. La vitesse d’exécution d'un graph dépend souvent énormément de son "allure" et il n'est pas rare qu'un graph un peu tordu plombe drastiquement les performances. J'ai tendance à préférer l'approche compilé d'un VEX ou d'un Presto mais en lisant le reste du chapitre, je ne suis pas sur que la RnD de Dreamworks ait pu disposer de toute la latitude (et des ressources?) nécessaires.
Après, je ne suis pas dev a Dreamworks et il y a peut être un autre paquet de bonnes raisons... :dentcasse:
Mais bien que l'équipe de Dreamworks semble avoir fait un travail colossale pour contourner les limitations qu'implique ce type de DG, plusieurs sections tendrons a confirmer mes (humbles) hypothèses.
Et quand je parle de travail colossale je ne suis pas en dessous de la vérité. C'est la raison principal pour laquelle ce chapitre est exceptionnel: Vous "sentez" la production. Vous savez, les deadlines, les riggers qui râle sur les nouveaux outils "car-le-vieu-DG-de-Maya-il-aytay-mieu". Je suis un gros fan des quelques exemples de discussions entre les différentes personnes impliquées dans le projet (avec une petite copie d'un mail bien senti lol). Il explique aussi comment ils on eu à apprendre à leurs riggers à penser multithreadé (chose qui n'était pas prévu au départ). La présentation des différents outils de bench fourni au riggers pour qu'ils puissent trouver eu même les critical paths de leur code et qu'ils les "coupe" ou "blend" la logique en un seul node (un autre argument pour se tenir loin d'un DG IMHO). LibEE semble avoir suivi la situation typique d'une prod de VFX/anim ou les outils sont développé et utilisé en même temps (à l'inverse de Presto ou l'équipe semble avoir travaillé loin de la production, même si ce n'est pas clairement dit, avant de relâcher leurs outils). D'un point de vu développement, gérer une tel situation est un art en soit dans la mesure ou les objectifs/contraintes changent au fil du temps et ce chapitre explique (un petit peu) comment gérer ça.
Si vous êtes développeur et que vous pensez que les studios de VFX échouent car ils ne sont pas assez carré, passez ce chapitre, vous allez détester car c'est vraiment des situations de terrain, du vécu. Les autres, si vous voulez bosser en VFX il y a vraiment de quoi vous inspirer (ou vous faire fuir, au choix!). Et les derniers, ceux qui bossent déjà en VFX habitué des patchs-en-prod-parce-que-cay-super-plus-urgent-que-les-autres-urgences, vous allez vous sentir moins seul! :baffed:
Le second point concerne les expériences et tests qu'ils ont effectués en prod ou de manière plus formel. J’étais déçu que le chapitre Presto ne mentionne que quelques points techniques souvent assez connus (effet NUMA, bande passante de la RAM, Hyperthreading, etc...) sans fournir aucun graph ou expliquer en quoi ces problèmes impactaient directement Presto. Ici, non seulement les gars de Dreamworks expliquent ce qui les amènes à faire tel ou tel test mais ils fournissent une quantité de détails impressionnant qui explique, de manière très clair, comment et pourquoi tel ou tel effet apparait. Et on peut dire qu'ils ont poussé le bouchon très loin. Une fois encore, tout ces exemples ne sont pas académiques, ce sont de vrais use cases décortiqués et analysés très clairement. Certains tests expliquent même pourquoi un effet attendu n'est, en pratique, pas un problème.
C'est donc un chapitre super chouette! Un des plus intéressant du livre. J'aurais aimé que le chapitre de Presto sois aussi poussé.
Chapitre 5: Fluides: Simulation sur le CPU (par Ronald D. Henderson)
lol celui la est massif. Si vous aimez le code et les maths vous allez être servi! :aupoil:
Il y a quelques codes de comparaison entre OpenMP et TBB (avec du lamdbas C++11, première fois que j'en vois! :gne2: ). Si vous n'êtes pas à l'aise avec le C++, ça risque d'être assez dure (et un peu déroutant).
Pour ce qui est des maths, je dois admettre que j'ai pas mal rigolé chaque fois que je me disais "tin mais je capte rien! :baffed: " après quelques équations (et leur magistrales explications).
Dans les autres chapitres du livre, beaucoup d'explications se font sous la forme de phrases assez basiques tel que: "Nous mesurons l’efficacité des cores en divisant le temps de calcul par le nombre de core" (enfin un truc compréhensible quoi...). Dans ce chapitre, de tel phrases serait plutôt présenté sous la forme d'une petite équation de math bien senti suivi d'un: "Ou p est la dérivé relationnel d'infrastructure de la logarithme du nombre de core et n est le temps de calcul exprimé en second (ISU-CIPM 1967)". Bien sûr j'exagère mais dans la mesure ou je ne suis pas habitué aux équations mathématiques c'est exactement l'effet que ça m'a fait à la première équation. Et j'ai d'autant plus rie quand, après m’être bien trituré pour déchiffrer et comprendre tout ça j'ai réalisé qu'on pouvait résumer ça en une phrase... Haha! :smileFou:
Mais j'ai eu l’opportunité de travailler avec des matheux (ou des gens qui apprécient les maths d'une manière général) et ils ont cette tendance à jouer avec les équations mathématiques partout, y compris pour des trucs anodins (comme le temps moyen que passe les gens à la cantines en fonction des menus proposé, ou bien le temps de retards moyen des réunions suivant les dates de livraison) et ça m'a rappelé tout ces moments assez drôles. :jdicajdirien:
J'ai également apprécié la petite présentation de OpenVDB. J'ai souvent entendu parlé de OpenVDB mais je n'ai jamais compris comment il marchait sous le capot. Cette petite présentation est parfaite! Il y a un bon nombre de librairies open source utilisé dans en CGI mais elles manques souvent de présentations faciles à comprendre alors qu'il y a beaucoup de graphistes qui, bien qu'il ne soit pas développeurs, sont assez techniques et pourrais bénéficier d'une meilleur compréhension de ces librairies dans leur travail au quotidien. Donc les VFX boys, cette présentation est pour vous!
Pour le reste, c'était malheureusement trop mathématiques pour moi. Si vous trouvez difficile de lire des équations mathématiques, vous serez perdu dans ce chapitre. Ça été le cas en ce qui me concerne et je ne peut pas vraiment dire si ce qui est présenté dans le reste du chapitre est intéressant ou classique. En fait, je pense que vous pourriez vous en tirer si vous êtes un temps soit peu habitué au solver de fluides/liquides.
Chapitre 6: Bullet Physics: Simulation avec OpenCL (par Erwin Coumans).
Un chapitre bien sympa auquel je ne m'attendais pas vraiment. :bravo:
Je ne suis pas spécialement intéressé par la physique (au sens mécanique) mais je dois admettre qu'Erwin a fait un gros travail pour décortiquer et présenter sa librairie. C'est un des deux chapitre à aborder OpenCL et le calcul GPU (le second étant le chapitre sur OpenSubdiv) mais c'est surement celui qui le fait le mieux. Bien que l'aperçu soit assez rapide, il décrit très bien les différentes étapes de créations des batchs de calcul pour le GPU et revient dessus à chacune des (très) nombreuses étape de détection de collision/calcul de répulsion. C'est d’ailleurs un très bon aperçu de comment fonctionne un moteur physique ce qui est quelque chose de très intéressant à savoir quand bon bosse en VFX.
Il y a aussi beaucoup de petits schémas qui sont tous très clair, chacun ne se focalisant que sur un seul problème. J'ai tendance à préférer cette approche plutôt que peu de gros schémas qui essaient d’expliquer milles choses.
Il fini sur des choses plus avancés.
Surement le chapitre le plus clair et plaisant à lire. :marioCours:
Chapitre 7: OpenSubdiv: Interoperating GPU Compute and Drawing (par Manuel Kraemer)
J'attendais ce chapitre avec impatience et je n'ai pas été décu. Très chouette, il est composé de deux parties.
La première partie explique succinctement comment la subdivision itérative Catmull Clark fonctionne, sont histoire, pourquoi une "subdivision universelle" était nécessaire, et comment ils ont choisi de la multithreader. Si vous n'êtes pas familier avec la structure de donnée géométrique en arc dur (dite hard edge data structure) vous allez surement être un peu paumé. Manuel donne quelques benchs timides et conclu que le design itératif de la subdivision Catmull Clark, bien que nécessaire pour le rendu non temps réelle (off-line rendering), ne s'adapte pas très bien au contraintes du temps réel. J'ai été assez surpris qu'il n'aborde pas du tout comment ils organisent leur données dans la mémoire pour améliorer le cache et bénéficier d'une meilleur vectorisation. Je ne suis pas un expert, mais après avoir lu les chapitres précédents, je me demande sérieusement si il n'y avait pas d'autres axes de recherche pour améliorer les performances. Je suppose que Manuel n'en parle pas car les gains n'était pas substantiels. Qui sait... Mais pour avoir suivi le développement d'OpenSubdiv de loin, j'ai l'impression qu'ils ont rapidement sauté sur le GPU. Comme si c'était quelque chose de plus important pour le studio (Presto semble s'appuyer dessus) et que le code CPU serait "suffisant". Les nouvelles versions de Renderman s'appuyant massivement sur du raytracing (et donc, une subdivision CPU), j'en viens à me demander si Renderman s'appuie réellement sur OpenSubdiv.
La seconde partie présente comment OpenSubdiv utilise le GPU (et OpenGL) pour permettre un rendu d'une subdivision temps réelle en utilisant les shaders de tesselation. Une fois encore, j'ai bien apprécié la présentation du fonctionnement d'un GPU, leurs différences (contraintes surtout) par rapport à un CPU et comment organiser et préparer ces batchs pour en bénéficier. Les Edges Creases (un "truc" très présent dans OpenSubdiv) semble avoir été un problème (tous comme la gestion des triangles/quads/polys le sont dans une algo de subdivision) mais je trouve qu'ils ont été assez ingénieux dans la façon de le gérer: Ils ont séparé chaque set de géométrie pour lui appliquer différents algorithmes. Dans la mesure ou la topologie ne change pas, le processus de séparation est fait une seul fois et le GPU ne fait que recalculer les position des points avec le shader de tesselation associé à son "set de topologie". Une approche intéressante et pragmatique pour appliquer différents algorithmes de subdivision à une seule géométrie (bien que ça semble pas mal compliquer le code).
Je dérive un peu pour étaler mon point de vu: Je me demande vraiment si il était pertinent de garder les Creases dans OpenSubdiv. Le concept s’accommode parfaitement avec le principe de surface limite cher aux rendu REYES. Mais:
- Cette approche ne semble pas particulièrement adapté au ray tracer qui nécessite une vrai géométrie à raycaster.
- Des Creases un peu fort nécessite d'augmenter pas mal le niveau de subdivision pour avoir un rendu correct là où un simple bevel ou une double edge aurait fait la blague (mais complexifié la géométrie c'est vrai). Encore une fois, quand on cherche à diminuer la taille des géométrie pour les faire rentrer dans la mémoire, augmenter drastiquement la subdivision pour récupérer l'équivalent d'un bevel me semble un peu dommage.
- Il semble complexifier et fragmenter pas mal de partie du code.
- Code fragmenté? Structure de donnée fragmenté? Quel impact sur la mémoire (accès et taille)?
Une des dernière alternative que je vois au coté gourmand (en géométrie) du Creases si on ne peut pas subdiviser à tout va c'est de modifier la normale au niveau de l'edge (pouah! Le temps de calcul du truc). Mais cela faisant, vous devez prendre l'information en compte au niveau du shading et ce n'est pas ce que OpenSubdiv est supposé faire (sans compter les petits soucis qu'on pourrait avoir pour "blender" les normales). Mais il faut avouer que ça marcherait pas mal!
Bref, je me demande si les Creases n'était pas un truc dont Pixar (et seulement Pixar) a besoin. Ça pourrait expliquer pourquoi un concept aussi "exotique" se retrouve dans OpenSubdiv.
Notez que la prochaine version de OpenSubdiv arrive et semble avoir bénéficier des contributions et feedbacks d'autres studios (Dreamworks?). On dirait qu'ils ont isolés la partie algorithme de la partie structure de donnée ce qui est clairement une bonne chose car devoir passer sa géométrie dans une structure de donnée externe pour la récupérer ensuite est vraiment contre productif. Je suis content de voir qu'OpenSubdiv semble aller dans la bonne direction. C'est d'autant plus important qu'il devient une partie intégrante de Maya. :)
Conclusion
Et bien... Un très bon livre! :D
Il y a une inconsistance entre les chapitres. Certains vont vraiment au bout des choses (LibEE, Fluids, Bullets), et d'autres ne font qu'effleurer la surface (Houdini, mais surtout Presto).
Donc ce livre est exactement ce qu'il prétend être: Multithreading for Visual Effects. Et il ne se focalise que là dessus, laissant certains points, qui mériteraient aussi d’être abordé, de coté. Mais les logiciels choisis son tellement intéressants (Sérieusement: Side Effect, Pixar, Dreamworks!) que ça en est frustrant!
Le livre est super classe (hard cover! :laClasse: ) bien que le prix me semble un chouilla élevé (hard cover? :ideenoire: ). Mais il s'agit d'un domaine de niche donc on va pas cracher dans la soupe (vous en connaissez vous des livres à lire dans votre canap' qui parle des logiciels les plus cools du monde? :sauteJoie: ). Et puis faut bien l'avouer, la plupart du temps c'est votre studio qui va l'acheter... :P
Est ce qu'il vaut le coup? Si vous avez à coder des softwares lié au VFX, n'hésitez pas une seconde! Vous n'y trouverez pas de ressources "clef en main" mais c'est un peu comme si vous aviez une discussion avec des gars brillants qui partage leur expériences autour de leur logiciel. Pas sur que vous puissiez trouver de tels infos ailleurs.
Si vous êtes un graphiste un peu technique, je ne vous recommanderais pas forcément de le prendre. Il est vraiment tourné autour du développement, vous n'y trouverez pas de trucs et astuces en multithreading pour améliorer votre travail. Cela dit, je vous encourage à le piquer à votre RnD ( :IFuckTheWorld: ) et à le feuilleter. Le début de chacun des chapitre est souvent assez clair.
Après avoir lu ce livre, je me dis que je pourrais bien payer pour d'autres du même genre qui passe en revu et en profondeur (un truc advanced j'entends) l'architecture des logiciels de VFX (Houdini, Presto, Maya, Nuke). Comment les données transitent, a quoi ressemble les structures de données utilisées, comment un type spécifique d'opération s’exécute, etc... Et pas juste un aperçu. Les docs des logiciels sont souvent trop timide sur le sujet.
Avant, j'étais incapable de lire et comprendre de tels codes. Je tentais de déduire le fonctionnement et la logique sous-jacente des logiciels que j'utilisais par dichotomie. Maintenant que je sais lire du code, autant y aller et voir directement le code des dis logiciels! :D
Malheureusement, je suis passé à coté de pas mal d'informations intéressantes à cause de mon incapacité à lire des équations de mathématiques. Je veux dire, je sais ce qu'est une matrice 4x4, les raisons pour lesquelles ont pourrait les utiliser, je sais ce qu'est un dot product, un produit en croix et comment je peut les utiliser pour obtenir un effet souhaité mais c'est à peut près tout... Je m’énervais contre moi même de n'être pas capable de déchiffrer les équations (qui au final ne semblaient pas super compliqué :nervous: ). Idem pour certains morceaux de code que j'ai eu du mal a comprendre :gne: . Je suis passé à coté de pas mal de choses dans le chapitre des Fluids.... :triste:
Ce qui est très intéressant c'est d'avoir rassemblé des cours d'une session Siggraph sur un sujet donnée et d'en faire un livre. J'adore l'idée car je passe des heures derrière un ordi et j'apprécie de pouvoir lire ce genre de chose sur du papier plutôt que mon écran de PC (lire un PDF me fatigue assez vite).
Je n'ai aucune idée du succès du livre mais j'ai entendu pas mal de gens intéressé donc on pourrait imaginé... Je ne sais pas... "Alembic/OpenVDB/OpenEXR under the hood and use cases" avec un maximum d'explications sur les fondations et l'infrastructure, jusqu'aux points clefs du code. :smileFou:
J'espère que vous avez apprécié cette humble review! N'hésitez pas à commentez si vous avez vous aussi lu ce livre. Je serais curieux de savoir ce que vous en pensez. :sourit:
Dorian
Commentaires
Merci pour ce billet très intéressant Dorian.
Olivier.
Pas de quoi ! :D