Depuis que j’ai commencé à utiliser Darktable, vers 2012, j’ai toujours été surpris par la très faible quantité de RAM qu’il utilisait. Les gens pensent qu’il est bon qu’une application utilise la mémoire avec parcimonie, et c’est certainement vrai lorsqu’on parle de votre environnement de bureau. Mais pour un logiciel de production qui fait du rendu pixel lourd sur des images de 12 à 54 mégapixels, cela signifie que les mêmes calculs coûteux sont refaits encore et encore au lieu d’être enregistrés pour être réutilisés plus tard. C’est précisément à cela que sert un cache : éviter des calculs coûteux. Et il devrait utiliser toute la RAM disponible pour cela, parce que calculer gaspille de l’énergie, ce qui a un impact concret si vous travaillez sur batterie. En plus, vous avez payé cette RAM et l’utiliser ne vide pas votre batterie. Le CPU et le GPU, eux…

Oui, les photo-reporters et de nombreux nomades numériques retouchent sur des ordinateurs portables sur batterie, sur le terrain. Il est temps que le monde open source réalise que tous les photographes ne sont pas assis à leur bureau le soir et le week-end, après leur « vrai » travail de journée. Donc calculer et recalculer les mêmes choses sans arrêt revient simplement à gaspiller de l’énergie, que vous soyez branché au secteur ou sur batterie.

Entre-temps, Darktable a toujours eu ce petit réglage étrange de RAM qui vous permettait de définir combien vous vouliez qu’il en utilise. Sauf qu’il n’avait absolument aucun moyen de suivre combien de RAM il utilisait réellement, donc c’était davantage une indication qu’une règle. Cela a ensuite été remplacé par une préférence qualitative « ressources » complètement idiote, qui ne veut rien dire et qui ne vous indique toujours pas la quantité de RAM que vous allouez au logiciel.

Le cache RAM de Darktable n’a donc jamais vraiment fonctionné, il était sous-utilisé et il fallait le réparer. Parce qu’en conséquence, tout était recalculé en permanence sans raison.

Mais… il y avait une raison pour laquelle le cache était sous-utilisé et ne fonctionnait pas vraiment : ce logiciel n’avait aucune visibilité sur le cycle de vie des données, ce qui est quand même un prérequis si vous voulez stocker des données pour les réutiliser. Cela venait d’un pipeline pixel vraiment alambiqué, je me demande sincèrement si quelqu’un parmi les « développeurs » de Darktable est capable de dessiner l’organigramme complet de ce que nous faisons subir aux pixels du début à la fin, mais aussi de métadonnées d’image calculées à plusieurs endroits, copiées et stockées à plusieurs endroits, puis partiellement mises à jour à plusieurs endroits…

Le cache du pipeline n’avait qu’un seul but : allouer les tampons d’entrée et de sortie des modules, et éventuellement les réutiliser s’ils étaient déjà disponibles. Mais sa taille était définie en nombre d’entrées, ce qui était très limité, et non en quantité de mémoire allouée. Cela le rendait à peine utile et nous obligeait à l’utiliser de façon beaucoup trop conservatrice, parce que sa taille mémoire réelle était totalement imprévisible.

Inutile de dire que « réparer le cache » impliquait « réparer le pipeline », ce qui impliquait à son tour de « rendre le cycle de vie des données visible, traçable et géré globalement », ce qui impliquait de repenser l’architecture de haut niveau du logiciel. Comme nous allons le voir, cela a eu des bénéfices inattendus sur les performances, qui dépassent largement les optimisations internes module par module que Darktable a faites dans le même temps.

Ce qui a changé depuis 2022

Cet article est la suite directe de Réparer le cache du pipeline et des bugs vieux de 10 ans. Le chapitre précédent consistait à trouver la pourriture et à arrêter les pannes les plus embarrassantes. Celui-ci raconte ce qui s’est passé ensuite, lorsqu’il est devenu évident que les bugs du cache n’étaient pas du tout des bugs isolés, mais les symptômes d’un moteur de rendu dont les responsabilités avaient été mélangées bien trop longtemps.

Où cela s’inscrit historiquement

Le problème d’origine n’était pas simplement que « le cache ne met pas assez en cache ». Le vrai problème, c’est que toute la pile du pipeline avait grossi autour de comportements accidentels. Des redessins de l’interface pouvaient finir par déclencher du traitement d’image, de manière non régulée. L’aperçu des masques faisait fuiter l’état de l’interface dans le moteur de rendu. La logique des histogrammes et de la pipette dépendait d’effets de bord particuliers du pipeline. Export, vignettes, aperçu et chambre noire avaient chacun leurs petites variantes de « on peut sûrement recopier ce tampon encore une fois ». Et chaque fois que la cohérence devenait difficile à prouver, lors de l’affichage des masques ou des changements de recadrage, la stratégie de repli consistait souvent à vider les lignes de cache et à tout recalculer, parce qu’apparemment « tout incendier et recommencer » a compté un temps comme de l’architecture.

Certains commentaires du code disaient déjà exactement cela. Il y avait des avertissements à propos des recomputations déclenchées par les redessins. Il y avait même une vieille note indiquant que le cache devrait devenir global un jour. Donc ces dernières années n’ont pas consisté à inventer une nouvelle théorie à partir de rien. Elles ont surtout consisté à enfin faire le nettoyage que le code réclamait depuis les années 2010.

Le basculement historique essentiel, c’est que le pipeline a cessé d’être traité comme un gros blob récursif censé découvrir ses propres besoins pendant qu’il s’exécute. Il a été progressivement scindé en chemins d’exécution plus clairs pour le rendu interactif, le rendu headless, le traitement GPU, le traitement CPU et les consommateurs côté interface. En même temps, le comportement d’interface a été renvoyé vers l’interface. Le dessin est redevenu du dessin. Le traitement est redevenu du traitement. Cela paraît banal, ce qui est généralement le signe que la conception précédente était devenue absurde.

Sommes de contrôle, ou comment la validité a cessé d’être un jeu de devinettes

Une fois que le moteur de rendu n’a plus été empêtré avec chaque widget et chaque canal parallèle, il est devenu possible de définir explicitement l’état de l’image. Chaque entrée d’historique porte désormais une somme de contrôle de module construite à partir des paramètres du module, des paramètres de fusion et de toute forme de masque pertinente. Cela donne à chaque état de module une empreinte canonique dès son entrée dans l’historique, au lieu de laisser sa validité être déduite plus tard à partir de ce qui a bien voulu s’exécuter.

Cela seul ne suffisait pas, parce qu’un pipeline n’est pas juste un tas de réglages de modules. Il dépend aussi de l’ordre, de la région d’intérêt, des contrats de tampons et de l’état lié à l’affichage. La phase de planification accumule donc ces empreintes au niveau des modules tout au long de la chaîne et y intègre le reste du contrat d’exécution avant qu’aucun pixel ne bouge. Le résultat est une identité stable par étape et une identité finale du pipeline.

C’est là que la visibilité apparaît enfin. Le pipeline peut savoir s’il est encore valide en comparant le hash de son historique. Le backbuffer affiché peut savoir s’il correspond toujours à l’état courant du pipeline et de l’historique. Une ligne de cache peut savoir si elle appartient réellement à l’étape exacte, à la région d’intérêt et aux paramètres actuellement demandés. Cela semble presque offensivement raisonnable, mais cela a d’abord demandé beaucoup de nettoyage : la synchronisation entre historique et pipeline a dû être rendue explicite, le contournement du cache a dû être déclaré à l’avance, pour l’aperçu des masques et les modifications de recadrage, l’affichage des masques ne pouvait plus muter les règles en plein milieu de l’exécution, et les vieux effets de bord déclenchés par les redessins ont dû disparaître. On n’obtient pas des hashes fiables sur un flux de contrôle qui ne l’est pas.

L’avantage pratique, c’est que la cohérence n’est plus maintenue par superstition. Nous n’avons plus besoin de nous demander « l’interface a-t-elle probablement invalidé la bonne chose au bon moment ? ». La réponse est dans les sommes de contrôle. Soit l’état correspond, soit non. Et nous pouvons récupérer n’importe quelle sortie de module depuis la couche interface sans exécuter de pipeline, parce que la somme de contrôle du module est prévisible et peut être calculée avant le rendu, ou en parallèle.

L’architecture cache-first

Des hashes fiables changent l’architecture presque par la force. Une fois que chaque état significatif du pipeline possède une identité stable connue à l’avance, le cache cesse d’être une optimisation au mieux opportuniste et devient le centre du flux de données.

C’est pourquoi le pipeline possède maintenant une vraie phase de planification avant l’exécution. L’historique est d’abord synchronisé. Les régions d’intérêt sont propagées à travers toute la chaîne. Chaque étape reçoit un contrat d’entrée et de sortie explicite. La politique de cache côté hôte est figée avant l’exécution. Les hashes globaux sont calculés avant le lancement. Autrement dit, le moteur sait ce qu’il s’apprête à faire avant de commencer à le faire, ce qui tranche agréablement avec l’ancienne méthode consistant à le découvrir en plein vol puis à compenser avec des rustines.

À partir de là, les recherches dans le cache deviennent déterministes. Une étape peut rouvrir exactement le tampon qui correspond à son état, de sorte qu’un module peut contourner complètement le calcul en regardant simplement dans le cache si une ligne correspond déjà à son état interne via une recherche par somme de contrôle. Les consommateurs en aval peuvent rouvrir des résultats publiés par hash au lieu de demander au moteur de rendu d’exécuter une nouvelle danse spéciale juste pour eux. Le cache cesse d’être un amas de mémoire vaguement réutilisable et devient le registre faisant autorité des résultats intermédiaires et finaux valides.

Cela explique aussi pourquoi autant de code a pu être supprimé ou simplifié. Une fois la validité centralisée, il faut moins d’heuristiques, moins de branches spéciales, moins de clauses « sauf lorsque l’interface est dans ce mode », et moins de purges d’urgence. Le code se raccourcit non pas parce que le problème a rétréci, mais parce qu’il a cessé d’être résolu à la fois dans six endroits contradictoires.

Les lignes de cache lient aussi la mémoire GPU et CPU, ce qui nous permet d’utiliser la mémoire épinglée d’OpenCL. Il existait depuis toujours une préférence utilisateur OpenCL permettant d’activer ou non cette mémoire épinglée, mais j’ai découvert qu’elle n’était utilisée que pour le traitement par tuiles, lorsque les modules ne pouvaient pas faire tenir tous leurs besoins mémoire sur le GPU. La mémoire épinglée a maintenant été étendue aux opérations normales, puisqu’elle permet de mapper RAM et VRAM sans copie. L’utilisation de mémoire épinglée sur un pipeline GPU entièrement mis en cache réduit jusqu’à un facteur 7 le temps passé à déplacer des tampons mémoire.

Mais forcer la mise en cache des sorties de modules OpenCL, même avec des tampons épinglés sans copie, a quand même un coût, et ce coût est plus élevé que de sauter complètement le cache. Pour certains modules lourds, comme denoise profiled, diffuse or sharpen ou contrast equalizer, le surcoût du cache vaut clairement la peine, mais pour les modules rapides, balance des blancs, exposition, profil de couleur d’entrée, le coût de mise en cache des tampons OpenCL rend en pratique ces modules plus lents sur GPU que sur CPU.

Nous avons donc désormais des heuristiques par module qui décident si un module OpenCL publiera ou non son tampon de sortie dans le cache RAM. Les modules lourds et tous ceux qui alimentent la pipette ou les histogrammes sont publiés par défaut. Pour les autres, les utilisateurs peuvent décider eux-mêmes :

image

Cela permettra aussi aux utilisateurs de désactiver OpenCL de manière sélective sur certains modules lorsqu’ils rencontrent des problèmes récurrents avec eux. Par exemple, denoise profiled et dehaze consomment beaucoup de mémoire GPU et peuvent échouer à l’exécution faute de mémoire disponible, même si la planification initiale estimait qu’il devrait y en avoir assez. Quand cela se produit, le module a déjà commencé à calculer depuis un moment et repasse ensuite sur CPU. Si cela arrive souvent, autant contourner le GPU pour ce module en permanence plutôt que de payer deux fois la pénalité.

Mais cela ne s’arrête pas là. Les tampons GPU restent aussi vivants sur le GPU. Cela signifie que le redémarrage d’un pipeline partiel depuis un module OpenCL récupère directement son entrée sur le GPU lorsque c’est possible, sans aucune copie depuis la RAM. Cela signifie aussi que les modules légers peuvent réutiliser leur précédent tampon GPU s’il convient, au lieu d’en allouer un nouveau. Rien que cela ouvre la possibilité d’avoir un pipeline temps réel, nécessaire au nouveau module Dessin, avec environ 60 à 80 ms de latence entre les coups de pinceau et la mise à jour de l’image. Je n’aurais jamais cru cela possible un jour.

La même réallocation mémoire se fait en RAM puisque Ansel héberge désormais tout le cache dans sa propre arène mémoire . Ce schéma nous permet non seulement de pré-réserver auprès du système d’exploitation un bloc mémoire contigu au lancement de l’application, ce qui accélère les allocations, mais il garantit aussi que nous ne dépassons pas le volume mémoire défini par l’utilisateur. Ainsi, lorsque vous décidez d’allouer 8 Go de RAM à Ansel, nous sommes maintenant capables de garantir que cela se produira réellement : nous remplirons tout ce volume jusqu’à saturation, après quoi nous commencerons à recycler les tampons les plus anciens. Tous les tampons de pixels, les entrées et sorties des modules, mais aussi les zones de travail temporaires internes, sont alloués dans notre arène mémoire.

