Résoudre les ralentissements réseaux dûs a Nuke, After Effect et autres logiciels du genre
Par Narann le dimanche, 14 octobre 2012, 12:46 - Script et code - Lien permanent
Nuke, After Effect et surement d'autres, peuvent se révéler très "gourmand" en termes d’accès disque. A tel point qu'ils peuvent rapidement mettre des disques réseaux à genoux si plusieurs stations font des rendus.
Dans ce billet je vous propose une explication rapide du comment du pourquoi ainsi qu'une petite classe Python qui fait office de prototype pour solutionner le probleme.
C'est quoi le problème?
Rien de mieux qu'un cas concret pour énoncer la chose:
Sur la fin de Lorax, il y avait un grand nombre de « compeurs » (compositeurs) qui rendaient leurs images en même temps et les estimations affichées par Nuke étaient parfois surprenantes (3h-4h restantes pour des compos simples). En surveillant un fichier en train de s’écrire, je me suis rendu compte qu’il montait très lentement. Il était à 100ko, puis passait à 300ko, puis au bout de dix secondes passait à 400ko, etc. J’en ai conclu que le réseau était surchargé… :reflexionIntense:
Je me suis rappelé que nous avions eu le même souci sur Les Contes De La Nuit. Notre infrastructure était, certes, beaucoup plus modeste mais 3 After Effect en rendu plombaient l’intégralité du réseau. La solution trouvée à ce moment la fut de rendre en local puis de copier les fichiers une fois finis. Les effets furent immédiats : Le réseau ne ramait plus. :sauteJoie:
J’ai donc tenté de rendre le compo d’un graphiste en local pour voir si cela diminuait le problème. Les plus gros fichiers à lires étant les fichiers sources, car plus nombreux, et l’image rendu étant relativement légère, je ne me faisais pas trop d’illusions. Et pourtant, une fois de plus, le constat est sans appel : Le rendu se finissait en 10-15 minutes (je ne rigole pas) au lieu de 3-4h… :laClasse:
Je me suis dit que c’était l’écriture qui devait poser problème. Pourtant, quand je copiais la séquence d’image fraichement calculée, la copie était très rapide. Je suis donc passé voir les graphistes les uns après les autres et en un gros après midi, le réseau était complètement désengorgé et les rendus passèrent tous.
Mais ce n’était pas une solution. Il fallait comprendre pourquoi Nuke n’arrivait pas à écrire ces images rapidement sur le réseau. Après avoir posé la question sur la mailing list nuke-users (ou j’ai pu constater que je n’étais pas seul mais que The Foundry semblait ne rien pouvoir faire), j’ai commencé à « profiler » Nuke pour savoir comment il s’y prenait (strace et inotifywatch sont tes amis :dentcasse: ).
Les conclusions semblent évidentes, mais il est toujours bon de vérifier par la pratique ce qu’on soupçonne :
Sous Nuke, si vous écrivez un EXR compressé zip 1 line, en 1920 × 1080, Nuke fera un peu moins de 900 (et des brouettes) accès en écriture sur le fichier. Si vous êtes en zip 16 lines, il fera environs 70 accès (1080/16). Et en non compressé c’est réellement 1080 accès. :trollface:
Dans les faits, compresser en zip 16 line n’est pas super efficace si les images doivent être lues par Nuke. Et suivant l’infrastructure de votre réseau, écrire ligne à ligne peut le mettre complètement à plat. Il est difficile d’expliquer comment finalement peu de rendus Nuke sont nécessaires pour plomber un réseau, même si ce dernier est costaud. J’ai la sensation que c’est lié au multithreading : Nuke lit des images (souvent beaucoup en même temps) sur le réseau pendant qu’il y écrit.
La solution la plus évidente reste donc d’écrire l’image (les images) rendu sur le disque local et de la copier en une fois (un seul accès) sur le disque réseau. Si vous ne disposez pas des ressources techniques nécessaires ou bien tout simplement de temps, c’est l’approche la plus simple mais sur des projets plus conséquents ça peut rapidement devenir rébarbatif et (ne l’oublions pas) source d’erreurs.
Il y a plusieurs solutions et je m’étais penché sur un prototype que je trouvais intéressant car facile à mettre en place.
Le principe
- Vous lancez un thread python qui va surveiller un dossier.
- Toutes les trois secondes le thread va lister les fichiers présents dans le dossier et voir si leur nom correspond à une expression régulière (le « modèle » de votre nom de fichier).
- Si le fichier semble être un fichier que vous souhaitez déplacer une fois fini, il cherche ce même fichier avec ".finished" (exemple : "toto.001.exr.finished").
- Si ce fichier existe, il déplace le fichier d’origine et supprime son équivalent en ".finished" puis relance la boucle principale.
- Une fois le rendu fini, vous donner l’ordre au thread de s’arrêter.
Comme vous pouvez le voir, ce système nécessite que vous créiez un fichier ".finished" à chaque fois qu’une image est finie. C’est dû au fait qu’il est impossible pour le thread de savoir à quel moment une image est fini de calculer et complète (et ducoup, quand il peut le lancer la copie). La création de ce ".finished" peut être géré de mille façons différentes (Pour Maya, un "Post render frame" fait l’affaire) donc je ne rentre pas dans les détails. :siffle:
Le code
Voici le code brut de décoffrage :
[python] import os, threading, re, time class MoverThread( threading.Thread ) : def __init__( self, dirTocheck, dirToMoveIn, patternToCheck, force=False ) : threading.Thread.__init__( self ) self._terminate = False self.dirTocheck = dirTocheck self.dirToMoveIn = dirToMoveIn self.force = force # regex pattern self.patternToCheck = patternToCheck self.rePattern = re.compile( patternToCheck ) # sanity check if not os.path.isdir(self.dirTocheck) : raise Exception( "The given directory (dirTocheck) is not a valid directory -> %s" % self.dirTocheck ) if not os.path.isdir(self.dirToMoveIn) : raise Exception( "The given directory (dirToMoveIn) is not a valid directory -> %s" % self.dirToMoveIn ) def run( self ) : filesNotMoved = [] while not self._terminate : # we wait 3 seconds before do anything time.sleep( 3 ) # for every "entry" (file or folder) in the folder we check it have the good pattern. If it has, we check for a ".finished" file for entry in os.listdir( self.dirTocheck ) : # check the current entry is "compliant" with the given regex if not self.rePattern.match( entry ) : continue srcFilePath = os.path.join( self.dirTocheck, entry ) dstFilePath = os.path.join( self.dirToMoveIn, entry ) if os.path.isfile( srcFilePath+".finished" ) : # destination file aready exist? if os.path.isfile( dstFilePath ) and not self.force: # don't add the entry if it is already in the list if not entry in filesNotMoved : filesNotMoved.append( entry ) continue # move the file to it new location os.rename( srcFilePath, dstFilePath ) os.remove( srcFilePath+".finished" ) print "File %s moved to %s" % ( entry, self.dirToMoveIn ) break # restart the while loop to avoid to continue the list of file we maybe have removed: ".finished" print "Terminated!" for fileNotMoved in filesNotMoved : print "Already exists: Can't move %s to %s" % ( fileNotMoved, self.dirToMoveIn ) def join( self ) : self._terminate = True threading.Thread.join( self )
Comme vous pouvez le voir (ou pas), tout ce passe dans un thread.
Ça s’utilise comme ça :
[python] import waitFinishAndCopy myMoverThread = waitFinishAndCopy.MoverThread("/a/local/path/", "/a/network/path/", "^toto\.[0-9]{4}\.exr$") myMoverThread.start() # start rendering, do rendering, end rendering. myMoverThread.join()
Et voila !
Conclusion
J’espère que ce modeste prototype vous inspirera si vous rencontrez des lenteurs sur votre réseau. :mechantCrash:
Je vous conseille de faire un peu de profilage réseau sur vos principales applications, surtout si elles sont utilisées par beaucoup de monde. Leur comportement est toujours intéressant (et parfois surprenant).
Passez une bonne journée !
Dorian
Commentaires
No english :(
Hi Tristan!
As it's a frequent problem I will find time to translate this one. ;)
EDIT: And there the English translation. :sourit:
:D
thanks for sharing
Voilà pourquoi je me suis fait depuis quelques années mon propre render manager pour After FX ... :o)
Son but est de lancer des aerender en ligne de commande en assignant chaque aerender à un processeur en particulier. Cela permet de ne pas utiliser la gestion (pas très efficace) des multicores dans AfterFX et d'avoir toujours des procs à 100 %.
Sous Windows, j'utilise pour ça psexec, qui depuis fait partie de Microsoft :
http://technet.microsoft.com/fr-fr/...
Cerise sur le gateau, on peu lancer des aerender à distance sur des machines qui se trouve sur le réseau (donc on peu utiliser toutes les machines du parc).
J'ai un bacth sur lequel je glisse mon *.aex, qui du coup le prends en argument, calcule la RAM restante, en divise le nombre par le nombre de proc et passe cette info à la ligne de commande de aerender.exe en tant que ram maximum à utiliser.
Ainsi, je ne swap jamais et mes 8 cores tournes à 100%. Lors de rendu de séquence d'images, je check la taille du fichier pour savoir si il est fini de calculer et ainsi je lance la copie sur le serveur distant ... ou alors je raccorde le tout à FFMPEG pour en produire un movie.
Je suis en train de coder une appli en QT pour pouvoir choisir le nombre de proc à utiliser sans changer les infos dans le Batch. :o)
C'est bon ça! :D
Par contre le soucis présenté ici c'est que si il y a des AE lisent ET écrivent sur le réseau, c'est la que ça plombe tout.
Ton outil écrit les images en local puis les copies dans le dossier de destination? Ou il les écrit directement dans le dossier sur le réseau?
Après, peut être qu'en effet c'est le multicore de AE (et potentiellement Nuke) qui écrit de manière peut être un peu barbare et plombe le réseau....
Au final, je n'ai jamais teste de rendre en monocore pour voir si le soucis était toujours la mais je prend bonne note! :)
Mon avis personnel sur la gestion du Multicore de After Effects est "trop aléatoire pour qu'on puisse lui faire confiance".
Il est efficace si on utilise énormément de pré-compositions car le contenu de chacune d'entre elle peut-être calculé en parallèle. Mais une fois que leur résultat doit être mixé avec les pré-compos supérieure dans la même compo ... ben là on retombe en monocore. Si tu n’utilises aucune pré-composition (ce qui est ridicule sur un gros projet, on est d'accord), alors tout le calcul sera monocore.
After FX ne traite pas une image par Bucket, comme le fait le module de compositing de Blender par exemple. Pas de Tile rendering ici.
Le moteur de rendu de After FX fonctionne par calque. Il superpose les deux calques les plus bas dans la pile, stock le résultat dans un buffer, puis il considère son contenu comme un nouveau calque, auquel il superpose le calcul supérieur et ainsi de suite. Par conséquent, le calcul se fait toujours avec seulement deux calques ... avec éventuellement la pondération d'un troisième lors de l'utilisation d'un "Track Matte".
Ce n'est donc pas sur le calcul d'une image qu'il faut penser optimisation Multi-core avec After FX ... mais calcul de plusieurs images indépendantes en même temps. "aerender" est pour cela la meilleure solution car on peut le scripter et l'intégrer dans un processus plus large.
psExec a pour avantage de pouvoir lancer des appli (aerender ou batch le contenant) à distance ... à travers le réseau. Mais il peut aussi te servir à commander des scripts locaux installer sur les machines, utilisant des commandes "start" qui permettent de définir la priorité des processus et l'affinité de ceux-ci sur un nombre limité de core (un petit "start /?" dans une fenêtre prompt/dos like te donnera toutes les commandes). Tu codes ainsi une appli qui s'occupe de lancer des aerender sur chaque proc, en activant l'option (uniquement valable pour le calcul en séquence d'images) permettant de ne calculer que les images manquantes. Là ... tu as vraiment 100% de tes procs à bloc ... tout le temps ! Bien évidemment, l'utilisation de la RAM est multiplié ... mais tu peux via le menu "Secret", demander à ce que la RAM soit vidée souvent afin d'éviter le Swapping.
aerender ne demandant pas de licence ... tu peux même l'installer sur les machines de la compta ... :o) Tu lances à distance avec psexec des calculs en priorité basse en limitant la RAM utilisée dans les paramètres de aerender ... et personne ne se rend compte que sa machine calcule pour toi . :o) J'ai personnellement usé et abusé de cette méthode pendant 4 ans, quand j'étais chef de studio ... :o) Crois-moi ... on gagne des jours et des jours de calculs. Potentiellement, tout le monde sert au calcul. Et quand les gens se casse pour bouffer ou dormir ... toute la puissance de leur machine est pour toi.
Pour le réseau, tu as plusieurs solutions, dont certaines sont parfois à utiliser en même temps :
- Tu peux calculer en local et lancer les transferts la nuit sur le serveur. C'est long ... mais pour ça, j'ai une petite méthode perso. Tu n'es pas sans savoir j'imagine que transférer plein de petits fichiers est plus long que d'en transférer un gros. Voilà ce que j'avais l'habitude de faire : Zipper les images (j'utilise maintenant le 7z qui est à mon sens bien plus efficace) ... et commencer le transfert du Zip ... alors que celui-ci n'est pas encore fini d'être crée ! Impossible via une copie simple me diras-tu. Oui, c'est vrai ... mais via un transfert FTP c'est possible, si le client réactualise le transfert en fonction du poids du fichier à chaque trame de réseau (la plupart des clients FTP le le font). Et tu peux même te payer le luxe de dézipper le fichier qui n'est pas encore fini d'arriver si tu es sous Linux. :o) Tu peux aussi créer un zip (ou 7z) directement sur la machine distante à partir des multiples fichier locaux. C'est BEAUCOUP plus rapide que de copier les fichiers un à un. :o)
- Tu peux effectuer un collect files en local des fichiers avant de lancer le calcul. Ainsi, n'auras que les transferts en sortie à gérer et pas les entrées. J'ai personnellement écrit mon propre collect files, intégrant plein de bidouilles pour accélérer les transferts ... de plus, celui de After FX plante lorsqu'on essaye de transférer des fichiers de plus de 4Go sur des transferts 32 bits. Donc ... on est jamais aussi bien servi que par soit-même. :o)
- Autre solution que j'ai mis en place à une époque ... mais c'est plus couteux : Chaque machine à deux cartes réseaux, dont les entrées/sorties passent par deux réseaux de switch différents. Ainsi, la lecture et l'écriture ne passe pas par les même tuyaux. :o) Un peu chiant ... mais efficace. Là encore, il faut coder soit même les transferts pour contrôler les flux sur deux serveurs différents.
- La façon dont After FX gère les séquences d'images sur le réseau est très étrange. Il ouvre le socket, charge l'image, ferme le socket et ainsi de suite pour toutes les images. Lorsque tu utilises un Movie (QT, AVI, etc ..), le socket reste ouvert et le flux de transfert est BEAUCOUP plus rapide.
J'ai fait une expérience il y a quelques années. Nous utilisions des stockshots de flammes/fumée/etc de chez Artbeats dans nos projets. Nous les avions acheté en séq d'images TGA (beurk). Je les avais converti en PNG pour limiter les transferts via le serveur de StockShot (home made avec une slackware). J'ai ensuite encapsulé ces mêmes images dans un Quicktime / Codec PNG. Donc des images strictement identiques ... mais encapsulées dans un seul fichier MOVIE.
En chargeant la séq d'images dans After FX ... la RAM preview (sans effet) était d'environ 1X le temps réel (25 FPS). En lisant le QT ... 12X le temps réel ! Pour les même images !!! Tu vois un peu l'avantage que ce type de conversion peu avoir.
Bien sur tu vas me dire ... oui mais à la boite il ne veulent pas travailler avec des MOVIES ... ils ne font que de la seq d'images. Et bien c'est pas grâve ... tu n'as cas mentir à la machine en mettant sur le serveur un FrameServer. Tu crées un faux fichier AVI par exemple, qui link vers la séquence. FFMPEG fait ça très bien, avec AVI Synth et le FS de DebugMode. Un petit exemple ici, où on utilise FFMPEG pour l'encodage dans Premiere Pro :
https://ffmpeg.org/trac/ffmpeg/wiki...
- Autre astuce : si tu fais ton propre système de gestion de rendu, tu peux lancer une copie pendant que l'image suivante calcule ... plutôt que d'avoir le cycle habituelle rendu/copie/rendu/copie. Là encore, la commande "start" permet de lancer des processus différents sur différents core ... en même temps. On peut d'ailleurs les appeler dans les scripts JSX de After FX. :o)
Voilà ... ce ne sont que quelques astuces par rapport à tout ce qu'il est possible de faire ... mais si ça peut t'aider. :o) Cela fait des années que je me dis qu'il faudrait que je publie une petit dossier sur la chose, ayant déjà largement creusée la question ... mais tu connais le problème. Le temps, mon ami ... le temps ... :)
Outch! Super intéressant ton billet. Beaucoup d'astuces que je ne connaissais pas ou de loin (FrameServer je l'avais oublie celui la! :D ).
Je vois que tu a bien potasse le truc, chapeau!
Merci beaucoup pour ce commentaire très instructif! :)
Je t'en prie ... c'est un plaisir ! :o)
Et merci à toi pour ton dossier initial ... :o)
Je n avais pas encore eu le temps de te dire merci, grâce à toi j ai réussi à sauver la serie sur laquelle je bossais. Merci pour tout tes billets et à très bientôt à mac guff pr que je puisse t offrir une peinte en remerciement :D
Héhé! Content que ça puisse aider, c'est le but!
Si en plus ça sauve des prods! ^^
Un truc qui peut aussi vraiment changer la donne selon les footages utilisés dans une compo Nuke.
C'est l'attribut "cached" d'un node... en particulier sur des footages exr multichannel avec plein de passes dedans, ça utilise plus de ram mais ça évite que nuke passe des plombes à boucler comme un fou-furieux pour évaluer le network de la comp parce qu'il accède aux fichiers sources en permanence dès qu'il a besoin d'une info.
Sur certaines compos j'ai eu des différences vraiment spectaculaires de temps de rendu.
Sinon super blog, c'est une mine d'information.
Oui, mais comme tu dis, ça dépend des footages. :D
L'attribut "cached" est a mon sens, pensé pour de l'interactif. Il ne fait "que" écrire une image d'un node (qui a priori ne va pas trop changer) en local pour gagner du temps lors d'une évaluation/d'un accès futur. C'est en fait plus tricky que ça car si tu bosse en zoom 50% (demi-résolution), "cached" n'écrira que cette demi résolution (et uniquement les "zones" que ton viewer a demandé). Au final, l’intérêt est mince car quand le script Nuke doit être calculé en rendu def, c'est toutes les images du plan qui doivent "passer a la moulinette" et en full résolution ce qui représente finalement une partie assez mince de ce que tu auras pu "choper" avec l'attribut "cached". Si en plus tu lance en farm, tout ce que tu a "chopé" avec "cached" n'existera tout simplement pas sur les farms.
Puis il faut voir les compos aussi. Sur ce projet, il n’était pas rare d'avoir des "Too Many Open Files", les exr étant splitte en post rendu pour éviter le multichannel et gagner en vitesse (ce qui augmente nécessairement la charge réseau, d’où le boulot nécessaire pour l'utiliser intelligemment).
Je rajouterai qu'un système similaire a "cached", plus évolué, avait été développé pour créer des "points de rendu" dans le graph, mais il nécessitait d'avoir un graph propre, ce qui est toujours difficile au fils des retakes.
Content que mon blog te plaise sinon. :)
Soit, donc en exr multichannel, où en HD tu peux monter à 100mo la frame avec toutes les passes d'illumination plus des passes de datas, le problème est assez récurrent (en particulier avec des gizmos un peu sophistiqués) et cette solution est réellement efficace.
Par contre, c'est donc sensible aussi en batch et également sur une farm, pas uniquement en interactif (je ne rends presque jamais en interactif sauf des tout petits trucs) - à base de trucs "collés" pendant 2h avec les cpus qui branlent rien qui passent à 10-15 minutes.
Je n'ai pas bien compris ton message. ^^'
Comme je l'explique dans le billet, le problème n'est pas tant le poids que le nombre d’écriture. Sur un gros réseau, il est souvent préférable de faire moins d’accès en écriture mais que ces accès écrivent beaucoup de données plutôt que l'inverse.
Donc il vaut mieux transférer l'image en un seul morceau sur le réseau (rendre dans un temp local et envoyer le fichier une fois fini) plutôt que laisser Nuke faire environs 1000 accès en écriture par image. :D