Et, enfin, il n’y a plus qu’un seul cache de pipeline pour toute l’application, ce qui signifie que tous les types de pipelines, aperçu de chambre noire, image principale de chambre noire, export de vignettes et export de fichiers, partagent le même cache. Bien que cela ait demandé une gestion correcte des accès concurrents multithread, cela veut dire que si un pipeline a besoin de la sortie d’un module déjà calculée à la même taille par un autre pipeline, il peut directement récupérer la ligne de cache et éviter son propre recalcul.

Pour tirer parti de cela, l’« aperçu » de chambre noire, auparavant de taille fixe, c’est-à-dire la version réduite de l’image complète utilisée dans le widget de navigation, pour échantillonner les histogrammes et la pipette, et comme remplissage transitoire « flou » lorsque l’image principale est en cours de recomputation, a été modifié pour adopter dynamiquement la taille correspondant au niveau de zoom « ajuster » de l’image principale, celle affichée au centre de la chambre noire. Le résultat, c’est qu’une seule image doit être calculée à l’entrée dans la chambre noire, et revenir au zoom « ajuster » devient instantané.

Mais comme cet aperçu à taille fixe avait été paresseusement utilisé à de nombreux endroits de l’interface pour remapper les coordonnées du curseur dans l’espace des widgets vers les coordonnées pleine résolution de l’image RAW, au moyen du même bloc de code hideux copié-collé partout, allant jusqu’à recopier le commentaire disant « je ne comprends pas ce que cela fait », tout le système de coordonnées a dû être refait pour absorber ce changement. Et, encore une fois, ces coordonnées n’étaient calculées qu’à l’intérieur d’un pipeline, donc l’interface devait attendre que le pipeline recalcule les nouvelles tailles pour savoir où positionner les objets, ce qui déclenchait des glitches d’interface, en particulier lors d’un changement de recadrage. Maintenant, tout cela est calculé à l’avance depuis une API unifiée, donc plus de copier-coller idiot multiplié par le nombre de modules.

Ce que cela a changé hors du cœur du moteur

La première conséquence externe, c’est que les copies ont cessé de se faire passer pour des fonctionnalités. La chambre noire n’a plus besoin d’un backbuffer d’interface dédié pour afficher ce que le moteur de rendu a déjà produit. Elle peut récupérer l’image publiée directement depuis le cache, y associer une surface Cairo et l’afficher en quelques millisecondes. Auparavant, ce chemin était plus lent et saturé de copies sans autre raison qu’une confusion de conception héritée. La chambre noire peut aussi vérifier directement si un backbuffer donné du pipeline est à jour par rapport à l’historique de développement courant, au moyen des sommes de contrôle. Avant, cela reposait sur un indicateur « dirty » qui pouvait être écrasé par plusieurs threads et empêchait tout redessin « au mieux », c’est-à-dire afficher le dernier backbuffer valide connu en attendant la recomputation de la version à jour. C’était important quand l’utilisateur modifiait les paramètres de modules plus vite que le pipeline ne pouvait recalculer : il nous faut toujours quelque chose pour remplir la vue et éviter le scintillement.

La même logique s’étend maintenant à l’export et aux vignettes, qui peuvent rouvrir le résultat final publié au lieu de reconstruire leur propre réalité privée. Le code des histogrammes et de la pipette a lui aussi cessé de se greffer sur l’exécution du pipeline. Les scopes suivent des signaux explicites d’achèvement provenant du chemin d’aperçu, mettent en cache leur propre surface Cairo par rapport à l’état source et échantillonnent directement depuis les backbuffers alimentés par le cache. Un histogramme RAW, échantillonné après le module demosaic, a été ajouté à l’ancienne sortie du pipeline : passer de l’un à l’autre ne nécessite pas de recomputation du pipeline, pas plus que l’utilisation de la pipette sur l’un ou l’autre. C’est une conception beaucoup plus ennuyeuse, et c’est exactement pour cela qu’elle est meilleure.

Une autre conséquence majeure, c’est que les différentes variantes du pipeline ne vivent plus dans des silos de cache isolés. L’aperçu, la chambre noire, l’export, les vignettes et les consommateurs d’histogrammes peuvent désormais partager le même état de cache. C’était l’intention déjà suggérée il y a longtemps dans le code du cache ; il a simplement fallu un nettoyage complet du suivi de validité pour le rendre réel. Une fois que tous ces consommateurs s’accordent sur les mêmes hashes, ils peuvent enfin s’accorder sur les mêmes tampons. L’avantage, c’est qu’un pipeline peut réutiliser une ligne de cache déjà calculée par un autre pipeline si elle convient, mêmes paramètres, mêmes tailles d’entrée et de sortie. Le début des pipelines de chambre noire a donc été basculé en pleine résolution : le dématriçage et le débruitage seront plus lents à l’entrée dans la chambre noire, mais ensuite le zoom et le panoramique deviennent instantanés. Et l’export d’une image récupérera lui aussi directement ces premières étapes depuis le cache. Au passage, exporter la même image deux fois à deux résolutions différentes ne recalculera qu’à partir du module de mise à l’échelle, à la fin du pipeline.

La gestion de la mémoire a suivi la même logique. Les lignes de cache sont devenues des objets réutilisables, verrouillables et munis d’un comptage de références, au lieu d’être des blobs jetables transportés dans des stockages annexes bricolés. Les chemins CPU et GPU partagent maintenant beaucoup plus le même modèle de propriété. Côté OpenCL, les tampons hôte et périphérique sont gérés avec des règles de durée de vie explicites, la mémoire épinglée est réutilisée au lieu d’être constamment recréée, et la RAM comme la VRAM restent synchronisées par des transferts épinglés et sans copie lorsque c’est possible, au lieu d’être dupliquées via des copies intermédiaires inutiles. Cela ne rend pas le GPU magiquement meilleur en calcul. Cela empêche surtout toute la tuyauterie autour de lui de gaspiller les gains.

Le bénéfice ne se limite pas à la vitesse dans les benchmarks. Il touche aussi la latence, la robustesse et la consommation d’énergie. Le démarrage de la chambre noire se raccourcit parce qu’il y a moins de copies et moins de passes dupliquées. Le zoom, le panoramique, l’aperçu des masques et la navigation dans l’historique cessent d’invalider la moitié de l’univers simplement parce qu’il était autrefois trop difficile de prouver la correction. OpenCL devient moins aléatoire parce que les replis, le tuilage et la propriété mémoire sont exprimés plus explicitement. Le logiciel passe moins de temps à déplacer des pixels juste pour se rassurer sur le fait qu’ils existent encore quelque part.

Comme effet de bord positif, la gestion des erreurs a été améliorée dans le pipeline. Les modules qui échouent à allouer de la mémoire déclenchent désormais immédiatement une erreur et interrompent le pipeline. Auparavant, les échecs d’allocation n’étaient pas vérifiés partout, et tout un pipeline pouvait transporter un tampon NULL jusqu’au bout… ou s’écraser franchement sur une erreur de type segmentation fault. Cela est maintenant empêché en amont de manière uniforme dans tout le logiciel.

L’échantillonnage de la pipette et des histogrammes peut maintenant lire les données sans déclencher de recomputation du pipeline en récupérant directement le cache, si la ligne de cache demandée existe. Le panneau global des scopes vous permet de choisir entre le RGB RAW, échantillonné après demosaicing, et le RGB final du pipeline, soit échantillonné après output color profile en flottant 32 bits, soit dans le backbuffer en entiers 8 bits : passer de l’un à l’autre ne redémarre pas un pipeline, cela charge simplement une autre ligne de cache, et la pipette s’applique sur cette ligne de cache de manière transparente.

Ce qui a réellement été corrigé en le faisant correctement

Beaucoup d’ennuis visibles découlent davantage de cette refonte que d’une quelconque micro-optimisation isolée. La gestion des régions d’intérêt en haute résolution s’est améliorée parce que la propriété de la géométrie est devenue plus claire et que la correspondance des coordonnées entre l’interface et l’image RAW est maintenant gérée dans une API centrale. La resynchronisation entre historique et pipeline est devenue moins fragile parce que l’historique a cessé d’être un effet de bord accessoire du flux d’interface pour devenir une entrée de premier ordre de la planification. L’aperçu de masque en temps réel n’a plus besoin de lutter contre un modèle de cache conçu pour une autre époque. Le rafraîchissement de l’histogramme est devenu prévisible parce qu’il consomme maintenant des données publiées, au lieu d’espérer que le bon tampon intermédiaire soit encore vivant quelque part. Les boucles infinies, les lignes de cache périmées, les backbuffers invalides et le classique « l’aperçu a recalculé tout le pipeline depuis le module 0 sans raison évidente » deviennent tous plus faciles à diagnostiquer une fois que le cycle de vie est explicite.

La partie intéressante ici n’est donc pas qu’une grosse refactorisation se soit produite isolément. C’est qu’une longue série de nettoyages s’est finalement alignée autour d’une idée : la validité doit être calculée, nommée et partagée à l’avance. Une fois cela en place, le reste a suivi presque mécaniquement. Le cache est devenu central parce que les hashes étaient dignes de confiance. Les consommateurs externes ont pu cesser de copier parce que les tampons publiés par le cache sont devenus faisant autorité. Les chemins CPU et GPU ont pu partager davantage d’état parce que la propriété est devenue explicite. Et le moteur de rendu a cessé de se comporter comme une maison hantée où bouger un curseur pouvait réveiller trois sous-systèmes sans rapport et un callback de redessin datant de 2013.

Benchmarks

Les benchmarks sont réalisés sur les deux applications compilées localement en mode Release avec GCC14 en utilisant leur script respectif build.sh --build-type Release. Le matériel est un Thinkpad P51, avec 8 × Intel® Xeon® CPU E3-1505M v6 à 3,00 GHz et un GPU Quadro M2200, fonctionnant sous Fedora 41 Plasma avec Wayland. La version du pilote Nvidia est 580.105.08. Les fichiers de configuration d’Ansel et de Darktable sont tous deux réglés avec ce script  afin de garantir la même base de configuration. Nous montrons ici la sortie de ansel -d perf et darktable -d perf. Ces benchmarks peuvent être reproduits sur n’importe quel ordinateur.

L’image test de 45 Mpx est prise ici  et le XMP d’édition peut être téléchargé ici.

Chargement initial dans la chambre noire

Attention : dans Ansel, il est important d’exécuter ce test sans qu’aucun module ne soit focalisé dans l’interface, c’est-à-dire sans module déplié. Dans cette situation, Ansel met toujours en cache la sortie du module précédent, ce qui garantit que lorsque des modifications sont effectuées dans ce module focalisé, les calculs redémarrent toujours depuis son entrée immédiate. Mais forcer ainsi le cache déclenche un surcoût mémoire qui peut être visible sur les modules légers.

 14.474793 [dev_pixelpipe] took 0.000 secs (0.000 CPU) to load the image.
 24.739267 [dev_pixelpipe] pipeline resync with history took 0.029 secs (0.026 CPU) for pipe virtual-preview
 34.806027 [dev_pixelpipe] pipeline resync with history took 0.000 secs (0.000 CPU) for pipe virtual-preview
 44.806089 [dev_pixelpipe] pipeline resync with history took 0.000 secs (0.000 CPU) for pipe virtual-preview
 54.872758 [dev_pixelpipe] pipeline resync with history took 0.026 secs (0.036 CPU) for pipe preview
 64.889006 [dev_pixelpipe] took 0.016 secs (0.037 CPU) initing base buffer [preview]
 74.922294 [dev_pixelpipe] took 0.033 secs (0.031 CPU) processed `Raw settings' on GPU, blended on  [preview]
 84.938778 [dev_pixelpipe] took 0.015 secs (0.018 CPU) processed `white balance' on GPU, blended on  [preview]
 98.456387 [dev_pixelpipe] took 3.518 secs (3.549 CPU) processed `highlight reconstruction' on GPU, blended on GPU [preview]
109.052687 [dev_pixelpipe] took 0.596 secs (0.308 CPU) processed `demosaic' on GPU, blended on  [preview]
119.401928 [dev_pixelpipe] took 0.349 secs (0.201 CPU) processed `lens correction' on GPU, blended on  [preview]
129.498531 [dev_pixelpipe] took 0.097 secs (0.095 CPU) processed `Initial resampling' on GPU, blended on  [preview]
139.499987 [dev_pixelpipe] took 0.001 secs (0.001 CPU) processed `exposure' on GPU, blended on GPU [preview]
149.502353 [dev_pixelpipe] took 0.002 secs (0.002 CPU) processed `input color profile' on GPU, blended on  [preview]
159.503926 [dev_pixelpipe] took 0.002 secs (0.001 CPU) processed `color calibration' on GPU, blended on GPU [preview]
1610.658713 [dev_pixelpipe] took 1.155 secs (1.115 CPU) processed `diffuse or sharpen' on GPU, blended on GPU [preview]
1710.667555 [dev_pixelpipe] took 0.009 secs (0.009 CPU) processed `color balance rgb' on GPU, blended on GPU [preview]
1810.673614 [dev_pixelpipe] took 0.006 secs (0.003 CPU) processed `filmic rgb' on GPU, blended on GPU [preview]
1910.695368 [dev_pixelpipe] took 0.022 secs (0.012 CPU) processed `output color profile' on GPU, blended on  [preview]
2010.742278 [dev_pixelpipe] took 0.047 secs (0.034 CPU) processed `dithering' on CPU, blended on  [preview]
2110.753262 [dev_pixelpipe] took 0.011 secs (0.048 CPU) processed `display encoding' on CPU, blended on  [preview]
2210.753354 [pixelpipe] preview internal pixel pipeline processing took 5.880 secs (5.464 CPU)
2310.753476 [dev_process_preview] pipeline processing thread took 5.881 secs (5.464 CPU)
2410.820855 [dev_pixelpipe] pipeline resync with history took 0.056 secs (0.406 CPU) for pipe full
2510.824477 [dev_process_full] pipeline processing thread took 0.000 secs (0.000 CPU)
 110,5815 [dt_dev_process_image_job] loading image. took 0,000 secs (0,000 CPU)
 210,6799 [dev_pixelpipe] took 0,018 secs (0,029 CPU) initing base buffer [full]
 310,6968 [dev_pixelpipe] took 0,017 secs (0,020 CPU) [full] processed `rawprepare' on GPU, blended on GPU
 410,7094 [dev_pixelpipe] took 0,013 secs (0,011 CPU) [full] processed `temperature' on GPU, blended on GPU
 514,4091 [dev_pixelpipe] took 3,700 secs (3,719 CPU) [full] processed `highlights' on GPU with tiling, blended on CPU
 614,8078 [resample_cl] plan 0,000 secs (0,000 CPU) resample 0,010 secs (0,002 CPU)
 714,8988 [dev_pixelpipe] took 0,490 secs (0,349 CPU) [full] processed `demosaic' on GPU, blended on GPU
 814,9094 [dev_pixelpipe] took 0,010 secs (0,001 CPU) [full] processed `lens' on GPU, blended on GPU
 914,9200 [dev_pixelpipe] took 0,010 secs (0,002 CPU) [full] processed `exposure' on GPU, blended on GPU
1014,9516 [dev_pixelpipe] took 0,032 secs (0,010 CPU) [full] processed `colorin' on GPU, blended on GPU
1114,9654 [dt_ioppr_transform_image_colorspace_cl] IOP_CS_LAB-->IOP_CS_RGB took 0,013 secs (0,000 GPU) [channelmixerrgb]
1214,9902 [dev_pixelpipe] took 0,039 secs (0,006 CPU) [full] processed `channelmixerrgb' on GPU, blended on GPU
1316,6800 [dev_pixelpipe] took 1,690 secs (1,745 CPU) [full] processed `diffuse' on GPU, blended on GPU
1416,6923 [dev_pixelpipe] took 0,012 secs (0,007 CPU) [full] processed `colorbalancergb' on GPU, blended on GPU
1516,7037 [dev_pixelpipe] took 0,011 secs (0,005 CPU) [full] processed `filmicrgb' on GPU, blended on GPU
1616,7100 [dt_ioppr_transform_image_colorspace_cl] IOP_CS_RGB-->IOP_CS_LAB took 0,006 secs (0,000 GPU) [colorout]
1716,7555 [dev_pixelpipe] took 0,052 secs (0,020 CPU) [full] processed `colorout' on GPU, blended on GPU
1816,8435 [dev_pixelpipe] took 0,088 secs (0,052 CPU) [full] processed `dither' on CPU, blended on CPU
1916,8525 [dev_pixelpipe] took 0,009 secs (0,066 CPU) [full] processed `gamma' on CPU, blended on CPU
2016,8594 [dev_process_image] pixel pipeline took 6,197 secs (6,048 CPU) processing `2022-06-17__DSC00078.arw'
21
2216,8918 [dt_dev_process_image_job] loading image. took 0,000 secs (0,000 CPU)
2316,9470 [dt_dev_process_image_job] loading image. took 0,000 secs (0,000 CPU)
2416,9968 [dev_pixelpipe] took 0,000 secs (0,000 CPU) initing base buffer [preview]
2516,9986 [dev_pixelpipe] took 0,002 secs (0,002 CPU) [preview] processed `rawprepare' on GPU, blended on GPU
2616,9995 [dev_pixelpipe] took 0,001 secs (0,001 CPU) [preview] processed `temperature' on GPU, blended on GPU
2717,0698 [dev_pixelpipe] took 0,070 secs (0,081 CPU) [preview] processed `highlights' on GPU, blended on GPU
2817,0811 [dev_pixelpipe] took 0,011 secs (0,005 CPU) [preview] processed `demosaic' on GPU, blended on GPU
2917,0829 [dev_pixelpipe] took 0,002 secs (0,000 CPU) [preview] processed `lens' on GPU, blended on GPU
3017,0853 [dev_pixelpipe] took 0,002 secs (0,001 CPU) [preview] processed `exposure' on GPU, blended on GPU
3117,0886 [dev_pixelpipe] took 0,003 secs (0,001 CPU) [preview] processed `colorin' on GPU, blended on GPU
3217,0915 [dt_ioppr_transform_image_colorspace_cl] IOP_CS_LAB-->IOP_CS_RGB took 0,003 secs (0,000 GPU) [channelmixerrgb]
3317,0952 [dev_pixelpipe] took 0,007 secs (0,002 CPU) [preview] processed `channelmixerrgb' on GPU, blended on GPU
3417,4667 [dev_pixelpipe] took 0,371 secs (0,354 CPU) [preview] processed `diffuse' on GPU, blended on GPU
3517,4702 [dev_pixelpipe] took 0,003 secs (0,001 CPU) [preview] processed `colorbalancergb' on GPU, blended on GPU
3617,4748 [dev_pixelpipe] took 0,004 secs (0,001 CPU) [preview] processed `filmicrgb' on GPU, blended on GPU
3717,4776 [dt_ioppr_transform_image_colorspace_cl] IOP_CS_RGB-->IOP_CS_LAB took 0,002 secs (0,000 GPU) [colorout]
3817,4939 [dev_pixelpipe] took 0,019 secs (0,009 CPU) [preview] processed `colorout' on GPU, blended on GPU
3917,5141 [dev_pixelpipe] took 0,020 secs (0,007 CPU) [preview] processed `dither' on CPU, blended on CPU
4017,5195 [dev_pixelpipe] took 0,005 secs (0,021 CPU) [preview] processed `gamma' on CPU, blended on CPU
4117,5355 [dt_ioppr_transform_image_colorspace_rgb] `Système' -> `sRVB' took 0,016 secs (0,067 CPU) [final histogram]
4217,6218 [dev_process_image] pixel pipeline took 0,625 secs (0,657 CPU) processing `2022-06-17__DSC00078.arw'
 13.695922 [dev_pixelpipe] took 0.000 secs (0.000 CPU) to load the image.
 24.101947 [dev_pixelpipe] pipeline resync with history took 0.023 secs (0.031 CPU) for pipe preview
 34.113447 [darkroom] took 0.015 secs (0.024 CPU) redraw
 44.117058 [dev_pixelpipe] took 0.015 secs (0.025 CPU) initing base buffer [preview]
 54.149378 [dev_pixelpipe] took 0.032 secs (0.086 CPU) processed `Raw settings' on CPU, blended on  [preview]
 64.181609 [dev_pixelpipe] took 0.032 secs (0.096 CPU) processed `white balance' on CPU, blended on  [preview]
 710.424658 [dev_pixelpipe] took 6.243 secs (44.074 CPU) processed `highlight reconstruction' on CPU, blended on CPU [preview]
 810.778340 [dev_pixelpipe] took 0.354 secs (2.622 CPU) processed `demosaic' on CPU, blended on  [preview]
 910.858075 [dev_pixelpipe] took 0.080 secs (0.481 CPU) processed `lens correction' on CPU, blended on  [preview]
1011.004742 [dev_pixelpipe] took 0.147 secs (0.916 CPU) processed `Initial resampling' on CPU, blended on  [preview]
1111.011701 [dev_pixelpipe] took 0.007 secs (0.036 CPU) processed `exposure' on CPU, blended on CPU [preview]
1211.015201 [dev_pixelpipe] took 0.003 secs (0.016 CPU) processed `input color profile' on CPU, blended on  [preview]
1311.077223 [dev_pixelpipe] took 0.062 secs (0.349 CPU) processed `color calibration' on CPU, blended on CPU [preview]
1414.953074 [dev_pixelpipe] took 3.876 secs (28.821 CPU) processed `diffuse or sharpen' on CPU, blended on CPU [preview]
1515.225196 [dev_pixelpipe] took 0.272 secs (1.627 CPU) processed `color balance rgb' on CPU, blended on CPU [preview]
1615.361886 [dev_pixelpipe] took 0.137 secs (0.772 CPU) processed `filmic rgb' on CPU, blended on CPU [preview]
1715.374734 [dev_pixelpipe] took 0.013 secs (0.071 CPU) processed `output color profile' on CPU, blended on  [preview]
1815.400044 [dev_pixelpipe] took 0.025 secs (0.040 CPU) processed `dithering' on CPU, blended on  [preview]
1915.410297 [dev_pixelpipe] took 0.010 secs (0.054 CPU) processed `display encoding' on CPU, blended on  [preview]
2015.410353 [pixelpipe] preview internal pixel pipeline processing took 11.308 secs (80.087 CPU)
2115.410383 [dev_process_preview] pipeline processing thread took 11.308 secs (80.087 CPU)
2215.479787 [dev_pixelpipe] pipeline resync with history took 0.059 secs (0.426 CPU) for pipe full
2315.479850 [dev_process_full] pipeline processing thread took 0.000 secs (0.000 CPU)
 14,5577 [dt_dev_load_raw] loading the image. took 0,131 secs (0,040 CPU)
 25,4913 [dt_dev_process_image_job] loading image. took 0,000 secs (0,000 CPU)
 35,6087 [dev_pixelpipe] took 0,017 secs (0,029 CPU) initing base buffer [full]
 45,6491 [dev_pixelpipe] took 0,040 secs (0,120 CPU) [full] processed `rawprepare' on CPU, blended on CPU
 55,6782 [dev_pixelpipe] took 0,029 secs (0,061 CPU) [full] processed `temperature' on CPU, blended on CPU
 612,0060 [dev_pixelpipe] took 6,328 secs (45,005 CPU) [full] processed `highlights' on CPU, blended on CPU
 712,6762 [resample_plain] plan 0,001 secs (0,001 CPU) resample 0,249 secs (1,829 CPU)
 812,7230 [dev_pixelpipe] took 0,717 secs (4,499 CPU) [full] processed `demosaic' on CPU, blended on CPU
 912,7370 [dev_pixelpipe] took 0,013 secs (0,029 CPU) [full] processed `lens' on CPU, blended on CPU
1012,7487 [dev_pixelpipe] took 0,012 secs (0,029 CPU) [full] processed `exposure' on CPU, blended on CPU
1112,7637 [dev_pixelpipe] took 0,015 secs (0,045 CPU) [full] processed `colorin' on CPU, blended on CPU
1212,7693 [dt_ioppr_transform_image_colorspace] IOP_CS_LAB-->IOP_CS_RGB took 0,006 secs (0,028 CPU) [channelmixerrgb]
1312,8473 [dev_pixelpipe] took 0,084 secs (0,438 CPU) [full] processed `channelmixerrgb' on CPU, blended on CPU
1417,0570 [dev_pixelpipe] took 4,210 secs (32,406 CPU) [full] processed `diffuse' on CPU, blended on CPU
1517,3181 [dev_pixelpipe] took 0,261 secs (1,872 CPU) [full] processed `colorbalancergb' on CPU, blended on CPU
1617,4583 [dev_pixelpipe] took 0,140 secs (0,898 CPU) [full] processed `filmicrgb' on CPU, blended on CPU
1717,4634 [dt_ioppr_transform_image_colorspace] IOP_CS_RGB-->IOP_CS_LAB took 0,005 secs (0,033 CPU) [colorout]
1817,4792 [dev_pixelpipe] took 0,021 secs (0,116 CPU) [full] processed `colorout' on CPU, blended on CPU
1917,5381 [dev_pixelpipe] took 0,059 secs (0,037 CPU) [full] processed `dither' on CPU, blended on CPU
2017,5472 [dev_pixelpipe] took 0,009 secs (0,061 CPU) [full] processed `gamma' on CPU, blended on CPU
2117,5543 [dev_process_image] pixel pipeline took 11,962 secs (85,649 CPU) processing `2022-06-17__DSC00078.arw'
22
2317,5861 [dt_dev_process_image_job] loading image. took 0,000 secs (0,000 CPU)
2417,6428 [dt_dev_process_image_job] loading image. took 0,000 secs (0,000 CPU)
2517,6993 [dev_pixelpipe] took 0,000 secs (0,000 CPU) initing base buffer [preview]
2617,7004 [dev_pixelpipe] took 0,001 secs (0,002 CPU) [preview] processed `rawprepare' on CPU, blended on CPU
2717,7015 [dev_pixelpipe] took 0,001 secs (0,004 CPU) [preview] processed `temperature' on CPU, blended on CPU
2817,8101 [dev_pixelpipe] took 0,109 secs (0,570 CPU) [preview] processed `highlights' on CPU, blended on CPU
2917,8261 [dev_pixelpipe] took 0,016 secs (0,071 CPU) [preview] processed `demosaic' on CPU, blended on CPU
3017,8308 [dev_pixelpipe] took 0,005 secs (0,006 CPU) [preview] processed `lens' on CPU, blended on CPU
3117,8345 [dev_pixelpipe] took 0,004 secs (0,006 CPU) [preview] processed `exposure' on CPU, blended on CPU
3217,8393 [dev_pixelpipe] took 0,005 secs (0,016 CPU) [preview] processed `colorin' on CPU, blended on CPU
3317,8411 [dt_ioppr_transform_image_colorspace] IOP_CS_LAB-->IOP_CS_RGB took 0,002 secs (0,008 CPU) [channelmixerrgb]
3417,8670 [dev_pixelpipe] took 0,028 secs (0,144 CPU) [preview] processed `channelmixerrgb' on CPU, blended on CPU
3519,0763 [dev_pixelpipe] took 1,209 secs (8,091 CPU) [preview] processed `diffuse' on CPU, blended on CPU
3619,1926 [dev_pixelpipe] took 0,116 secs (0,708 CPU) [preview] processed `colorbalancergb' on CPU, blended on CPU
3719,2628 [dev_pixelpipe] took 0,070 secs (0,342 CPU) [preview] processed `filmicrgb' on CPU, blended on CPU
3819,2667 [dt_ioppr_transform_image_colorspace] IOP_CS_RGB-->IOP_CS_LAB took 0,003 secs (0,012 CPU) [colorout]
3919,2758 [dev_pixelpipe] took 0,012 secs (0,045 CPU) [preview] processed `colorout' on CPU, blended on CPU
4019,2860 [dev_pixelpipe] took 0,010 secs (0,003 CPU) [preview] processed `dither' on CPU, blended on CPU
4119,2907 [dev_pixelpipe] took 0,005 secs (0,020 CPU) [preview] processed `gamma' on CPU, blended on CPU
4219,3052 [dt_ioppr_transform_image_colorspace_rgb] `Système' -> `sRVB' took 0,014 secs (0,071 CPU) [final histogram]
4319,3928 [dev_process_image] pixel pipeline took 1,694 secs (10,211 CPU) processing `2022-06-17__DSC00078.arw'

D’abord, en entrant dans la chambre noire, Darktable calcule 2 pipelines pour obtenir le même résultat qu’Ansel, qui n’en calcule qu’un seul. Donc, indépendamment des temps d’exécution réels, cela représente déjà deux fois plus de calculs inutiles.

Ensuite, Ansel calcule l’image en pleine résolution jusqu’au module de rééchantillonnage initial, tandis que Darktable s’arrête juste avant le dématriçage. C’est important pour la comparaison :

LogicielGPUCPU
Ansel5.880 s11.308 s
Darktable 5.4.16.822 s13.66 s

Darktable parvient à être plus lent qu’Ansel sur GPU même si l’on ne regarde que le rendu du seul pipeline principal et même s’il réduit l’image à la résolution d’affichage plus tôt dans le pipeline. Mais on observe en réalité le même effet sur CPU, malgré toutes les soi-disant « optimisations » réalisées sur les modules purement CPU entre 2023 et 2024 dans Darktable. Au total, Darktable va figer votre ordinateur 0,94 s de plus sur GPU (16 % de plus) et 2,35 s de plus sur CPU (21 % de plus).

Cela donne les comparaisons de modules côte à côte suivantes (nous ne comparons que les modules s’exécutant à la même résolution, uniquement pour le pipeline principal de l’image) :

Temps relatifs par module (GPU). Plus bas = mieux.
Temps relatifs par module (CPU). Plus bas = mieux.
Module / puceAnsel MasterDarktable 5.4.1Gain Ansel
Exposition / CPU7 ms12 ms×1.71 / +42 %
Exposition / GPU1 ms10 ms×10 / +90 %
Profil de couleur d’entrée / CPU3 ms15 ms×5 / +80 %
Profil de couleur d’entrée / GPU2 ms32 ms×16 / +94 %
Calibration des couleurs / CPU62 ms84 ms×1.35 / +26 %
Calibration des couleurs / GPU2 ms39 ms×19.5 / +95 %
Diffusion ou netteté / CPU3.88 s4.21 s×1.09 / +8 %
Diffusion ou netteté / GPU1.16 s1.69 s×1.46 / +32 %
Balance couleur RVB / CPU272 ms261 ms×0.96 / -4.2 %
Balance couleur RVB / GPU9 ms12 ms×1.33 / +25 %
Filmic RVB / CPU137 ms140 ms×1.02 / +2.1 %
Filmic RVB / GPU6 ms11 ms×1.83 / +45 %
Profil de couleur de sortie / CPU13 ms21 ms×1.62 / +38 %
Profil de couleur de sortie / GPU22 ms52 ms×2.36 / +58 %
Tramage (CPU uniquement) / Pipeline CPU25 ms59 ms×2.36 / +58 %
Tramage (CPU uniquement) / Pipeline GPU47 ms88 ms×1.87 / +47 %
Gamma / encodage d’affichage (CPU uniquement) / Pipeline CPU10 ms9 ms×0.9 / -11 %

Sur GPU, ce que l’on observe correspond clairement au bénéfice de la gestion mémoire de haut niveau, utilisant lorsque c’est pertinent soit de la mémoire OpenCL épinglée, soit de simples tampons réservés au périphérique. Je ne connais aucun changement entre le code GPU de Darktable et celui d’Ansel pour ces modules, donc la seule explication est la refonte orientée cache avec une gestion mémoire correcte. Et cela nous donne des gains de vitesse de 25 à 95 % sans aucun changement algorithmique interne.

Sur CPU, cela devient intéressant car Darktable est censé avoir reçu beaucoup d’optimisations de boucles par pixel, module par module, entre les versions 4.0 et 5.2. Et pourtant, il reste plus lent qu’Ansel pour presque tous les modules listés, à l’exception de gamma/encodage d’affichage. Ansel a réintégré les optimisations algorithmiques de filmic RVB issues de Darktable 5.0, diffusion ou netteté calcule en réalité 9 divisions de plus par pixel dans le code d’Ansel que dans celui de Darktable (correction de bug), tramage utilise encore le code de Darktable 4.0 (alors que celui de la 5.4 était soi-disant optimisé), et les profils de couleur d’entrée/sortie, la balance couleur RVB et la calibration des couleurs ont été optimisés dans Ansel en utilisant explicitement les types vectoriels du compilateur (une optimisation d’un autre genre que celle faite dans Darktable avec OpenMP et des indications au compilateur).

Dans Darktable, Ralf Brown a optimisé des modules  pour un CPU AMD Threadripper à 64 cœurs, tandis que j’utilise un bon vieux Xeon Intel avec 4 × 2 cœurs en Hyper-Threading. Donc, malgré tout le battage trompeur et les mensonges répandus dans les notes de version ces dernières années, Darktable s’est dégradé sur Intel et toute cette agitation ne vaut finalement rien. Je connais bien plus de gens qui utilisent du matériel Intel i5/i7 que n’importe quel CPU à 64 cœurs. Nous sommes donc très heureux de voir que les performances se sont améliorées pour ceux qui en avaient le moins besoin. Les autres n’ont qu’à acheter du matériel neuf, j’imagine. Le logiciel libre est devenu la même camelote consumériste que le logiciel propriétaire.

Mais il est évidemment plus facile de s’attaquer à des optimisations module par module parce que les modules sont… modulaires, c’est-à-dire autonomes et auto-contenus. Il y a peu de risques de casser l’ensemble du logiciel quand on travaille sur les modules, puisqu’ils sont isolés du reste. Les changements d’architecture à l’échelle de l’application, à l’inverse, sont dangereux, compliqués et exigeants. Et ils imposent d’arrêter complètement le développement de nouveaux modules tant que l’architecture n’est pas stabilisée. Darktable n’aura jamais la discipline de faire cela, parce que le projet n’est pas géré et que des pull requests aléatoires apparaissent sur GitHub à des moments aléatoires, proposées par des contributeurs aléatoires.

Mais l’ampleur des gains atteints ici ne fait que confirmer ce que je dis depuis des années : l’échelle du module est la mauvaise échelle, le problème de cette application est son pipeline. Ralf Brown rapporte des gains individuels de 5 à 15 %, incohérents selon le nombre de cœurs utilisés, avec souvent des régressions sur 8 cœurs ou moins. Sur CPU, pour les calculs de matrices de couleur, on obtient une plage de gains comprise entre 31 et 80 %.

Zoom à 100 %

Dans la chambre noire, à partir du mode « ajuster à la fenêtre », zoomez à 100 % en un clic avec le bouton du milieu de la souris.

 112.083270 [dev_pixelpipe] pipeline resync with history took 0.000 secs (0.000 CPU) for pipe virtual-preview
 212.111607 [dev_pixelpipe] pipeline resync with history took 0.000 secs (0.000 CPU) for pipe full
 312.177976 [dev_pixelpipe] took 0.066 secs (0.095 CPU) processed `Initial resampling' on GPU, blended on  [full]
 412.179976 [dev_pixelpipe] took 0.002 secs (0.002 CPU) processed `exposure' on GPU, blended on GPU [full]
 512.183194 [dev_pixelpipe] took 0.003 secs (0.005 CPU) processed `input color profile' on GPU, blended on  [full]
 612.185327 [dev_pixelpipe] took 0.002 secs (0.002 CPU) processed `color calibration' on GPU, blended on GPU [full]
 714.887725 [dev_pixelpipe] took 2.702 secs (2.705 CPU) processed `diffuse or sharpen' on GPU, blended on GPU [full]
 814.899213 [dev_pixelpipe] took 0.011 secs (0.013 CPU) processed `color balance rgb' on GPU, blended on GPU [full]
 914.907159 [dev_pixelpipe] took 0.008 secs (0.008 CPU) processed `filmic rgb' on GPU, blended on GPU [full]
1014.940617 [dev_pixelpipe] took 0.033 secs (0.039 CPU) processed `output color profile' on GPU, blended on  [full]
1115.005967 [dev_pixelpipe] took 0.065 secs (0.057 CPU) processed `dithering' on CPU, blended on  [full]
1215.030966 [dev_pixelpipe] took 0.025 secs (0.036 CPU) processed `display encoding' on GPU, blended on  [full]
1315.031021 [pixelpipe] full internal pixel pipeline processing took 2.919 secs (2.963 CPU)
1415.031080 [dev_process_full] pipeline processing thread took 2.919 secs (2.963 CPU)
 189,3415 [dt_dev_process_image_job] loading image. took 0,000 secs (0,000 CPU)
 289,3440 [dev_pixelpipe] took 0,002 secs (0,001 CPU) initing base buffer [full]
 389,3544 [dev_pixelpipe] took 0,010 secs (0,008 CPU) [full] processed `rawprepare' on GPU, blended on GPU
 489,3573 [dev_pixelpipe] took 0,003 secs (0,001 CPU) [full] processed `temperature' on GPU, blended on GPU
 589,8611 [dev_pixelpipe] took 0,504 secs (0,511 CPU) [full] processed `highlights' on GPU, blended on GPU
 689,9200 [dev_pixelpipe] took 0,059 secs (0,023 CPU) [full] processed `demosaic' on GPU, blended on GPU
 789,9251 [dev_pixelpipe] took 0,005 secs (0,003 CPU) [full] processed `lens' on GPU, blended on GPU
 889,9346 [dev_pixelpipe] took 0,009 secs (0,003 CPU) [full] processed `exposure' on GPU, blended on GPU
 989,9480 [dev_pixelpipe] took 0,013 secs (0,003 CPU) [full] processed `colorin' on GPU, blended on GPU
1089,9576 [dt_ioppr_transform_image_colorspace_cl] IOP_CS_LAB-->IOP_CS_RGB took 0,009 secs (0,002 GPU) [channelmixerrgb]
1189,9736 [dev_pixelpipe] took 0,026 secs (0,008 CPU) [full] processed `channelmixerrgb' on GPU, blended on GPU
1294,9286 [dev_pixelpipe] took 4,955 secs (5,096 CPU) [full] processed `diffuse' on GPU, blended on GPU
1394,9528 [dev_pixelpipe] took 0,024 secs (0,007 CPU) [full] processed `colorbalancergb' on GPU, blended on GPU
1494,9836 [dev_pixelpipe] took 0,031 secs (0,009 CPU) [full] processed `filmicrgb' on GPU, blended on GPU
1594,9925 [dt_ioppr_transform_image_colorspace_cl] IOP_CS_RGB-->IOP_CS_LAB took 0,009 secs (0,000 GPU) [colorout]
1695,0700 [dev_pixelpipe] took 0,086 secs (0,028 CPU) [full] processed `colorout' on GPU, blended on GPU
1795,2184 [dev_pixelpipe] took 0,148 secs (0,093 CPU) [full] processed `dither' on CPU, blended on CPU
1895,2404 [dev_pixelpipe] took 0,022 secs (0,104 CPU) [full] processed `gamma' on CPU, blended on CPU
1995,2537 [dev_process_image] pixel pipeline took 5,912 secs (5,904 CPU) processing `2022-06-17__DSC00078.arw'
2095,2634 [dev_process_image] pixel pipeline took 0,010 secs (0,004 CPU) processing `2022-06-17__DSC00078.arw'

Comme Ansel traite les premières étapes du pipeline en pleine résolution, elles sont elles aussi mises en cache en pleine résolution. Cela signifie que le panoramique et le zoom récupèrent le cache en amont du module de rééchantillonnage initial et ne recalculent que l’aval. Le rendu prend donc 2,9 s. Darktable, en revanche, a un cache non fonctionnel, il recalcule donc tout depuis le début, ce qui lui prend 5,9 s pour afficher exactement la même chose. Cela représente une pénalité de performance de ×2 pour Darktable.

Panoramique à 100 %

 155.456510 [dev_pixelpipe] pipeline resync with history took 0.000 secs (0.000 CPU) for pipe full
 255.536149 [dev_pixelpipe] took 0.080 secs (0.121 CPU) processed `Initial resampling' on GPU, blended on  [full]
 355.541720 [dev_pixelpipe] took 0.005 secs (0.003 CPU) processed `exposure' on GPU, recycled cacheline, blended on GPU [full]
 455.551346 [dev_pixelpipe] took 0.009 secs (0.006 CPU) processed `input color profile' on GPU, recycled cacheline, blended on  [full]
 555.559669 [dev_pixelpipe] took 0.008 secs (0.005 CPU) processed `color calibration' on GPU, recycled cacheline, blended on GPU [full]
 658.290943 [dev_pixelpipe] took 2.731 secs (2.819 CPU) processed `diffuse or sharpen' on GPU, blended on GPU [full]
 758.310400 [dev_pixelpipe] took 0.016 secs (0.017 CPU) processed `color balance rgb' on GPU, recycled cacheline, blended on GPU [full]
 858.322514 [dev_pixelpipe] took 0.012 secs (0.014 CPU) processed `filmic rgb' on GPU, recycled cacheline, blended on GPU [full]
 958.364803 [dev_pixelpipe] took 0.042 secs (0.054 CPU) processed `output color profile' on GPU, blended on  [full]
1058.411730 [dev_pixelpipe] took 0.045 secs (0.052 CPU) processed `dithering' on CPU, recycled cacheline, blended on  [full]
1158.440432 [dev_pixelpipe] took 0.029 secs (0.042 CPU) processed `display encoding' on GPU, blended on  [full]
1258.440478 [pixelpipe] full internal pixel pipeline processing took 2.984 secs (3.133 CPU)
1358.440516 [dev_process_full] pipeline processing thread took 2.984 secs (3.133 CPU)
 1253,1082 [dev_pixelpipe] took 0,003 secs (0,004 CPU) initing base buffer [full]
 2253,1132 [dev_pixelpipe] took 0,005 secs (0,004 CPU) [full] processed `rawprepare' on GPU, blended on GPU
 3253,1152 [dev_pixelpipe] took 0,002 secs (0,001 CPU) [full] processed `temperature' on GPU, blended on GPU
 4253,5743 [dev_pixelpipe] took 0,459 secs (0,441 CPU) [full] processed `highlights' on GPU, blended on GPU
 5253,6257 [dev_pixelpipe] took 0,051 secs (0,022 CPU) [full] processed `demosaic' on GPU, blended on GPU
 6253,6307 [dev_pixelpipe] took 0,005 secs (0,002 CPU) [full] processed `lens' on GPU, blended on GPU
 7253,6394 [dev_pixelpipe] took 0,009 secs (0,003 CPU) [full] processed `exposure' on GPU, blended on GPU
 8253,6521 [dev_pixelpipe] took 0,013 secs (0,003 CPU) [full] processed `colorin' on GPU, blended on GPU
 9253,6610 [dt_ioppr_transform_image_colorspace_cl] IOP_CS_LAB-->IOP_CS_RGB took 0,008 secs (0,001 GPU) [channelmixerrgb]
10253,6760 [dev_pixelpipe] took 0,024 secs (0,005 CPU) [full] processed `channelmixerrgb' on GPU, blended on GPU
11258,6306 [dev_pixelpipe] took 4,954 secs (4,882 CPU) [full] processed `diffuse' on GPU, blended on GPU
12258,6548 [dev_pixelpipe] took 0,024 secs (0,005 CPU) [full] processed `colorbalancergb' on GPU, blended on GPU
13258,6857 [dev_pixelpipe] took 0,031 secs (0,009 CPU) [full] processed `filmicrgb' on GPU, blended on GPU
14258,6945 [dt_ioppr_transform_image_colorspace_cl] IOP_CS_RGB-->IOP_CS_LAB took 0,009 secs (0,000 GPU) [colorout]
15258,7725 [dev_pixelpipe] took 0,087 secs (0,034 CPU) [full] processed `colorout' on GPU, blended on GPU
16258,9198 [dev_pixelpipe] took 0,147 secs (0,086 CPU) [full] processed `dither' on CPU, blended on CPU
17258,9373 [dev_pixelpipe] took 0,017 secs (0,110 CPU) [full] processed `gamma' on CPU, blended on CPU
18258,9407 [dev_process_image] pixel pipeline took 5,859 secs (5,614 CPU) processing `2022-06-17__DSC00078.arw'
19258,9735 [dev_process_image] pixel pipeline took 0,033 secs (0,014 CPU) processing `2022-06-17__DSC00078.arw'

Le panoramique conduit à la même situation que le zoom : Ansel a l’image en pleine résolution en cache, donc nous recalculons à partir du rééchantillonnage initial, ce qui prend 3 s. Darktable est incapable de gérer correctement un cache, donc vous repartez pour un recalcul complet et 5,9 s d’ordinateur figé.

Aller-retour vers la table lumineuse

1184.536988 [dev_pixelpipe] took 0.000 secs (0.000 CPU) to load the image.
2184.574776 [dev_pixelpipe] pipeline resync with history took 0.019 secs (0.016 CPU) for pipe virtual-preview
3185.012395 [dev_pixelpipe] pipeline resync with history took 0.020 secs (0.017 CPU) for pipe virtual-preview
4185.046379 [dev_pixelpipe] pipeline resync with history took 0.000 secs (0.000 CPU) for pipe virtual-preview
5185.046424 [dev_pixelpipe] pipeline resync with history took 0.000 secs (0.000 CPU) for pipe virtual-preview
6185.049883 [dev_pixelpipe] pipeline resync with history took 0.026 secs (0.049 CPU) for pipe preview
7185.049931 [dev_pixelpipe] pipeline resync with history took 0.000 secs (0.000 CPU) for pipe preview
8185.068516 [dev_pixelpipe] pipeline resync with history took 0.019 secs (0.020 CPU) for pipe full
9185.068568 [dev_process_full] pipeline processing thread took 0.000 secs (0.000 CPU)
 13324,5295 [histogram] took 0,006 secs (0,005 CPU) scope draw
 23324,5837 [dt_dev_process_image_job] loading image. took 0,000 secs (0,000 CPU)
 33324,6371 [dev_process_image] pixel pipeline took 0,002 secs (0,001 CPU) processing `2022-06-17__DSC00078.arw'
 4
 53324,7352 [dt_dev_process_image_job] loading image. took 0,000 secs (0,000 CPU)
 63324,8074 [dev_pixelpipe] took 0,000 secs (0,000 CPU) initing base buffer [preview]
 73324,8155 [dev_pixelpipe] took 0,008 secs (0,005 CPU) [preview] processed `rawprepare' on GPU, blended on GPU
 83324,8165 [dev_pixelpipe] took 0,001 secs (0,001 CPU) [preview] processed `temperature' on GPU, blended on GPU
 93324,8178 [dev_pixelpipe] took 0,001 secs (0,003 CPU) [preview] processed `highlights' on GPU, blended on GPU
103324,8337 [dev_pixelpipe] took 0,016 secs (0,021 CPU) [preview] processed `demosaic' on GPU, blended on GPU
113324,8351 [dev_pixelpipe] took 0,001 secs (0,002 CPU) [preview] processed `lens' on GPU, blended on GPU
123324,8387 [dev_pixelpipe] took 0,003 secs (0,013 CPU) [preview] processed `exposure' on GPU, blended on GPU
133324,8440 [dev_pixelpipe] took 0,005 secs (0,015 CPU) [preview] processed `colorin' on GPU, blended on GPU
143324,8491 [dt_ioppr_transform_image_colorspace_cl] IOP_CS_LAB-->IOP_CS_RGB took 0,005 secs (0,012 GPU) [channelmixerrgb]
153324,8545 [dev_pixelpipe] took 0,010 secs (0,019 CPU) [preview] processed `channelmixerrgb' on GPU, blended on GPU
163325,6440 [dev_pixelpipe] took 0,789 secs (0,921 CPU) [preview] processed `diffuse' on GPU, blended on GPU
173325,6482 [dev_pixelpipe] took 0,004 secs (0,003 CPU) [preview] processed `colorbalancergb' on GPU, blended on GPU
183325,6534 [dev_pixelpipe] took 0,005 secs (0,003 CPU) [preview] processed `filmicrgb' on GPU, blended on GPU
193325,6569 [dt_ioppr_transform_image_colorspace_cl] IOP_CS_RGB-->IOP_CS_LAB took 0,003 secs (0,002 GPU) [colorout]
203325,6747 [dev_pixelpipe] took 0,021 secs (0,008 CPU) [preview] processed `colorout' on GPU, blended on GPU
213325,6979 [dev_pixelpipe] took 0,023 secs (0,009 CPU) [preview] processed `dither' on CPU, blended on CPU
223325,7061 [dev_pixelpipe] took 0,008 secs (0,029 CPU) [preview] processed `gamma' on CPU, blended on CPU
233325,7263 [dt_ioppr_transform_image_colorspace_rgb] `Système' -> `sRVB' took 0,020 secs (0,068 CPU) [final histogram]
243325,7357 [dev_process_image] pixel pipeline took 0,935 secs (1,152 CPU) processing `2022-06-17__DSC00078.arw'

Le cache du pipeline d’Ansel survit aux pipelines eux-mêmes, ce qui signifie que sortir de la chambre noire puis y revenir est quasi instantané : il suffit de 60 ms pour resynchroniser tous les pipelines avec l’historique de développement et constater, via leur somme de contrôle, que la sortie du pipeline est déjà en cache. Notez qu’il n’y a ici aucun traitement spécial, c’est simplement le comportement générique du cache.

Pour Darktable, cela marche presque pour l’image principale, mais pas pour l’aperçu, ce qui vous vaut 1 s de recalcul inutile. Étant donné que cela fonctionne sur un pipeline mais pas sur l’autre, je parierais que c’est géré par des cas particuliers. Comparé à Ansel, cela représente une pénalité de performance de ×1,75.

Désactiver un module au milieu du pipeline

J’ai désactivé diffusion ou netteté en étant zoomé à la taille « remplir ».

 1124.206298 [dev_pixelpipe] pipeline resync with history took 0.011 secs (0.010 CPU) for pipe virtual-preview
 2124.237640 [dev_pixelpipe] pipeline resync with history took 0.011 secs (0.028 CPU) for pipe preview
 3124.290940 [dev_pixelpipe] took 0.023 secs (0.028 CPU) processed `color balance rgb' on GPU, recycled cacheline, blended on GPU [preview]
 4124.303176 [dev_pixelpipe] took 0.012 secs (0.012 CPU) processed `filmic rgb' on GPU, recycled cacheline, blended on GPU [preview]
 5124.331334 [dev_pixelpipe] took 0.028 secs (0.030 CPU) processed `output color profile' on GPU, blended on  [preview]
 6124.370193 [darkroom] took 0.007 secs (0.011 CPU) redraw
 7124.382425 [dev_pixelpipe] took 0.051 secs (0.055 CPU) processed `dithering' on CPU, blended on  [preview]
 8124.410053 [dev_pixelpipe] took 0.028 secs (0.028 CPU) processed `display encoding' on GPU, blended on  [preview]
 9124.410088 [pixelpipe] preview internal pixel pipeline processing took 0.172 secs (0.197 CPU)
10124.410115 [dev_process_preview] pipeline processing thread took 0.172 secs (0.197 CPU)
11124.433186 [dev_pixelpipe] pipeline resync with history took 0.013 secs (0.017 CPU) for pipe full
12124.433286 [dev_process_full] pipeline processing thread took 0.000 secs (0.000 CPU)
 1300,9771 [dt_dev_process_image_job] loading image. took 0,000 secs (0,000 CPU)
 2301,0564 [dev_pixelpipe] took 0,016 secs (0,028 CPU) initing base buffer [full]
 3301,0936 [dev_pixelpipe] took 0,037 secs (0,037 CPU) [full] processed `rawprepare' on GPU, blended on GPU
 4301,1102 [dev_pixelpipe] took 0,017 secs (0,017 CPU) [full] processed `temperature' on GPU, blended on GPU
 5304,8700 [dev_pixelpipe] took 3,760 secs (3,786 CPU) [full] processed `highlights' on GPU with tiling, blended on CPU
 6305,2630 [resample_cl] plan 0,001 secs (0,000 CPU) resample 0,010 secs (0,001 CPU)
 7305,3543 [dev_pixelpipe] took 0,484 secs (0,284 CPU) [full] processed `demosaic' on GPU, blended on GPU
 8305,3648 [dev_pixelpipe] took 0,010 secs (0,002 CPU) [full] processed `lens' on GPU, blended on GPU
 9305,3764 [dev_pixelpipe] took 0,012 secs (0,001 CPU) [full] processed `exposure' on GPU, blended on GPU
10305,4083 [dev_pixelpipe] took 0,032 secs (0,007 CPU) [full] processed `colorin' on GPU, blended on GPU
11305,4220 [dt_ioppr_transform_image_colorspace_cl] IOP_CS_LAB-->IOP_CS_RGB took 0,013 secs (0,001 GPU) [channelmixerrgb]
12305,4460 [dev_pixelpipe] took 0,038 secs (0,008 CPU) [full] processed `channelmixerrgb' on GPU, blended on GPU
13307,1388 [dev_pixelpipe] took 1,693 secs (1,645 CPU) [full] processed `diffuse' on GPU, blended on GPU
14307,1500 [dev_pixelpipe] took 0,011 secs (0,003 CPU) [full] processed `colorbalancergb' on GPU, blended on GPU
15307,1612 [dev_pixelpipe] took 0,011 secs (0,003 CPU) [full] processed `filmicrgb' on GPU, blended on GPU
16307,1675 [dt_ioppr_transform_image_colorspace_cl] IOP_CS_RGB-->IOP_CS_LAB took 0,006 secs (0,001 GPU) [colorout]
17307,2185 [dev_pixelpipe] took 0,057 secs (0,020 CPU) [full] processed `colorout' on GPU, blended on GPU
18307,3095 [dev_pixelpipe] took 0,091 secs (0,056 CPU) [full] processed `dither' on CPU, blended on CPU
19307,3235 [dev_pixelpipe] took 0,014 secs (0,065 CPU) [full] processed `gamma' on CPU, blended on CPU
20307,3333 [dev_process_image] pixel pipeline took 6,293 secs (5,969 CPU) processing `2022-06-17__DSC00078.arw'
21307,3843 [dev_process_image] pixel pipeline took 0,051 secs (0,018 CPU) processing `2022-06-17__DSC00078.arw'
22
23307,4258 [dt_dev_process_image_job] loading image. took 0,000 secs (0,000 CPU)
24307,4875 [dev_pixelpipe] took 0,000 secs (0,000 CPU) initing base buffer [preview]
25307,4895 [dev_pixelpipe] took 0,002 secs (0,002 CPU) [preview] processed `rawprepare' on GPU, blended on GPU
26307,4906 [dev_pixelpipe] took 0,001 secs (0,001 CPU) [preview] processed `temperature' on GPU, blended on GPU
27307,5609 [dev_pixelpipe] took 0,070 secs (0,072 CPU) [preview] processed `highlights' on GPU, blended on GPU
28307,5722 [dev_pixelpipe] took 0,011 secs (0,005 CPU) [preview] processed `demosaic' on GPU, blended on GPU
29307,5740 [dev_pixelpipe] took 0,002 secs (0,000 CPU) [preview] processed `lens' on GPU, blended on GPU
30307,5765 [dev_pixelpipe] took 0,002 secs (0,001 CPU) [preview] processed `exposure' on GPU, blended on GPU
31307,5798 [dev_pixelpipe] took 0,003 secs (0,002 CPU) [preview] processed `colorin' on GPU, blended on GPU
32307,5827 [dt_ioppr_transform_image_colorspace_cl] IOP_CS_LAB-->IOP_CS_RGB took 0,002 secs (0,002 GPU) [channelmixerrgb]
33307,5864 [dev_pixelpipe] took 0,007 secs (0,003 CPU) [preview] processed `channelmixerrgb' on GPU, blended on GPU
34307,9576 [dev_pixelpipe] took 0,371 secs (0,356 CPU) [preview] processed `diffuse' on GPU, blended on GPU
35307,9613 [dev_pixelpipe] took 0,003 secs (0,002 CPU) [preview] processed `colorbalancergb' on GPU, blended on GPU
36307,9659 [dev_pixelpipe] took 0,005 secs (0,000 CPU) [preview] processed `filmicrgb' on GPU, blended on GPU
37307,9686 [dt_ioppr_transform_image_colorspace_cl] IOP_CS_RGB-->IOP_CS_LAB took 0,002 secs (0,000 GPU) [colorout]
38307,9847 [dev_pixelpipe] took 0,019 secs (0,008 CPU) [preview] processed `colorout' on GPU, blended on GPU
39308,0043 [dev_pixelpipe] took 0,020 secs (0,011 CPU) [preview] processed `dither' on CPU, blended on CPU
40308,0103 [dev_pixelpipe] took 0,006 secs (0,023 CPU) [preview] processed `gamma' on CPU, blended on CPU
41308,0244 [dt_ioppr_transform_image_colorspace_rgb] `Système' -> `sRVB' took 0,014 secs (0,081 CPU) [final histogram]
42308,0353 [dev_process_image] pixel pipeline took 0,553 secs (0,604 CPU) processing `2022-06-17__DSC00078.arw'

Une fois encore, le cache d’Ansel fonctionne tout simplement : désactiver diffusion ou netteté ne conduit à recalculer que les modules situés en aval, ce qui prend 172 ms. Une fois encore, le cache de Darktable ne fonctionne pas, ce qui conduit à recalculer l’ensemble des 2 pipelines (aperçu et principal), pour un coût total de 6,9 s. Cela représente une pénalité de performance de ×40, tout de même ! Tous les pseudo gains de +15 % de Ralf Brown deviennent donc totalement insignifiants. Mais bon ! Continuez à chercher vos clés là où vous voyez vos pieds plutôt que là où vous les avez perdues ! .

Pipette couleur dans un module

Nous réglons la balance des blancs dans calibration des couleurs à l’aide de la pipette, au niveau de zoom « ajuster à la fenêtre ».

 1colorpicker stats reading took 0.020 secs (0.088 CPU)
 2colorpicker stats reading took 0.018 secs (0.087 CPU)
 312.128030 [dev_pixelpipe] pipeline resync with history took 0.024 secs (0.037 CPU) for pipe virtual-preview
 412.137829 [dev_pixelpipe] pipeline resync with history took 0.024 secs (0.056 CPU) for pipe preview
 512.157665 [dev_pixelpipe] took 0.020 secs (0.048 CPU) processed `color calibration' on GPU, blended on GPU [preview]
 613.327326 [dev_pixelpipe] took 1.170 secs (1.231 CPU) processed `diffuse or sharpen' on GPU, blended on GPU [preview]
 713.336618 [dev_pixelpipe] took 0.009 secs (0.008 CPU) processed `color balance rgb' on GPU, recycled cacheline, blended on GPU [preview]
 813.345393 [dev_pixelpipe] took 0.009 secs (0.007 CPU) processed `filmic rgb' on GPU, recycled cacheline, blended on GPU [preview]
 913.368053 [dev_pixelpipe] took 0.023 secs (0.030 CPU) processed `output color profile' on GPU, blended on  [preview]
1013.418643 [dev_pixelpipe] took 0.050 secs (0.061 CPU) processed `dithering' on CPU, blended on  [preview]
1113.435846 [dev_pixelpipe] took 0.017 secs (0.024 CPU) processed `display encoding' on GPU, blended on  [preview]
1213.435919 [pixelpipe] preview internal pixel pipeline processing took 1.298 secs (1.408 CPU)
1313.435949 [dev_process_preview] pipeline processing thread took 1.298 secs (1.408 CPU)
1413.526316 [dev_pixelpipe] pipeline resync with history took 0.080 secs (0.376 CPU) for pipe full
1513.526443 [dev_process_full] pipeline processing thread took 0.000 secs (0.000 CPU)
16colorpicker stats reading took 0.185 secs (0.741 CPU)
17image colorspace transform RGB-->RGB took 0.000 secs (0.000 CPU) [[histogram] sample swatch]
18image colorspace transform RGB-->RGB took 0.000 secs (0.000 CPU) [[histogram] sample swatch]
19image colorspace transform RGB-->RGB took 0.000 secs (0.000 CPU) [[histogram] sample swatch]
 116,1190 [dt_dev_process_image_job] loading image. took 0,000 secs (0,000 CPU)
 216,1364 dt_color_picker_helper stats reading 4 channels (filters 2492765332) cst 2 -> 2 size 1118880 denoised 0 took 0,001 secs (0,005 CPU)
 316,1396 [dev_pixelpipe] took 0,020 secs (0,025 CPU) [preview] processed `channelmixerrgb' on GPU, blended on GPU
 416,2071 [dt_dev_process_image_job] loading image. took 0,000 secs (0,000 CPU)
 516,6064 [dev_pixelpipe] took 0,467 secs (0,567 CPU) [preview] processed `diffuse' on GPU, blended on GPU
 616,6106 [dev_pixelpipe] took 0,004 secs (0,004 CPU) [preview] processed `colorbalancergb' on GPU, blended on GPU
 716,6160 [dev_pixelpipe] took 0,005 secs (0,001 CPU) [preview] processed `filmicrgb' on GPU, blended on GPU
 816,6192 [dt_ioppr_transform_image_colorspace_cl] IOP_CS_RGB-->IOP_CS_LAB took 0,003 secs (0,002 GPU) [colorout]
 916,6364 [dev_pixelpipe] took 0,020 secs (0,010 CPU) [preview] processed `colorout' on GPU, blended on GPU
1016,6513 [dev_pixelpipe] took 0,015 secs (0,005 CPU) [preview] processed `dither' on CPU, blended on CPU
1116,6572 [dev_pixelpipe] took 0,006 secs (0,023 CPU) [preview] processed `gamma' on CPU, blended on CPU
1216,6584 dt_color_picker_helper stats reading 4 channels (filters 2492765332) cst 2 -> 2 size 1118880 denoised 0 took 0,001 secs (0,005 CPU)
1316,6586 [dt_ioppr_transform_image_colorspace] IOP_CS_RGB-->IOP_CS_LAB took 0,000 secs (0,000 CPU) [gamma]
1416,6587 [dt_ioppr_transform_image_colorspace_rgb] `Système' -> `sRVB' took 0,000 secs (0,000 CPU) [primary picker]
1516,6754 [dt_ioppr_transform_image_colorspace_rgb] `Système' -> `sRVB' took 0,017 secs (0,063 CPU) [final histogram]
1616,6843 [histogram] took 0,026 secs (0,093 CPU) final waveform
1716,6847 [dev_process_image] pixel pipeline took 0,566 secs (0,733 CPU) processing `2022-06-17__DSC00078.arw'
18
1916,6854 [dt_dev_process_image_job] loading image. took 0,000 secs (0,000 CPU)
2020,3395 [dev_pixelpipe] took 3,606 secs (3,778 CPU) [full] processed `highlights' on GPU with tiling, blended on CPU
2120,7350 [resample_cl] plan 0,001 secs (0,000 CPU) resample 0,010 secs (0,001 CPU)
2220,8263 [dev_pixelpipe] took 0,487 secs (0,285 CPU) [full] processed `demosaic' on GPU, blended on GPU
2320,8389 [dev_pixelpipe] took 0,012 secs (0,002 CPU) [full] processed `lens' on GPU, blended on GPU
2420,8505 [dev_pixelpipe] took 0,012 secs (0,002 CPU) [full] processed `exposure' on GPU, blended on GPU
2520,8812 [dev_pixelpipe] took 0,031 secs (0,008 CPU) [full] processed `colorin' on GPU, blended on GPU
2620,8972 [dt_ioppr_transform_image_colorspace_cl] IOP_CS_LAB-->IOP_CS_RGB took 0,016 secs (0,004 GPU) [channelmixerrgb]
2720,9551 [dev_pixelpipe] took 0,074 secs (0,023 CPU) [full] processed `channelmixerrgb' on GPU, blended on GPU
2821,8137 [dev_pixelpipe] took 0,000 secs (0,000 CPU) initing base buffer [preview]
2921,8160 [dev_pixelpipe] took 0,001 secs (0,005 CPU) [preview] processed `rawprepare' on CPU, blended on CPU
3021,8173 [dev_pixelpipe] took 0,001 secs (0,001 CPU) [preview] processed `temperature' on CPU, blended on CPU
3121,9879 [dev_pixelpipe] took 0,171 secs (0,758 CPU) [preview] processed `highlights' on CPU, blended on CPU
3222,0020 [dev_pixelpipe] took 0,014 secs (0,080 CPU) [preview] processed `demosaic' on CPU, blended on CPU
3322,0103 [dev_pixelpipe] took 0,008 secs (0,016 CPU) [preview] processed `lens' on CPU, blended on CPU
3422,0127 [dev_pixelpipe] took 0,002 secs (0,014 CPU) [preview] processed `exposure' on CPU, blended on CPU
3522,0155 [dev_pixelpipe] took 0,003 secs (0,015 CPU) [preview] processed `colorin' on CPU, blended on CPU
3622,0172 [dt_ioppr_transform_image_colorspace] IOP_CS_LAB-->IOP_CS_RGB took 0,002 secs (0,009 CPU) [channelmixerrgb]
3722,0457 dt_color_picker_helper stats reading 4 channels (filters 2492765332) cst 2 -> 2 size 1118880 denoised 0 took 0,002 secs (0,013 CPU)
3822,0458 [dev_pixelpipe] took 0,030 secs (0,180 CPU) [preview] processed `channelmixerrgb' on CPU, blended on CPU
3922,6461 [dev_pixelpipe] took 1,691 secs (5,549 CPU) [full] processed `diffuse' on GPU, blended on GPU
4022,6595 [dev_pixelpipe] took 0,013 secs (0,079 CPU) [full] processed `colorbalancergb' on GPU, blended on GPU
4122,6725 [dev_pixelpipe] took 0,013 secs (0,073 CPU) [full] processed `filmicrgb' on GPU, blended on GPU
4222,6824 [dt_ioppr_transform_image_colorspace_cl] IOP_CS_RGB-->IOP_CS_LAB took 0,007 secs (0,041 GPU) [colorout]
4322,7443 [dev_pixelpipe] took 0,072 secs (0,398 CPU) [full] processed `colorout' on GPU, blended on GPU
4422,8624 [dev_pixelpipe] took 0,118 secs (0,664 CPU) [full] processed `dither' on CPU, blended on CPU
4522,8878 [dev_pixelpipe] took 0,025 secs (0,172 CPU) [full] processed `gamma' on CPU, blended on CPU
4622,8894 [dev_process_image] pixel pipeline took 6,637 secs (11,596 CPU) processing `2022-06-17__DSC00078.arw'
4723,4204 [dev_pixelpipe] took 1,374 secs (8,556 CPU) [preview] processed `diffuse' on CPU, blended on CPU
4823,5271 [dev_pixelpipe] took 0,106 secs (0,617 CPU) [preview] processed `colorbalancergb' on CPU, blended on CPU
4923,5788 [dev_pixelpipe] took 0,051 secs (0,291 CPU) [preview] processed `filmicrgb' on CPU, blended on CPU
5023,5817 [dt_ioppr_transform_image_colorspace] IOP_CS_RGB-->IOP_CS_LAB took 0,003 secs (0,011 CPU) [colorout]
5123,5870 [dev_pixelpipe] took 0,008 secs (0,037 CPU) [preview] processed `colorout' on CPU, blended on CPU
5223,5913 [dev_pixelpipe] took 0,004 secs (0,004 CPU) [preview] processed `dither' on CPU, blended on CPU
5323,5964 [dev_pixelpipe] took 0,005 secs (0,021 CPU) [preview] processed `gamma' on CPU, blended on CPU
5423,5973 dt_color_picker_helper stats reading 4 channels (filters 2492765332) cst 2 -> 2 size 1118880 denoised 0 took 0,001 secs (0,006 CPU)
5523,5976 [dt_ioppr_transform_image_colorspace] IOP_CS_RGB-->IOP_CS_LAB took 0,000 secs (0,000 CPU) [gamma]
5623,5977 [dt_ioppr_transform_image_colorspace_rgb] `Système' -> `sRVB' took 0,000 secs (0,000 CPU) [primary picker]
5723,6099 [dt_ioppr_transform_image_colorspace_rgb] `Système' -> `sRVB' took 0,012 secs (0,067 CPU) [final histogram]
5823,6215 [histogram] took 0,024 secs (0,097 CPU) final waveform
5923,6219 [dev_process_image] pixel pipeline took 6,889 secs (15,651 CPU) processing `2022-06-17__DSC00078.arw'

Ansel n’a pas besoin de relancer un rendu de pipeline pour échantillonner avec la pipette, car l’entrée et la sortie du module sont encore en cache. L’échantillonnage prend donc 40 ms au total. Ensuite, les nouveaux paramètres sont enregistrés dans le module, ce qui modifie l’historique et déclenche un recalcul du module calibration des couleurs ainsi que de tous les modules situés en aval. Temps total passé : 1,4 s.

Darktable doit relancer un nouveau rendu de pipeline pour échantillonner avec la pipette, parce que pourquoi pas ? Cela prend 0,6 s. Vous serez ravi d’apprendre que Darktable échantillonne la couleur sur GPU, ce qui ne prend qu'1 ms, au milieu de ces 0,6 s de rendu de pipeline, tandis qu’Ansel échantillonne sur CPU en 20 ms. Ensuite, les paramètres de la pipette sont enregistrés dans l’historique, ce qui relance un recalcul complet des 2 pipelines. Temps total passé : 7,5 s.

Cela représente une pénalité de ×5,4 pour Darktable, tout cela parce qu’ils n’améliorent pas leurs pipelines mais poursuivent toutes les fausses pistes possibles.

Export en pleine résolution

Nous exportons après avoir ouvert l’image dans la chambre noire. C’est important. Dans Darktable, l’échantillonnage « haute qualité » est utilisé ; dans Ansel, c’est le seul mode.

 118.917740 [dev_pixelpipe] took 0.000 secs (0.000 CPU) to load the image.
 218.970877 [export] creating pixelpipe took 0.054 secs (0.074 CPU)
 319.215438 [dev_pixelpipe] took 0.244 secs (0.222 CPU) processed `exposure' on GPU, blended on GPU [export]
 419.241544 [dev_pixelpipe] took 0.026 secs (0.025 CPU) processed `input color profile' on GPU, blended on  [export]
 519.267887 [dev_pixelpipe] took 0.026 secs (0.026 CPU) processed `color calibration' on GPU, blended on GPU [export]
 656.171662 [dev_pixelpipe] took 36.904 secs (36.592 CPU) processed `diffuse or sharpen' on GPU with tiling, blended on CPU [export]
 756.364588 [dev_pixelpipe] took 0.193 secs (0.156 CPU) processed `color balance rgb' on GPU, blended on GPU [export]
 857.293819 [dev_pixelpipe] took 0.929 secs (0.468 CPU) processed `filmic rgb' on GPU with tiling, blended on CPU [export]
 957.516252 [dev_pixelpipe] took 0.222 secs (0.176 CPU) processed `output color profile' on GPU, blended on  [export]
1057.534628 [dev_pixelpipe] took 0.018 secs (0.017 CPU) processed `Final resampling' on GPU, blended on  [export]
1158.462789 [dev_pixelpipe] took 0.928 secs (0.544 CPU) processed `dithering' on CPU, blended on  [export]
1258.462829 [pixelpipe] export internal pixel pipeline processing took 39.492 secs (38.227 CPU)
1358.462898 [dev_process_export] pixel pipeline processing thread took 39.492 secs (38.227 CPU)
14[export_job] exported to `/home/aurelienpierre/Téléchargements/2022-06-17__DSC00078_08.jpg'
 1526,9861 [export] creating pixelpipe took 0,063 secs (0,091 CPU)
 2526,9862 [dev_pixelpipe] took 0,000 secs (0,000 CPU) initing base buffer [export]
 3527,0072 [dev_pixelpipe] took 0,021 secs (0,020 CPU) [export] processed `rawprepare' on GPU, blended on GPU
 4527,0237 [dev_pixelpipe] took 0,016 secs (0,007 CPU) [export] processed `temperature' on GPU, blended on GPU
 5530,7560 [dev_pixelpipe] took 3,732 secs (3,868 CPU) [export] processed `highlights' on GPU with tiling, blended on CPU
 6531,1444 [dev_pixelpipe] took 0,388 secs (0,201 CPU) [export] processed `demosaic' on GPU, blended on GPU
 7531,9899 [dev_pixelpipe] took 0,845 secs (0,493 CPU) [export] processed `lens' on GPU with tiling, blended on CPU
 8532,1378 [dev_pixelpipe] took 0,148 secs (0,139 CPU) [export] processed `exposure' on GPU, blended on GPU
 9532,1968 [dev_pixelpipe] took 0,059 secs (0,047 CPU) [export] processed `colorin' on GPU, blended on GPU
10532,2287 [dt_ioppr_transform_image_colorspace_cl] IOP_CS_LAB-->IOP_CS_RGB took 0,031 secs (0,019 GPU) [channelmixerrgb]
11532,3206 [dev_pixelpipe] took 0,124 secs (0,089 CPU) [export] processed `channelmixerrgb' on GPU, blended on GPU
12575,5645 [dev_pixelpipe] took 43,244 secs (43,220 CPU) [export] processed `diffuse' on GPU with tiling, blended on CPU
13575,7422 [dev_pixelpipe] took 0,178 secs (0,167 CPU) [export] processed `colorbalancergb' on GPU, blended on GPU
14576,2975 [dev_pixelpipe] took 0,555 secs (0,474 CPU) [export] processed `filmicrgb' on GPU with tiling, blended on CPU
15576,3897 [resample_cl] took 0,000 secs (0,000 CPU) 1:1 copy/crop of 7968x5320 pixels
16576,4081 [dev_pixelpipe] took 0,111 secs (0,101 CPU) [export] processed `finalscale' on GPU, blended on GPU
17576,4403 [dt_ioppr_transform_image_colorspace_cl] IOP_CS_RGB-->IOP_CS_LAB took 0,032 secs (0,020 GPU) [colorout]
18576,5618 [dev_pixelpipe] took 0,154 secs (0,120 CPU) [export] processed `colorout' on GPU, blended on GPU
19577,0202 [dev_pixelpipe] took 0,458 secs (0,456 CPU) [export] processed `dither' on CPU, blended on CPU
20577,0204 [dev_process_export] pixel pipeline processing took 50,034 secs (49,403 CPU)
21578,2407 [export_job] exported to `/home/aurelienpierre/Téléchargements/2022-06-17__DSC00078_06.jpg'

Dans Ansel, puisque nous avons ouvert l’image auparavant dans la chambre noire, les premières étapes du pipeline sont encore en cache, donc l’export démarre au module exposition et prend 39,5 s. Dans Darktable, eh bien… comme d’habitude : c’est un recalcul complet du pipeline qui prend 50 s.

Exporter à nouveau en demi-résolution

Nous exportons en demi-résolution juste après avoir exporté en pleine résolution.

165.601240 [dev_pixelpipe] took 0.000 secs (0.000 CPU) to load the image.
265.658874 [export] creating pixelpipe took 0.060 secs (0.079 CPU)
365.912677 [dev_pixelpipe] took 0.254 secs (0.233 CPU) processed `output color profile' on GPU, blended on  [export]
465.972780 [dev_pixelpipe] took 0.060 secs (0.073 CPU) processed `Final resampling' on GPU, blended on  [export]
566.160165 [dev_pixelpipe] took 0.187 secs (0.238 CPU) processed `dithering' on CPU, blended on  [export]
666.160184 [pixelpipe] export internal pixel pipeline processing took 0.501 secs (0.544 CPU)
766.160202 [dev_process_export] pixel pipeline processing thread took 0.501 secs (0.545 CPU)
8[export_job] exported to `/home/aurelienpierre/Téléchargements/2022-06-17__DSC00078_09.jpg'
 1607,7584 [export] creating pixelpipe took 0,064 secs (0,088 CPU)
 2607,7586 [dev_pixelpipe] took 0,000 secs (0,000 CPU) initing base buffer [export]
 3607,7918 [dev_pixelpipe] took 0,033 secs (0,028 CPU) [export] processed `rawprepare' on GPU, blended on GPU
 4607,8083 [dev_pixelpipe] took 0,016 secs (0,007 CPU) [export] processed `temperature' on GPU, blended on GPU
 5611,5555 [dev_pixelpipe] took 3,747 secs (3,865 CPU) [export] processed `highlights' on GPU with tiling, blended on CPU
 6611,9454 [dev_pixelpipe] took 0,390 secs (0,201 CPU) [export] processed `demosaic' on GPU, blended on GPU
 7612,7083 [dev_pixelpipe] took 0,763 secs (0,462 CPU) [export] processed `lens' on GPU with tiling, blended on CPU
 8612,8559 [dev_pixelpipe] took 0,148 secs (0,137 CPU) [export] processed `exposure' on GPU, blended on GPU
 9612,9149 [dev_pixelpipe] took 0,059 secs (0,045 CPU) [export] processed `colorin' on GPU, blended on GPU
10612,9468 [dt_ioppr_transform_image_colorspace_cl] IOP_CS_LAB-->IOP_CS_RGB took 0,032 secs (0,021 GPU) [channelmixerrgb]
11613,0390 [dev_pixelpipe] took 0,124 secs (0,090 CPU) [export] processed `channelmixerrgb' on GPU, blended on GPU
12656,1897 [dev_pixelpipe] took 43,151 secs (42,852 CPU) [export] processed `diffuse' on GPU with tiling, blended on CPU
13656,3673 [dev_pixelpipe] took 0,178 secs (0,167 CPU) [export] processed `colorbalancergb' on GPU, blended on GPU
14656,9163 [dev_pixelpipe] took 0,549 secs (0,467 CPU) [export] processed `filmicrgb' on GPU with tiling, blended on CPU
15657,0078 [resample_cl] plan 0,001 secs (0,000 CPU) resample 0,001 secs (0,000 CPU)
16657,1070 [dev_pixelpipe] took 0,191 secs (0,181 CPU) [export] processed `finalscale' on GPU, blended on GPU
17657,1233 [dt_ioppr_transform_image_colorspace_cl] IOP_CS_RGB-->IOP_CS_LAB took 0,016 secs (0,004 GPU) [colorout]
18657,1792 [dev_pixelpipe] took 0,072 secs (0,030 CPU) [export] processed `colorout' on GPU, blended on GPU
19657,3186 [dev_pixelpipe] took 0,139 secs (0,136 CPU) [export] processed `dither' on CPU, blended on CPU
20657,3187 [dev_process_export] pixel pipeline processing took 49,560 secs (48,669 CPU)
21657,8107 [export_job] exported to `/home/aurelienpierre/Téléchargements/2022-06-17__DSC00078_07.jpg'

Comme Ansel exporte toujours en pleine résolution jusqu’au module de rééchantillonnage final (le mode appelé « haute qualité » dans Darktable), le pipeline reste pour l’essentiel identique quelle que soit la taille finale. Comme le cache d’Ansel fonctionne, l’image pleine résolution précédemment exportée est encore en cache, donc nous ne recalculons que les dernières étapes : profil de couleur de sortie, réduction de résolution, tramage. Cela prend 0,5 s.

Darktable, comme d’habitude, ne gère pas ce qu’il a calculé auparavant, donc vous repartez pour un nouvel export complet de ~50 s. Autrement dit, une pénalité de performance de ×100.

Notez que le cache du pipeline d’Ansel possède un ramasse-miettes qui vide toutes les lignes de cache inutilisées depuis plus de 5 minutes, donc cela ne fonctionnerait plus si vous attendiez trop longtemps.

Conclusion

Il est assez hilarant de lire, sur des forums de tech-bros comme discuss.pixls.us , que des gens font davantage confiance à Darktable qu’à Ansel parce que le premier compte beaucoup plus de singes gesticulant dans tous les sens et faisant semblant de travailler, ce qui suffit apparemment à produire un sentiment totalement erroné de fiabilité et de confiance. C’est la preuve ultime que les gens n’ont aucune idée de ce qui se passe dans le code. Mais c’est une maladie capitaliste que de confondre agitation et travail, et un travail qui ne crée aucune valeur peut tout de même être vendu comme du progrès avec le marketing approprié.

Je ne fais pas dans l’opinion, je fais des benchmarks. Les chiffres que je publie ici peuvent être entièrement reproduits :

Pénalité de performance de Darktable par cas de benchmark
  • les modules pris individuellement sont en moyenne 26 % plus rapides sur Ansel CPU que sur Darktable CPU,
  • les modules pris individuellement sont en moyenne 61 % plus rapides sur Ansel GPU que sur Darktable GPU,
  • les pipelines complets sont entre ×1,16 et ×100 plus rapides sur Ansel GPU que sur Darktable GPU, parce qu’Ansel a un cache fonctionnel,
  • Ansel n’est pas seulement plus rapide, il est aussi plus économe en énergie, car moins de choses sont recalculées.

Tout cela a été obtenu en retirant le code d’interface du code de rendu du pipeline, en rendant le cycle de vie des données plus clair, plus simple et plus robuste, et non en ajoutant davantage de cas particuliers et de contournements. Ansel est capable d’utiliser bien plus de RAM que Darktable, tout en tenant la comptabilité nécessaire pour savoir combien il utilise réellement et s’il doit commencer à recycler de la mémoire.

Tout cela prouve que « l’équipe » Darktable n’améliore pas le logiciel et que son absence de gestion nuit activement au logiciel. De nombreuses « optimisations » ont été annoncées dans les notes de version depuis 2022. Et pourtant, leur logiciel reste une bête d’inefficacité douloureusement lente.

Notez que les optimisations module par module réalisées dans Darktable peuvent être rétroportées dans Ansel, même si, au vu de ce qui est montré ici, toutes ne semblent pas correspondre à de vrais gains de vitesse. Certaines l’ont déjà été. En revanche, Darktable ne peut pas rétroporter l’architecture de pipeline d’Ansel, d’où provient l’essentiel de nos gains.

Au cours de ce travail, j’ai aussi perdu foi en Vkdt . Vkdt est une nouvelle tentative, par le fondateur de Darktable Johannes Hanika, de redémarrer un logiciel photo RAW depuis zéro et de le faire fonctionner entièrement sur GPU via Vulkan. Mais après 4 ans passés à nettoyer la merde des autres, il y a deux ou trois choses que j’ai apprises :

  1. Les ralentissements de Darktable ne sont pas à mettre sur le dos de la pile GTK ni de la pile OpenCL. Certes, elles ne sont pas extraordinaires, mais cela se gère. Le problème vient d’erreurs de programmation et d’une mauvaise conception qui n’a pas été réévaluée à mesure que les besoins et les exigences du logiciel évoluaient.
  2. Une conception orientée cache permet de masquer de nombreuses latences en évitant les recalculs, et un flux de travail entièrement sur GPU se heurtera bientôt aux limites mémoire des GPU.
  3. Les GPU deviennent de plus en plus chers, après la ruée sur les cryptomonnaies et maintenant la ruée sur l’IA.
  4. Ce qui a tué Darktable, c’est l’absence de gestion et l’absence de vision, une culture du « laissez-faire » et du hasard joyeux qui ne fonctionne qu’avec une poignée de développeurs se connaissant tous. Vkdt prend la même direction. Si le code et les fonctionnalités peuvent croître sans priorités ni resserrement des API, c’est un train qui déraille au ralenti. « plus récent = meilleur » n’est qu’un état transitoire. Le véritable défi, c’est de gérer la croissance.

Du point de vue du développeur, Darktable souffre de 3 maladies principales :

  1. Ses développeurs manquent complètement, et depuis longtemps, des capacités d’abstraction nécessaires pour distinguer mentalement ce qui relève de la couche d’interface graphique et ce qui relève de la couche backend. Ainsi, on trouve des états GUI/GTK incrustés au plus profond du backend, ce qui est d’autant plus agaçant que darktable-cli fonctionne sans interface et oblige à gérer les deux modes. Mais on retrouve aussi sans cesse des problèmes d’interface « résolus » dans le backend, et inversement. Cela déclenche de vrais problèmes de fiabilité, en plus de rendre l’ensemble compliqué, illisible et emmêlé.
  2. Ses développeurs ne comprennent pas le concept d’API privées et d’encapsulation : toutes les structures de données sont publiques, tous les fichiers d’en-tête sont importés dans l’ensemble du logiciel, si bien que tout le logiciel connaît tout le logiciel et est autorisé à modifier les structures de données sur place depuis n’importe où dans le code.
  3. La complexité est « résolue » par davantage de complexité, et personne ne semble y voir un problème appelé dette technique.

Les bases de code saines protègent les données en les rendant privées et en imposant les interactions via des fonctions getter/setter définies comme API publiques. Cela permet de gérer facilement la sûreté vis-à-vis des threads quand le besoin apparaît, mais aussi de suivre depuis un point central qui écrit où et quand. Ensuite, on n’inclut dans chaque fichier que le jeu minimal d’API nécessaire, afin d’isoler le code autant que possible.

Au lieu de cela, le code source de Darktable écrit partout depuis n’importe où, sans interfaces correctes. Donc le moindre changement dans la base de code de Darktable déclenche systématiquement des effets de bord et casse quelque chose d’inattendu à l’autre bout du logiciel, dans des séances déprimantes de whack-a-mole nées de la paresse et de la stupidité.

C’est pourquoi beaucoup de travail ingrat a été réalisé dans Ansel pour resserrer les API, privatiser autant de structures de données que possible et isoler les bibliothèques les unes des autres afin de démêler quelque peu le graphe de dépendances.

Annexe : les schémas

Architecture générale

Voici l’organigramme de haut niveau de toute l’architecture, de la base de données jusqu’aux tampons de sortie. Nous pouvons enfin dessiner un schéma de ce bazar.

flowchart TD
  subgraph Module
    M["Module instance
dt_iop_module_t"] end subgraph Develop H["dev->history
history items"] D["dt_develop_t
history_end
history_hash"] end subgraph Persist["DB + image cache"] DB["SQLite history tables"] IC["image cache
dt_image_t"] end subgraph Pipeline F["Pipe dirty flags"] P["Pipe nodes / pieces"] G["global_hash"] B["Backbuf"] end subgraph Cache C["Pixelpipe cache"] end subgraph Consumers["Backbuffer consumers"] DR["darkroom.c"] NAV["navigation.c"] EXP["imageio.c"] end M -->|"write snapshot
dt_dev_add_history_item_real()"| H H -->|"update history_end / history_hash
dt_dev_add_history_item_real()"| D D -->|"write history
dt_dev_write_history_ext()"| DB D -->|"write image-side hash
dt_dev_write_history_ext()"| IC DB -.->|"read history
dt_dev_read_history_ext()"| H IC -.->|"read image metadata
dt_dev_read_history_ext()"| H H -->|"apply to modules
dt_dev_pop_history_items_ext()"| M D -->|"mark pipes dirty
dt_dev_add_history_item_real()"| F D -->|"check history mismatch
dt_dev_darkroom_pipeline()"| F H -->|"sync history into pipe
dt_dev_pixelpipe_change()"| P F -->|"consume dirty flags
dt_dev_pixelpipe_change()"| P P -->|"compute cache keys
dt_pixelpipe_get_global_hash()"| G G -->|"key cache entries
dt_dev_pixelpipe_process_rec()"| C C -.->|"read exact-hit / input
dt_dev_pixelpipe_process_rec()"| P P -->|"publish stage output
dt_dev_pixelpipe_process_rec()"| C C -->|"promote final output
dt_dev_pixelpipe_process()"| B B -->|"borrow main/preview backbuf
_darkroom_expose()"| DR B -->|"copy preview backbuf
_lib_navigation_draw_callback()"| NAV B -->|"borrow export backbuf
dt_imageio_export_with_flags()"| EXP EXP -->|"update mipmap hash
dt_imageio_export_with_flags()"| IC

Gestion de la mémoire RAM/vRAM

Gestion des tampons du cache :

flowchart TD
  H["CPU RAM buffer"]
  P["Pinned vRAM buffer
CL_MEM_USE_HOST_PTR"] V["Device-only vRAM buffer"] G["pixelpipe_process_on_GPU()"] C["pixelpipe_process_on_CPU()"] H -->|"prepare GPU input
dt_dev_pixelpipe_cache_prepare_cl_input()"| P P -->|"sync host/device if needed
dt_dev_pixelpipe_cache_sync_cl_buffer()"| G V -->|"reuse existing vRAM input
dt_dev_pixelpipe_cache_prepare_cl_input()"| G G -->|"publish pinned output
dt_dev_pixelpipe_cache_release_cl_buffer()"| P G -->|"publish device-only output
dt_dev_pixelpipe_cache_release_cl_buffer()"| V V -->|"GPU to CPU fallback copy
dt_dev_pixelpipe_cache_restore_cl_buffer()"| H H -->|"resume CPU processing
pixelpipe_process_on_CPU()"| C

Si la sortie du module est destinée à être mise en cache, en raison des préférences utilisateur, du besoin de ce tampon pour l’histogramme ou la pipette couleur, ou parce que le module suivant est celui qui est actif dans l’interface, OpenCL utilise un tampon mémoire épinglé, épinglé sur le cache RAM. Nous épinglons aussi les tampons d’entrée lorsque le GPU prend ses entrées depuis le cache RAM. C’est légèrement plus coûteux que d’utiliser un simple tampon réservé au périphérique, mais moins coûteux que de recopier ce tampon réservé au périphérique vers l’hôte. Si la sortie OpenCL n’est pas destinée à être mise en cache, nous utilisons un tampon réservé au périphérique qui pourra être recyclé plus tard mais ne sera jamais copié en RAM.

En cas d’erreur OpenCL sur un module donné, la plus évidente étant un manque de mémoire, le tampon d’entrée GPU est recopié de manière transparente dans le cache RAM avant de basculer vers le chemin d’exécution purement CPU.

Tous ces tampons sont suivis par des entrées de cache, qui associent un tampon RAM à un tampon vRAM, si les deux sont utilisés dans une situation épinglée, ou se contentent de suivre ce qui est alloué. Si une allocation mémoire GPU échoue, nous parcourons d’abord le cache pour libérer tous les tampons précédemment alloués. Si l’allocation mémoire GPU échoue encore après cela, nous revenons au CPU.

Entrées de cache

Le cache est une table de hachage d’entrées de cache. Leur clé est un hash ou une somme de contrôle. Nous avons 2 types d’entrées de cache :

  • les internes, utilisées comme tampons réutilisables pour les entrées/sorties des modules,
  • les externes, utilisées comme tampons jetables pour les calculs intermédiaires des modules.

Les entrées de cache internes sont indexées par le global_hash de leur module parent et peuvent être retrouvées plus tard, même depuis l’interface, parce que ce hash reste stable d’une exécution à l’autre et d’un pipeline à l’autre. Elles peuvent être réindexées si nous réutilisons leur tampon mémoire, afin que la nouvelle clé corresponde au nouvel état du module parent. Ces entrées de cache peuvent être verrouillées en lecture ou en écriture pour permettre un accès concurrent sûr entre threads parallèles. Un verrou d’écriture ne peut être pris que par un seul thread si aucun autre ne lit, tandis qu’un verrou de lecture peut être pris par plusieurs threads si aucun n’écrit. Comme le verrou d’écriture est relâché par le module qui écrit sa sortie avant que le verrou de lecture ne soit acquis par le module qui utilise la même entrée de cache comme entrée, avec un risque de suppression entre les deux, elles disposent aussi d’un mécanisme de refcount : le producteur de l’entrée de cache l’incrémente, le consommateur la décrémente. Le même mécanisme est utilisé par la chambre noire pour récupérer le backbuffer sans copie.

Les entrées de cache externes sont indexées par l’adresse mémoire de leur tampon de données. Elles sont supposées être de courte durée de vie.

L’ajout ou la suppression d’entrées de cache incrémente ou décrémente automatiquement la taille globale du cache. Nous continuons à ajouter de nouvelles entrées tant que la taille globale du cache reste sous le seuil autorisé, après quoi nous commençons à détruire les plus anciennes si leur refcount vaut 0 et qu’elles ne sont pas verrouillées.

flowchart TD
  A["absent"]
  B["reserved for write
refcount + 1
write-locked"] C["published
exact-hit eligible"] D["borrowed by consumer
refcount > 0"] E["backbuf-held
one display ref kept"] F["auto-destroy flagged"] G["removed / freed"] A -->|"create new entry
dt_dev_pixelpipe_cache_get()
dt_dev_pixelpipe_cache_get_writable()"| B C -->|"rekey reusable entry
dt_dev_pixelpipe_cache_get_writable()
dt_dev_pixelpipe_cache_rekey()"| B B -->|"backend finishes and unlocks
dt_dev_pixelpipe_process_rec()"| C C -->|"exact-hit / reopen / GUI read-only
dt_dev_pixelpipe_cache_peek()
dt_dev_pixelpipe_cache_get_read_only()"| D D -->|"release consumer ref
dt_dev_pixelpipe_cache_unref_hash()
dt_dev_pixelpipe_cache_close_read_only()"| C C -->|"keep final output alive for display
_update_backbuf_cache_reference()"| E E -->|"backbuf changes or invalidates
_update_backbuf_cache_reference()"| C C -->|"mark transient output
dt_dev_pixelpipe_cache_flag_auto_destroy()"| F F -->|"reap when no ref and no lock remain
dt_dev_pixelpipe_cache_auto_destroy_apply()"| G C -->|"explicit remove / GC / LRU
dt_dev_pixelpipe_cache_remove()
dt_dev_pixelpipe_cache_flush_old()
dt_dev_pixel_pipe_cache_remove_lru()"| G C -->|"invalid payload detected
dt_dev_pixelpipe_cache_peek()"| G

Une entrée de cache ne connaît pas la nature des données qu’elle contient. Elle ne connaît que l’adresse des tampons, leur taille, leur compteur de références et leur état de verrouillage. C’est donc une abstraction générique de choses stockées en mémoire.


Translated from English by : Aurélien Pierre, ChatGPT. In case of conflict, inconsistency or error, the English version shall prevail.